Ecological
combines PEP526 and environment variables to make the configuration of
12 factor apps easy.
Ecological
automatically gets and converts environment variables according to the configuration class definition.
For example, imagine your application has a configurable (integer) Port and (boolean) Debug flag and a (string) Log
Level, that is INFO
by default, you could simply declare your configuration as:
class Configuration(ecological.Config):
port: int
debug: bool
log_level: str = "INFO"
And then set the environment variables PORT
, DEBUG
and LOG_LEVEL
. Ecological
will automatically set the
class properties from the environment variables with the same (but upper cased) name.
By default the values are set at the class definition type and assigned to the class itself (i.e. the class doesn't need to be instantiated). If needed this behavior can be changed (see the Autoloading section).
The tutorial can be used to get to know with the library's basic features interactively.
Ecological
also supports some of the types defined in PEP484, for example:
class Configuration(ecological.Config):
list_of_values: List[str]
Will automatically parse the environment variable value as a list.
Note
Please note that while this will ensure Configuration.list_of_values
is a list it will not check that it
contains only strings.
You can also decide to prefix your application configuration, for example, to avoid collisions:
class Configuration(ecological.Config, prefix='myapp'):
home: str
In this case the home
property will be fetched from the MYAPP_HOME
environment property.
Ecological.Config
also supports nested configurations, for example:
class Configuration(ecological.Config):
integer: int
class Nested(ecological.Config, prefix='nested'):
boolean: bool
This way you can group related configuration properties hierarchically.
You can control some behavior of how the configuration properties are set.
It can be achieved by providing a ecological.Variable
instance as the default
value for an attribute or by specifying global options on the class level:
my_source = {"KEY1": "VALUE1"}
class Configuration(ecological.Config, transform=lambda v, wt: v, wanted_type=int, ...):
my_var1: WantedType = ecological.Variable(transform=lambda v, wt: wt(v), source=my_source, ...)
my_var2: str
# ...
All possible options and their meaning can be found in the table below:
Option | Class level | Variable level | Default | Description |
---|---|---|---|---|
prefix |
yes | no | None |
A prefix that is uppercased and prepended when a variable name is derived from an attribute name. |
variable_name |
yes | yes | Derived from attribute name and prefixed
with prefix if specified; uppercased. |
When specified on the variable level it states the exact name of the source variable that will be used. When specified on the class level it is treated as a function that returns a variable name from the attribute name with the following signature:
|
default |
no | yes | (no default) | Default value for the property if it isn't set. |
transform |
yes | yes | A source value is casted to the wanted_type
In case of non-scalar types (+ scalar bool )
the value is Python-parsed first. |
A function that converts a value from the
|
source |
yes | yes | os.environ |
Dictionary that the value will be loaded from. |
wanted_type |
yes | yes | str |
Desired Python type of the attribute's value. On the variable level it is specified via a type annotation on
the attribute: However it can be also specified on the class level, then it acts as a default when the annotation is not provided:
|
The following rules apply when options are resolved:
- when options are specified on both levels (variable and class), the variable ones take precedence over class ones,
- when some options are missing on the variable level, their default values are taken from the class level,
- it is not necessary to assign an
ecological.Variable
instance to change the behavior; it can still be changed on the class level (globally).
It is possible to defer/disable autoloading (setting) of variable values by specifying the autoload
option on class definition.
When no option is provided values are loaded immediately on class creation and assigned to class attributes:
class Configuration(ecological.Config):
port: int
# Values already read and set at this point.
# assert Configuration.port == <value-of-PORT-env-var>
When this option is chosen, no autoloading happens. In order to set variable values, the Config.load
method needs to be called explicitly:
class Configuration(ecological.Config, autoload=ecological.Autoload.NEVER):
port: int
# Values not set at this point.
# Accessing Configuration.port would throw AttributeError.
Configuration.load()
# Values read and set at this point.
# assert Configuration.port == <value-of-PORT-env-var>
If it is preferred to load and store attribute values on the object instance instead of the class itself, the Autoload.OBJECT
strategy can be used:
class Configuration(ecological.Config, autoload=ecological.Autoload.OBJECT):
port: int
# Values not set at this point.
config = Configuration()
# Values read and set at this point on ``config``.
# assert config.port == <value-of-PORT-env-var>
# Accessing ``Configuration.port`` would throw AttributeError.
Ecological
doesn't support (public) methods inConfig
classes