API Documentation#
This section provides the API documentation for Keep It Secret.
The Secrets Class#
- class keep_it_secret.Secrets(parent: Secrets | None = None)#
The base Secrets class, used to declare application-specfic secrets containers.
Example:
class AppSecrets(Secrets): secret_key: str = AbstractField.new() db_password: str = EnvField.new('APP_DB_PASSWORD', required=True) not_a_secret = 'spam' def do_something(self) -> bool: return 'eggs'
When instantiated,
AppSecrets
will evaluate each of the fields and fill in instance properties with the appropriate values. Attributes which don’t evaluate toField
instances will not be modified.Secrets classes retain their behaviour when they’re subclassed. This allows the developer to compose env-specific secrets:
class DevelopmentSecrets(AppSecrets): secret_key: str = LiteralField.new('thisisntsecure')
In this case, the
secret_key
field gets overloaded, while all the others remain as declared inAppSecrets
.- Parameters:
parent – The parent
Secrets
subclass orNone
.
- UNRESOLVED_DEPENDENCY: list[Any] = []#
Sentinel for unresolved dependency.
- resolve_dependency(name: str, *, include_parents: bool = True) Any #
Resolve a dependency field identified by name and return its value. returns
keep_it_secret.Secrets.UNRESOLVED_DEPENDENCY
if the value can’t be resolved.- Parameters:
include_parents – Recursively include parents, if any.
Base Field#
- class keep_it_secret.Field(*, as_type: type | None = <class 'str'>, required: bool = True, description: str | None = None)#
Base class for fields.
Example:
class SpamField(Field): def get_value(self, secrets): return 'spam' class AppSecrets(Secrets): spam: str = SpamField.new()
Normal instantiation and using the
new()
factory method are functionally equal. Thenew()
method is provided for compatibility with type annotations.>>> spam_one = SpamField.new() >>> spam_two = SpamField() >>> spam_one(secrets) == spam_two(secrets) True
The field is evaluated when its instance is called. This is done during initialization of the
keep_it_secret.Secrets
subclass in which the field was used. The result of this is either the value cast toas_type
(if specified) orNone
.Note that the base class doesn’t enforce the
required
flag. Its behaviour is implementation specific.- Parameters:
as_type – Type to cast the value to. If
None
, no casting will be done. Defaults tostr
.required – Required flag. Defaults to
True
.description – Human readable description. Defaults to
None
.
- exception KeepItSecretFieldError#
Base class for field exceptions.
- exception RequiredValueMissing#
Raised when the field’s value is required but missing.
- exception DependencyMissing#
Raised when the field depends on another, which isn’t defined.
- classmethod new(**field_options) Any #
The field factory. Constructs the field in a manner which is compatible with type annotations.
Positional arguments, keyword arguments and field_options are passed to the constructor.
Concrete Fields#
- class keep_it_secret.AbstractField(*, as_type: type | None = <class 'str'>, required: bool = True, description: str | None = None)#
The “placeholder” field. Use it in a
keep_it_secret.Secrets
subclass to indicate that the field must be overloaded by a subclass.Instances will raise
NotImplementedError
during evaluation.Example:
class BaseSecrets(Secrets): secret_key: str = AbstractField.new() class DevelopmentSecrets(BaseSecrets): secret_key: str = LiteralField.new('thisisntsecure') class ProductionSecrets(BaseSecrets): secret_key: str = EnvField.new('APP_SECRET_KEY', required=True)
Trying to instantiate
BaseSecrets
will fail:>>> secrets = BaseSecrets() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/Users/bilbo/Projects/PLAYG/keep_it_secret/keep_it_secret/secrets.py", line 105, in __init__ self.__secrets_data__[field_name] = getattr(self, field_name) File "/Users/bilbo/Projects/PLAYG/keep_it_secret/keep_it_secret/secrets.py", line 27, in getter instance.__secrets_data__[field_name] = field(instance) File "/Users/bilbo/Projects/PLAYG/keep_it_secret/keep_it_secret/fields.py", line 83, in __call__ value = self.get_value(secrets) File "/Users/bilbo/Projects/PLAYG/keep_it_secret/keep_it_secret/fields.py", line 171, in get_value raise NotImplementedError('Abstract field must be overloaded: `%s`' % self.name) NotImplementedError: Abstract field must be overloaded: `BaseSecrets.secret_key`
Instantiating
DevelopmentSecrets
will work as expected:>>> secrets = DevelopmentSecrets() >>> secrets.secret_key 'thisisntsecure'
- class keep_it_secret.EnvField(key: str, default: Any | None = None, **field_options)#
Concrete
keep_it_secret.Field
subclass that usesos.environ
to resolve the value.Example:
>>> db_password = EnvField('APP_DB_PASSWORD') >>> db_password(secrets) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/Users/bilbo/Projects/PLAYG/keep_it_secret/keep_it_secret/fields.py", line 83, in __call__ if value is None: File "/Users/bilbo/Projects/PLAYG/keep_it_secret/keep_it_secret/fields.py", line 129, in get_value def new(cls: type[EnvField], key: str, default: typing.Any = None, **field_options) -> typing.Any: keep_it_secret.fields.Field.RequiredValueMissing: APP_DB_PASSWORD >>> os.environ['APP_DB_PASSWORD'] = 'spam' >>> db_password(secrets) 'spam'
- Parameters:
key – Environment dictionary key.
default – Default value. Defaults to
None
.
- classmethod new(key: str, default: Any | None = None, **field_options) Any #
The field factory. Constructs the field in a manner which is compatible with type annotations.
Positional arguments, keyword arguments and field_options are passed to the constructor.
- get_value(secrets: Secrets) Any #
Resolve the value using
os.environ
.- Raises:
RequiredValueMissing – Signal the field’s value is required but key is not present in the environment.
- class keep_it_secret.LiteralField(value: Any, **field_options)#
Concrete
keep_it_secret.Field
subclass that wraps a literal value.Example:
>>> spam = LiteralField('spam') >>> spam(secrets) 'spam' >>> one = LiteralField(1) >>> one() >>> one(secrets) 1 >>> anything_works = LiteralField(RuntimeError('BOOM')) >>> anything_works(secrets) RuntimeError('BOOM')
- Parameters:
value – The value to wrap.
- classmethod new(value: Any, **field_options) Any #
The field factory. Constructs the field in a manner which is compatible with type annotations.
Positional arguments, keyword arguments and field_options are passed to the constructor.
- class keep_it_secret.SecretsField(klass: type[Secrets], **field_options)#
Concrete
keep_it_secret.Field
subclass that wraps akeep_it_secret.Secrets
subclass. Provides API to declare complex secret structures.Example:
class WeatherAPICredentials(Secrets): username: str = LiteralField('spam') password: str = EnvField.new('APP_WEATHER_API_PASSWORD', required=True) class AppSecrets(Secrets): db_password: str = EnvField.new('APP_DB_PASSWORD', required=True) weather_api: WeatherAPICredentials = SecretsField(WeatherAPICredentials)
>>> secrets = AppSecrets() >>> secrets.weather_api.password 'eggs' >>> secrets.weather_api.__secrets_parent__ == secrets True
- Parameters:
klass –
keep_it_secret.Secrets
subclass to wrap.