Configure your program via environment variables, validated by pydantic.
Project description
Umwelt
Describe a configuration schema with dataclasses or pydantic and load values from the environment, in a static-typing-friendly way.
>>> os.environ["APP_DB_PORT"] = "32"
import umwelt
from typing import Tuple
from pydantic import UrlStr
@umwelt.subconfig # only needed on nested configs
class DbConfig:
port: int
class MyConfig:
db: DbConfig # nested
hosts: Tuple[UrlStr, ...] = ("http://b.org", "http://sky.net") # default value
config = umwelt.new(MyConfig, prefix="app")
>>> is_dataclass(config)
True
>>> config.hosts
("http://b.org", "http://sky.net")
>>> config.db.port
32
Install
$ pip install umwelt
Features
umwelt.new
umwelt.new
expects one positional argument: the config class to fill.
Umwelt will convert it into a dataclass if it's not one already.
umwelt.new
also accepts named arguments:
source
(by defaultos.environ
) is aMapping[str, str]
from which values are extracted.prefix
can be a string or a callable. As a string, it is prepended to the config field's name. As a callable, it receives the config field's name and its result is the source key name.decoder
is a callable expecting a type and a string, and returns a conversion of that string in that type, or in a type that pydantic can convert in that type. For example, when umwelt's defaultdecoder
is called with (List[Set[int]]
,"[[1]]"
), it simply decodes the string from JSON and hence returns a list of lists, which pydantic properly converts into a list of sets.
@umwelt.subconfig
@umwelt.subconfig
tags classes so that, when they appear as field annotations
in another config class, umwelt.new
doesn't instantiate them from a single
source
value, but rather from one source
value per class field.
Example:
class Point: # no @subconfig
def __init__(self, s: str): # string input
self.x, self.y = s.split(",", 1) # arbitrary implementation
class MyConf:
point: Point
conf = umwelt.new(MyConf, source={"POINT": "1,2"}) # one source entry
conf.point # <Point at 0x7f07b1d04750>
conf.point
is an instance of Point, built by passing the input value "1,2"
directly to Point.__new__
.
There is only one source
key: POINT
.
Now compare with @umwelt.subconfig
:
@umwelt.subconfig
class Point:
x: int
y: int
class MyConf:
point: Point
conf = umwelt.new(MyConf, source={"POINT_X": "1", "POINT_Y": "2"})
conf.point # Point(x=1, y=2)
conf.point
is still an instance of Point (Point has been made a
dataclass by Umwelt, hence the automatic __str__
implementation).
There are two source
keys: POINT_X
and POINT_Y
, each corresponding to
a field of the Point class.
Comparison with Ecological
I've used Ecological for a long time. Today, a large part of Ecological's codebase implements features already found in dataclasses and pydantic, which are more mature. I believe Ecological's design can be dramatically simplified and improved by enforcing a strict separation of concerns:
- class scaffolding is the responsibility of dataclasses (which, compared
to metaclasses, is simpler, more introspectable, and comes with helpers like
asdict
); - type coercion and validation is the responsibility of pydantic (which has more features, e.g. nested data types, JSON Schema, serialization, etc.);
- mapping a pydantic schema (the configuration class) to a string-to-string
dict (like
os.environ
) is the responsibility of Umwelt.
Some compatibility-breaking decisions prevent from doing this in Ecological:
- Don't autoload configuration values, especially not at class definition time.
Instead, offer just one function (
umwelt.new
) that loads the configuration when it is called. - Don't tie variable prefixes to configuration classes, as that doesn't play well with nested configurations.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for umwelt-2019.8.1-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8d93527aee7bab5c30f2601647404d245953b0bd5b6f2003398f3fdf423cb09f |
|
MD5 | ec15fb7bdcc00b61644741a0005c092b |
|
BLAKE2b-256 | 8fc0802797bc29f5a0f6b7a1b0387584064c5199658dd72db71fd2cc1afb89e5 |