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))