Coverage for src / ocarina / opinionated / infra / env.py: 0.00%
13 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-06-04 15:56 +0200
« prev ^ index » next coverage.py v7.13.5, created at 2026-06-04 15:56 +0200
1"""Typed environment variable accessor with key autocompletion support.
3Provides a generic, type-safe interface for accessing environment variables,
4with IDE autocompletion and mypy validation on keys.
6Keys are defined as Literal types in the consuming project, giving full
7static analysis support without any runtime overhead.
9Example:
10 >>> import os
11 >>> from typing import Literal
12 >>> from types import MappingProxyType
13 >>>
14 >>> type MyCredKeys = Literal["intranet", "api_service"]
15 >>> type MyValueKeys = Literal["expected_fullname"]
16 >>>
17 >>> def _creds(login_key: str, password_key: str) -> ImmutableCredentials:
18 ... return MappingProxyType({
19 ... "login": os.environ[login_key],
20 ... "password": os.environ[password_key],
21 ... })
22 >>>
23 >>> def create_env_getters() -> EnvGetters[MyCredKeys, MyValueKeys]:
24 ... return EnvGetters(
25 ... credentials={
26 ... "intranet": _creds("INTRANET_LOGIN", "INTRANET_PASSWORD"),
27 ... "api_service": _creds("API_LOGIN", "API_PASSWORD"),
28 ... },
29 ... values={
30 ... "expected_fullname": os.environ["EXPECTED_FULLNAME"],
31 ... },
32 ... )
33 >>>
34 >>> env = create_env_getters()
35 >>> env.get_credentials("intranet") # ✅ autocomplete + mypy OK
36 >>> env.get_credentials("unknown") # ❌ mypy error
37 >>> env.get_value("expected_fullname") # ✅ autocomplete + mypy OK
38 >>> env.get_value("typo") # ❌ mypy error
40"""
42from types import MappingProxyType
43from typing import Literal
45ImmutableCredentialsKeys = Literal["login", "password"]
46type ImmutableCredentials = MappingProxyType[ImmutableCredentialsKeys, str]
47"""Read-only credential mapping. Prevents accidental mutation after retrieval."""
50class EnvGetters[TCredKeys: str, TValueKeys: str]:
51 """Type-safe accessor for environment variables.
53 Generic over TCredKeys and TValueKeys — both are inferred from the
54 dicts passed at construction, no manual annotation needed when using
55 a factory function that declares the return type explicitly.
57 Args:
58 credentials: Mapping of credential keys to immutable credential pairs.
59 values: Mapping of value keys to string values. Optional.
61 """
63 def __init__(
64 self,
65 credentials: dict[TCredKeys, ImmutableCredentials] | None = None,
66 values: dict[TValueKeys, str] | None = None,
67 ) -> None:
68 """Initialize the env getter.
70 Args:
71 credentials: Mapping of credential keys to immutable credential pairs.
72 Keys become the valid arguments for get_credentials().
73 values: Mapping of value keys to string values.
74 Keys become the valid arguments for get_value().
75 Defaults to empty dict if not provided.
77 """
78 self._credentials: dict[TCredKeys, ImmutableCredentials] = credentials or {}
79 self._values: dict[TValueKeys, str] = values or {}
81 def get_credentials(self, k: TCredKeys) -> ImmutableCredentials:
82 """Return the immutable credentials for the given key."""
83 return self._credentials[k]
85 def get_value(self, k: TValueKeys) -> str:
86 """Return the string value for the given key."""
87 return self._values[k]