Umwelt
Describe a configuration schema with dataclasses or pydantic and load values from the environment, in a static-typing-friendly way.
Examples
Flat
>>> os.environ["APP_HOSTS"] = '["b.org","sky.net"]'
>>> os.environ["APP_TOKEN"] = "very secret"
from typing import Sequence
from pydantic import SecretStr
import umwelt
class MyConfig:
hosts: Sequence[str]
token: SecretStr
replicas: int = 2
config = umwelt.new(MyConfig, prefix="app")
>>> dataclasses.is_dataclass(config)
True
>>> config.hosts
["b.org", "sky.net"]
>>> config.token
SecretStr('**********')
>>> config.replicas
2
Nested
>>> os.environ["APP_DB_PORT"] = "32"
from __future__ import annotations # for forward-references
from pydantic import UrlStr
import umwelt
class MyConfig:
db: DbConfig
host: UrlStr = "http://b.org"
@umwelt.subconfig
class DbConfig:
port: int
debug: bool = False
config = umwelt.new(MyConfig, prefix="app")
>>> config.host
"http://b.org"
>>> 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.prefixcan 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.decoderis 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 defaultdecoderis 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.
Release files for umwelt 2019.8.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| umwelt-2019.8.2.tar.gz | 5.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| umwelt-2019.8.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 12.0 kB
Release files / umwelt-2019.8.2.tar.gz
| Download URL | umwelt-2019.8.2.tar.gz |
|---|---|
| Size | 5.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
72c2c095e3b4aa059fb0082d0e70f117641782b4a35459f6a6fdeef85c873dbf
|
|
BLAKE2b-256 checksum How to use checksums |
e71ae85037f106b8f6035262b8bd50046c6a0a0a5e56dd6e23b6c32bb378578a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/0.12.16 CPython/3.7.4 Linux/5.2.7-100.fc29.x86_64
|
Release files / umwelt-2019.8.2-py3-none-any.whl
| Download URL | umwelt-2019.8.2-py3-none-any.whl |
|---|---|
| Size | 6.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4e6b8c9aa33324fab9807975051548953f7cc524af5301059028e2f1fc91cf0e
|
|
BLAKE2b-256 checksum How to use checksums |
d3dc2fcaa9d72457c2bbcbe0aa6897d3443848044b7d7bcc0ef089b328ed30b2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/0.12.16 CPython/3.7.4 Linux/5.2.7-100.fc29.x86_64
|