Override config values per instance¶
EnvConfig accepts keyword arguments at construction. Overrides take
precedence over the environment, so you can layer env-derived config with
values from any other source — a YAML file, CLI arguments, programmatic
wiring, test fixtures — without touching os.environ.
from env_proxy import EnvConfig, EnvProxy, Field
class AppConfig(EnvConfig):
env_proxy = EnvProxy(prefix="APP")
timeout: int = Field(default=30)
services: list[str] = Field(default=[])
# Layer env with values loaded from a YAML file:
file_config = load_yaml("app.yaml") # {"timeout": 5, "services": ["redis", "rabbitmq"]}
cfg = AppConfig(**file_config)
assert cfg.timeout == 5
assert cfg.services == ["redis", "rabbitmq"]
Semantics¶
- Keys are Python field names (not env-var keys), regardless of any
aliasorenv_prefix. - Values are used as-is — no string parsing or type conversion. Pass
real
int,list,dict, etc. - Overrides shadow the environment for reads on that instance only;
other instances and direct
os.environaccess are unaffected. - Unknown override keys raise
ValueError, listing the valid field names — typo-proof. - Fields with
allow_set=Falsecan be initialised via override but cannot be reassigned afterwards; theallow_setcontract is unchanged. - For fields with
allow_set=True, assignment after construction updates both the override entry andos.environ(preserving the existing side-effect contract).
The rationale behind these choices lives in Override semantics.
Static type-checking¶
EnvConfig is decorated with PEP 681's dataclass_transform, so mypy and
Pyright/Pylance synthesise a typed __init__ from each subclass's
annotated fields:
AppConfig(timout=5) # ✗ unknown field — type-check error
AppConfig(timeout="bad") # ✗ wrong type — type-check error
AppConfig(timeout=5) # ✓ ok
IDEs autocomplete field names with their declared types.
Use in tests¶
Overrides are particularly handy in tests — they give you full control
without monkey-patching os.environ: