In [1]:
import os
os.environ["INTEGER_LIST"] = "[1, 2, 3, 4, 5]"
os.environ["DICTIONARY"] = "{'key': 'value'}"
os.environ["INTEGER"] = "42"
os.environ["BOOLEAN"] = "False"
os.environ["OVERRIDE_DEFAULT"] = "This is NOT the default value"
Now let's create a configuration class:
In [2]:
import ecological
class Configuration(ecological.Config):
integer_list: list
integer: int
dictionary: dict
boolean: bool
with_default: str = "This is the default value"
override_default: str = "This is the default value"
Easy right? Now that we created the configuration class. Let's look at what's inside:
In [3]:
print(repr(Configuration.integer_list))
print(type(Configuration.integer_list))
In [4]:
print(repr(Configuration.integer))
print(type(Configuration.integer))
In [5]:
print(repr(Configuration.dictionary))
print(type(Configuration.dictionary))
In [6]:
print(repr(Configuration.boolean))
print(type(Configuration.boolean))
In [7]:
print(repr(Configuration.with_default))
print(type(Configuration.with_default))
In [8]:
print(repr(Configuration.override_default))
print(type(Configuration.override_default))
As you can see all the values where cast from str
to the expected types, and if a default value is set it will be used if the corresponding environment variable doesn't exist.
Ecological also supports some of the types defined in PEP 484, for example:
In [9]:
from typing import List, Dict
class ConfigurationTyping(ecological.Config):
integer_list: List
dictionary: Dict
As expected the variables were converted to the real types:
In [10]:
print(repr(ConfigurationTyping.integer_list))
print(type(ConfigurationTyping.integer_list))
In [11]:
print(repr(ConfigurationTyping.dictionary))
print(type(ConfigurationTyping.dictionary))
In [12]:
os.environ["HOME"] = "/home/myuser/"
os.environ["VALUE"] = "Not Prefixed"
os.environ["CONFIG_HOME"] = "/app/home"
os.environ["CONFIG_VALUE"] = "Prefixed"
class ConfigurationPrefix(ecological.Config, prefix="config"):
home: str
value: str
In this case the home
and value
properties will be fetched from the CONFIG_HOME
and CONFIG_VALUE
environment properties:
In [13]:
print(repr(ConfigurationPrefix.home))
In [14]:
print(repr(ConfigurationPrefix.value))
You can control how the configuration properties are set by providing a ecological.Variable instance as the default value.
ecological.Variable
receives the following parameters:
variable_name
(optional) - exact name of the environment variable that will be used.
default
(optional) - default value for the property if it isn't set.
transform
(optional) - function that converts the string in the environment to the value and type you expect in your application. The default transform
function will try to cast the string to the annotation type of the property.
The transformation function receive two parameters, a string representation with the raw value, and a wanted_type with the value of the annotation (usually, but not necessarily a type).
In [22]:
os.environ["Integer"] = "42"
def times_2(value, wanted_type):
assert wanted_type is int
return int(value) * 2
class ConfigurationVariable(ecological.Config, prefix="this_is_going_to_be_ignored"):
integer = ecological.Variable("Integer", transform=lambda v, wt: int(v))
integer_x2: int = ecological.Variable("Integer", transform=times_2)
integer_as_str: str = ecological.Variable("Integer", transform=lambda v, wt: v)
boolean: bool = ecological.Variable("404", default=False)
integer
, integer_x2
and integer_as_str
will use the same enviroment variable but return different values:
In [17]:
print(repr(ConfigurationVariable.integer))
In [20]:
print(repr(ConfigurationVariable.integer_x2))
In [23]:
print(repr(ConfigurationVariable.integer_as_str))
Because the environment variable 404
is not set, boolean
will have the default value:
In [24]:
print(repr(ConfigurationVariable.boolean))
In [26]:
os.environ["INTEGER"] = "42"
os.environ["NESTED_BOOLEAN"] = "True"
class ConfigurationNested(ecological.Config):
integer: int
class Nested(ecological.Config, prefix='nested'):
boolean: bool
This way you can group related configuration properties hierarchically:
In [28]:
print(repr(ConfigurationNested.integer))
In [27]:
print(repr(ConfigurationNested.Nested.boolean))