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 to Field 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 in AppSecrets.

Parameters:

parent – The parent Secrets subclass or None.

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. The new() 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 to as_type (if specified) or None.

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 to str.

  • 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.

__call__(secrets: Secrets) Any#

Evaluate the field and return the value.

abstract get_value(secrets: Secrets) Any#

Get and return the field’s value. Subclasses must implement this method.

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'
get_value(secrets: Secrets) Any#
Raises:

NotImplementedError – Signal that the field needs to be overloaded.

class keep_it_secret.EnvField(key: str, default: Any | None = None, **field_options)#

Concrete keep_it_secret.Field subclass that uses os.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.

get_value(secrets: Secrets) Any#

Returns the wrapped value.

class keep_it_secret.SecretsField(klass: type[Secrets], **field_options)#

Concrete keep_it_secret.Field subclass that wraps a keep_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:

klasskeep_it_secret.Secrets subclass to wrap.

classmethod new(klass: type[Secrets], **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#

Instantiate the wrapped klass and return it. secrets will be passed as parent to the instance.