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.
Metadata
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 | Provenance | Actions |
|---|---|---|---|---|
| umwelt-2019.8.2.tar.gz | 5.9 kB | Without provenance | Details for umwelt-2019.8.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| umwelt-2019.8.2-py3-none-any.whl | Python 3 | none | any | Details for umwelt-2019.8.2-py3-none-any.whl |
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
|
Security for umwelt 2019.8.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.