configaroo 0.3.0__py3-none-any.whl → 0.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- configaroo/__init__.py +1 -1
- configaroo/configuration.py +70 -50
- configaroo/loaders/__init__.py +6 -1
- {configaroo-0.3.0.dist-info → configaroo-0.4.1.dist-info}/METADATA +1 -1
- configaroo-0.4.1.dist-info/RECORD +12 -0
- configaroo-0.3.0.dist-info/RECORD +0 -12
- {configaroo-0.3.0.dist-info → configaroo-0.4.1.dist-info}/WHEEL +0 -0
- {configaroo-0.3.0.dist-info → configaroo-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {configaroo-0.3.0.dist-info → configaroo-0.4.1.dist-info}/top_level.txt +0 -0
configaroo/__init__.py
CHANGED
configaroo/configuration.py
CHANGED
@@ -6,6 +6,7 @@ import re
|
|
6
6
|
from collections import UserDict
|
7
7
|
from collections.abc import Callable
|
8
8
|
from pathlib import Path
|
9
|
+
from types import UnionType
|
9
10
|
from typing import Any, Self, TypeVar
|
10
11
|
|
11
12
|
from pydantic import BaseModel
|
@@ -36,33 +37,20 @@ class Configuration(UserDict[str, Any]):
|
|
36
37
|
def from_file(
|
37
38
|
cls,
|
38
39
|
file_path: str | Path,
|
40
|
+
*,
|
39
41
|
loader: str | None = None,
|
40
|
-
|
41
|
-
env_prefix: str = "",
|
42
|
-
extra_dynamic: dict[str, Any] | None = None,
|
42
|
+
not_exist_ok: bool = False,
|
43
43
|
) -> Self:
|
44
|
-
"""Read a Configuration from a file.
|
45
|
-
config_dict = loaders.from_file(file_path, loader=loader)
|
46
|
-
return cls(config_dict).initialize(
|
47
|
-
envs=envs, env_prefix=env_prefix, extra_dynamic=extra_dynamic
|
48
|
-
)
|
44
|
+
"""Read a Configuration from a file.
|
49
45
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
env_prefix: str = "",
|
54
|
-
extra_dynamic: dict[str, Any] | None = None,
|
55
|
-
) -> Self:
|
56
|
-
"""Initialize a configuration.
|
57
|
-
|
58
|
-
The initialization adds environment variables and parses dynamic values.
|
46
|
+
If not_exist_ok is True, then a missing file returns an empty
|
47
|
+
configuration. This may be useful if the configuration is potentially
|
48
|
+
populated by environment variables.
|
59
49
|
"""
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
"""Apply a pydantic model to a configuration."""
|
65
|
-
return self.validate_model(model).convert_model(model)
|
50
|
+
config_dict = loaders.from_file(
|
51
|
+
file_path, loader=loader, not_exist_ok=not_exist_ok
|
52
|
+
)
|
53
|
+
return cls(config_dict)
|
66
54
|
|
67
55
|
def __getitem__(self, key: str) -> Any: # noqa: ANN401
|
68
56
|
"""Make sure nested sections have type Configuration."""
|
@@ -112,30 +100,6 @@ class Configuration(UserDict[str, Any]):
|
|
112
100
|
cls = type(self)
|
113
101
|
return self | {prefix: cls(self.setdefault(prefix, {})).add(rest, value)}
|
114
102
|
|
115
|
-
def add_envs(self, envs: dict[str, str] | None = None, prefix: str = "") -> Self:
|
116
|
-
"""Add environment variables to configuration.
|
117
|
-
|
118
|
-
If you don't specify which environment variables to read, you'll
|
119
|
-
automatically add any that matches a top-level value of the
|
120
|
-
configuration.
|
121
|
-
"""
|
122
|
-
if envs is None:
|
123
|
-
# Automatically add top-level configuration items
|
124
|
-
envs = {
|
125
|
-
re.sub(r"\W", "_", key).upper(): key
|
126
|
-
for key, value in self.data.items()
|
127
|
-
if isinstance(value, str | int | float)
|
128
|
-
}
|
129
|
-
|
130
|
-
# Read environment variables
|
131
|
-
for env, key in envs.items():
|
132
|
-
env_key = f"{prefix}{env}"
|
133
|
-
if env_value := os.getenv(env_key):
|
134
|
-
self = self.add(key, env_value) # noqa: PLW0642
|
135
|
-
elif key not in self:
|
136
|
-
raise MissingEnvironmentVariableError(env_key)
|
137
|
-
return self
|
138
|
-
|
139
103
|
def parse_dynamic(
|
140
104
|
self, extra: dict[str, Any] | None = None, *, _include_self: bool = True
|
141
105
|
) -> Self:
|
@@ -163,6 +127,60 @@ class Configuration(UserDict[str, Any]):
|
|
163
127
|
# Continue parsing until no more replacements are made.
|
164
128
|
return parsed.parse_dynamic(extra=extra, _include_self=_include_self)
|
165
129
|
|
130
|
+
def add_envs(self, envs: dict[str, str] | None = None, prefix: str = "") -> Self:
|
131
|
+
"""Add environment variables to configuration.
|
132
|
+
|
133
|
+
If you don't specify which environment variables to read, you'll
|
134
|
+
automatically add any that matches a simple top-level value of the
|
135
|
+
configuration.
|
136
|
+
"""
|
137
|
+
if envs is None:
|
138
|
+
# Automatically add top-level configuration items
|
139
|
+
envs = {
|
140
|
+
re.sub(r"\W", "_", key).upper(): key
|
141
|
+
for key, value in self.data.items()
|
142
|
+
if isinstance(value, str | int | float)
|
143
|
+
}
|
144
|
+
|
145
|
+
# Read environment variables
|
146
|
+
for env, key in envs.items():
|
147
|
+
env_key = f"{prefix}{env}"
|
148
|
+
if env_value := os.getenv(env_key):
|
149
|
+
self = self.add(key, env_value) # noqa: PLW0642
|
150
|
+
elif key not in self:
|
151
|
+
raise MissingEnvironmentVariableError(env_key)
|
152
|
+
return self
|
153
|
+
|
154
|
+
def add_envs_from_model(
|
155
|
+
self,
|
156
|
+
model: type[BaseModel],
|
157
|
+
prefix: str = "",
|
158
|
+
types: type | UnionType = str | bool | int | float,
|
159
|
+
) -> Self:
|
160
|
+
"""Add environment variables to configuration based on the given model.
|
161
|
+
|
162
|
+
Top level string, bool, integer, and float fields from the model are
|
163
|
+
looked for among environment variables.
|
164
|
+
"""
|
165
|
+
|
166
|
+
def _get_class_from_annotation(annotation: type) -> type:
|
167
|
+
"""Unpack generic annotations and return the underlying class."""
|
168
|
+
return (
|
169
|
+
_get_class_from_annotation(annotation.__origin__)
|
170
|
+
if hasattr(annotation, "__origin__")
|
171
|
+
else annotation
|
172
|
+
)
|
173
|
+
|
174
|
+
envs = {
|
175
|
+
re.sub(r"\W", "_", key).upper(): key
|
176
|
+
for key, field in model.model_fields.items()
|
177
|
+
if (
|
178
|
+
field.annotation is not None
|
179
|
+
and issubclass(_get_class_from_annotation(field.annotation), types)
|
180
|
+
)
|
181
|
+
}
|
182
|
+
return self.add_envs(envs, prefix=prefix)
|
183
|
+
|
166
184
|
def validate_model(self, model: type[BaseModel]) -> Self:
|
167
185
|
"""Validate the configuration against the given model."""
|
168
186
|
model.model_validate(self.data)
|
@@ -172,6 +190,10 @@ class Configuration(UserDict[str, Any]):
|
|
172
190
|
"""Convert data types to match the given model."""
|
173
191
|
return model(**self.data)
|
174
192
|
|
193
|
+
def with_model(self, model: type[ModelT]) -> ModelT:
|
194
|
+
"""Apply a pydantic model to a configuration."""
|
195
|
+
return self.validate_model(model).convert_model(model)
|
196
|
+
|
175
197
|
def to_dict(self) -> dict[str, Any]:
|
176
198
|
"""Dump the configuration into a Python dictionary."""
|
177
199
|
return {
|
@@ -217,9 +239,7 @@ def _get_rich_print() -> Callable[[str], None]: # pragma: no cover
|
|
217
239
|
|
218
240
|
return Console().print
|
219
241
|
except ImportError:
|
220
|
-
|
221
|
-
|
222
|
-
return builtins.print
|
242
|
+
return print
|
223
243
|
|
224
244
|
|
225
245
|
def _print_dict_as_tree(
|
configaroo/loaders/__init__.py
CHANGED
@@ -26,9 +26,14 @@ def loader_names() -> list[str]:
|
|
26
26
|
return sorted(pyplugs.names(PACKAGE))
|
27
27
|
|
28
28
|
|
29
|
-
def from_file(
|
29
|
+
def from_file(
|
30
|
+
path: str | Path, *, loader: str | None = None, not_exist_ok: bool = False
|
31
|
+
) -> dict[str, Any]:
|
30
32
|
"""Load a file using a loader defined by the suffix if necessary."""
|
31
33
|
path = Path(path)
|
34
|
+
if not path.exists() and not_exist_ok:
|
35
|
+
return {}
|
36
|
+
|
32
37
|
loader = path.suffix.lstrip(".") if loader is None else loader
|
33
38
|
try:
|
34
39
|
return load(loader, path=path)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
configaroo/__init__.py,sha256=yTD0mUdvhepcJSiLEBKTPDuQh8BWjrKnTB1QSfmjHRw,412
|
2
|
+
configaroo/configuration.py,sha256=1tGBLkyz703txcaGx49kWHMiVYgjTcERnikVQ69Pusc,10928
|
3
|
+
configaroo/exceptions.py,sha256=GfLf3CLfHStiQjvdS7ZAtrKF9gmGL_8biFLayue6J0M,772
|
4
|
+
configaroo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
+
configaroo/loaders/__init__.py,sha256=XQrFwCMWzQ71ykaZFPmYysDz12y_elPqxWhUMQCsq3s,1076
|
6
|
+
configaroo/loaders/json.py,sha256=fT2Lg4hPM2BuwqrDsP7bcJlepAdmEh2iKU-YVK4KmIA,306
|
7
|
+
configaroo/loaders/toml.py,sha256=jw9U78Lf-GMA8QmGIM8xMBqOhPaa8ITSMAhhN1ZNyng,256
|
8
|
+
configaroo-0.4.1.dist-info/licenses/LICENSE,sha256=rdeT6Y5bm0MUaERso7HRWpPj37Y1RD5li2lIQaMNJjc,1090
|
9
|
+
configaroo-0.4.1.dist-info/METADATA,sha256=U_sK60hkeYLqI5gEjv6UoG1l1r3w3pKhhADB4kfXqDQ,2672
|
10
|
+
configaroo-0.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
11
|
+
configaroo-0.4.1.dist-info/top_level.txt,sha256=JVYICl1cWSjvSOZuZMYm976z9lnZaWtHVRSt373QCxg,11
|
12
|
+
configaroo-0.4.1.dist-info/RECORD,,
|
@@ -1,12 +0,0 @@
|
|
1
|
-
configaroo/__init__.py,sha256=yNF3fDQx9xXJEXHtJmeYMdqy-Dnk0mHz2-EL590A858,412
|
2
|
-
configaroo/configuration.py,sha256=phBUk_hHRFQb4vCcn6WHfNOnovEmdV6hnotRJkPSz2E,10255
|
3
|
-
configaroo/exceptions.py,sha256=GfLf3CLfHStiQjvdS7ZAtrKF9gmGL_8biFLayue6J0M,772
|
4
|
-
configaroo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
configaroo/loaders/__init__.py,sha256=l2pHeD9PJ3ZQA5xUCq9nfFqw2YhAHLeTe50wvhMmTYA,977
|
6
|
-
configaroo/loaders/json.py,sha256=fT2Lg4hPM2BuwqrDsP7bcJlepAdmEh2iKU-YVK4KmIA,306
|
7
|
-
configaroo/loaders/toml.py,sha256=jw9U78Lf-GMA8QmGIM8xMBqOhPaa8ITSMAhhN1ZNyng,256
|
8
|
-
configaroo-0.3.0.dist-info/licenses/LICENSE,sha256=rdeT6Y5bm0MUaERso7HRWpPj37Y1RD5li2lIQaMNJjc,1090
|
9
|
-
configaroo-0.3.0.dist-info/METADATA,sha256=spH6HtB45ksEBsjuCJIADFjWabsQQfzTPZvCuMfV8Fk,2672
|
10
|
-
configaroo-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
11
|
-
configaroo-0.3.0.dist-info/top_level.txt,sha256=JVYICl1cWSjvSOZuZMYm976z9lnZaWtHVRSt373QCxg,11
|
12
|
-
configaroo-0.3.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|