confarg 0.0.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.
- confarg/__init__.py +60 -0
- confarg/_api.py +405 -0
- confarg/_callable.py +599 -0
- confarg/_cast.py +63 -0
- confarg/_defaults.py +38 -0
- confarg/_files.py +534 -0
- confarg/_import.py +54 -0
- confarg/_merge.py +412 -0
- confarg/_parse_cli.py +949 -0
- confarg/_parse_env.py +392 -0
- confarg/_pipeline.py +118 -0
- confarg/_serialize.py +242 -0
- confarg/_types.py +739 -0
- confarg/cli/__init__.py +15 -0
- confarg/cli/_collect.py +464 -0
- confarg/cli/argparse/__init__.py +37 -0
- confarg/cli/argparse/_build.py +1081 -0
- confarg/cli/argparse/_completion.py +362 -0
- confarg/cli/argparse/_namespace.py +170 -0
- confarg/cli/argparse/_register.py +288 -0
- confarg/cli/argparse/_spec.py +147 -0
- confarg/cli/click/__init__.py +23 -0
- confarg/cli/click/_completion.py +82 -0
- confarg/cli/click/_context.py +190 -0
- confarg/cli/click/_register.py +189 -0
- confarg/cli/cyclopts/__init__.py +21 -0
- confarg/cli/cyclopts/_context.py +197 -0
- confarg/cli/cyclopts/_register.py +238 -0
- confarg/dictexpr/__init__.py +41 -0
- confarg/dictexpr/_expressions.py +623 -0
- confarg/exceptions.py +111 -0
- confarg/typedload/__init__.py +44 -0
- confarg/typedload/_coerce.py +315 -0
- confarg/typedload/_construct.py +960 -0
- confarg-0.0.1.dist-info/METADATA +453 -0
- confarg-0.0.1.dist-info/RECORD +37 -0
- confarg-0.0.1.dist-info/WHEEL +4 -0
confarg/__init__.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
|
|
5
|
+
"""A tool to manage complex configurations.
|
|
6
|
+
|
|
7
|
+
> Load and resolve complex configurations from files, environment variables and command line arguments. Keep your data
|
|
8
|
+
> structures and favorite CLI library.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
|
|
18
|
+
from confarg import exceptions
|
|
19
|
+
from confarg._api import build, dump, dump_file, from_dict, load, merge, resolve
|
|
20
|
+
from confarg._types import TagPolicy
|
|
21
|
+
from confarg.typedload._coerce import _LEAF_COERCIONS
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def register_leaf_type(tp: type, coerce: Callable[[Any], Any]) -> None:
|
|
25
|
+
"""Register a custom leaf type.
|
|
26
|
+
|
|
27
|
+
After registration, ``tp`` is treated as a leaf: values of that type can
|
|
28
|
+
appear as scalars in config files, env vars, and CLI args. ``coerce`` is
|
|
29
|
+
called with the raw input value — either a ``_StrToken`` (a ``str``
|
|
30
|
+
subclass) from CLI/env sources, or the natively-parsed Python object from
|
|
31
|
+
config files — and must return an instance of ``tp``, raising
|
|
32
|
+
``ValueError`` or ``TypeError`` on failure.
|
|
33
|
+
|
|
34
|
+
Example::
|
|
35
|
+
|
|
36
|
+
from uuid import UUID
|
|
37
|
+
confarg.register_leaf_type(UUID, UUID)
|
|
38
|
+
"""
|
|
39
|
+
_LEAF_COERCIONS[tp] = coerce
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
__all__ = [ # noqa: RUF022
|
|
43
|
+
# Two-step API
|
|
44
|
+
"merge",
|
|
45
|
+
"build",
|
|
46
|
+
# Three-step API (dict-centric)
|
|
47
|
+
"resolve",
|
|
48
|
+
"from_dict",
|
|
49
|
+
# One-step convenience
|
|
50
|
+
"load",
|
|
51
|
+
# Dump
|
|
52
|
+
"dump",
|
|
53
|
+
"dump_file",
|
|
54
|
+
# Types
|
|
55
|
+
"TagPolicy",
|
|
56
|
+
# Leaf-type extension
|
|
57
|
+
"register_leaf_type",
|
|
58
|
+
# Exceptions / warnings
|
|
59
|
+
"exceptions",
|
|
60
|
+
]
|
confarg/_api.py
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
|
|
5
|
+
"""Public API implementation."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import TYPE_CHECKING, Any, overload
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import Mapping, Sequence
|
|
16
|
+
from types import UnionType
|
|
17
|
+
from typing import TypeAliasType
|
|
18
|
+
|
|
19
|
+
from confarg import _defaults
|
|
20
|
+
from confarg._files import _dump_file
|
|
21
|
+
from confarg._parse_cli import _parse_cli
|
|
22
|
+
from confarg._pipeline import _merge_sources
|
|
23
|
+
from confarg._serialize import _serialize
|
|
24
|
+
from confarg._types import _MISSING, TagPolicy, _is_dc, _is_struct, _is_struct_like, _resolve_type
|
|
25
|
+
from confarg._types import _StrToken as _ST
|
|
26
|
+
from confarg.dictexpr import resolve_expressions
|
|
27
|
+
from confarg.exceptions import MissingFieldError
|
|
28
|
+
from confarg.typedload import construct as _tc
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def merge( # noqa: PLR0913
|
|
32
|
+
target: type | TypeAliasType | UnionType,
|
|
33
|
+
*,
|
|
34
|
+
argv: Sequence[str] | None = None,
|
|
35
|
+
env: Mapping[str, str] | None = None,
|
|
36
|
+
env_prefix: str | None = _defaults.ENV_PREFIX,
|
|
37
|
+
env_separator: str = _defaults.ENV_SEPARATOR,
|
|
38
|
+
cli_prefix: str = "",
|
|
39
|
+
config_flag: str = _defaults.CONFIG_FLAG,
|
|
40
|
+
files: Sequence[str | Path] = (),
|
|
41
|
+
env_config: str | None = None,
|
|
42
|
+
union_tag: str = _defaults.UNION_TAG,
|
|
43
|
+
) -> dict[str, Any]:
|
|
44
|
+
"""Collect and merge configuration from all sources into a raw dict.
|
|
45
|
+
|
|
46
|
+
Sources are merged in priority order: config files (lowest), then
|
|
47
|
+
environment variables, then CLI arguments (highest). No expression
|
|
48
|
+
resolution and no dataclass construction are performed — the returned
|
|
49
|
+
dict reflects the config input exactly as written, with ${...}
|
|
50
|
+
expression strings preserved.
|
|
51
|
+
|
|
52
|
+
The returned dict is **unvalidated**: it is not guaranteed to be
|
|
53
|
+
constructible into ``target``. Type validation and coercion happen in
|
|
54
|
+
``build()`` (or the one-shot ``load()``), which raises ``TypeCoercionError``
|
|
55
|
+
/ ``MissingFieldError`` for data that cannot be built. CLI parsing rejects
|
|
56
|
+
only the cases it can prove wrong at parse time (e.g. a missing value); a
|
|
57
|
+
config file or env var carrying ``"abc"`` for an ``int`` field merges
|
|
58
|
+
cleanly here and fails later, in ``build()``.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
target: The dataclass type (or scalar type) used to guide CLI parsing.
|
|
62
|
+
argv: CLI arguments to parse. Defaults to sys.argv[1:].
|
|
63
|
+
env: Environment variable mapping to scan. Defaults to os.environ.
|
|
64
|
+
env_prefix: Prefix that env vars must start with. Defaults to ``None``,
|
|
65
|
+
which disables environment variable parsing entirely. Set to ``""``
|
|
66
|
+
to read all env vars without filtering, or to e.g. ``"MYAPP_"`` to
|
|
67
|
+
read only vars with that prefix.
|
|
68
|
+
env_separator: Separator used to split env var names into nested keys.
|
|
69
|
+
Defaults to ``"__"`` (double underscore).
|
|
70
|
+
cli_prefix: Required prefix for all CLI flags. Defaults to ``""``,
|
|
71
|
+
which means no prefix is required.
|
|
72
|
+
config_flag: Flag name used to specify config files on the CLI
|
|
73
|
+
(``--config path/to/file.yaml``). Set to ``""`` to disable.
|
|
74
|
+
Defaults to ``"config"``.
|
|
75
|
+
files: Paths to config files to load.
|
|
76
|
+
env_config: Name of an env var whose value is a config file path to load.
|
|
77
|
+
Loaded after ``files`` but before CLI ``--config`` files.
|
|
78
|
+
union_tag: Field name used as a discriminator tag in union types.
|
|
79
|
+
Defaults to ``"class"`` — a Python keyword that can never clash
|
|
80
|
+
with a dataclass field name.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
A plain dict of the merged configuration, with expression strings intact.
|
|
84
|
+
|
|
85
|
+
Config file loading order:
|
|
86
|
+
All config files share the same priority level (below inline env vars and
|
|
87
|
+
CLI args). Within that level they are loaded left-to-right so that later
|
|
88
|
+
sources win on conflict. The full sequence is:
|
|
89
|
+
|
|
90
|
+
1. ``files`` — in the order given.
|
|
91
|
+
2. ``env_config`` — the single global path named by that env var (if set).
|
|
92
|
+
3. ``CONFIG__*`` env vars — sorted lexicographically by their env var name,
|
|
93
|
+
which is equivalent to sorting by subpath depth (shallower paths first).
|
|
94
|
+
A global ``CONFIG=file`` therefore loads before ``CONFIG__DB=db.yaml``,
|
|
95
|
+
which loads before ``CONFIG__DB__HOST=host.yaml``.
|
|
96
|
+
4. CLI ``--config`` / ``--config.subpath`` flags — in left-to-right order.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
InvalidConfigFileError: If a config file cannot be loaded.
|
|
100
|
+
UnknownArgumentError: If an unrecognized CLI argument is encountered.
|
|
101
|
+
"""
|
|
102
|
+
if argv is None:
|
|
103
|
+
argv = sys.argv[1:]
|
|
104
|
+
if env is None:
|
|
105
|
+
env = os.environ
|
|
106
|
+
|
|
107
|
+
cli_data, cli_configs = _parse_cli(argv, target, cli_prefix, config_flag, union_tag)
|
|
108
|
+
return _merge_sources(
|
|
109
|
+
target,
|
|
110
|
+
cli_data,
|
|
111
|
+
cli_configs,
|
|
112
|
+
env=env,
|
|
113
|
+
env_prefix=env_prefix,
|
|
114
|
+
env_separator=env_separator,
|
|
115
|
+
config_flag=config_flag,
|
|
116
|
+
files=files,
|
|
117
|
+
env_config=env_config,
|
|
118
|
+
union_tag=union_tag,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@overload
|
|
123
|
+
def build[T](target: type[T], data: dict[str, Any], *, union_tag: str = ...) -> T: ...
|
|
124
|
+
@overload
|
|
125
|
+
def build(target: object, data: dict[str, Any], *, union_tag: str = ...) -> Any: ...
|
|
126
|
+
def build[T](
|
|
127
|
+
target: type[T] | TypeAliasType | UnionType,
|
|
128
|
+
data: dict[str, Any],
|
|
129
|
+
*,
|
|
130
|
+
union_tag: str = _defaults.UNION_TAG,
|
|
131
|
+
) -> T:
|
|
132
|
+
"""Resolve ``${...}`` expressions and construct the target type from a merged config dict.
|
|
133
|
+
|
|
134
|
+
Use this as the second step after ``merge()``, or to load configuration
|
|
135
|
+
from a dict you have assembled yourself.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
target: The dataclass type (or scalar type) to construct.
|
|
139
|
+
data: The raw config dict (e.g. the output of ``merge()``).
|
|
140
|
+
union_tag: The field name used as a discriminator tag in unions.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
An instance of the target type.
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
MissingFieldError: If a required field is not provided.
|
|
147
|
+
TypeCoercionError: If a value cannot be coerced to the target type.
|
|
148
|
+
AmbiguousUnionError: If a Union cannot be disambiguated.
|
|
149
|
+
CircularReferenceError: If expression references form a cycle.
|
|
150
|
+
UnsafeExpressionError: If an expression contains disallowed constructs.
|
|
151
|
+
MissingReferenceError: If an expression references a field that does not exist.
|
|
152
|
+
ExpressionEvalError: If an expression fails at runtime.
|
|
153
|
+
"""
|
|
154
|
+
target_r = _resolve_type(target)
|
|
155
|
+
is_dataclass = _is_struct_like(target_r)
|
|
156
|
+
|
|
157
|
+
resolved = resolve_expressions(data)
|
|
158
|
+
|
|
159
|
+
if not is_dataclass:
|
|
160
|
+
raw = resolved.get("__root__", _MISSING)
|
|
161
|
+
if raw is _MISSING:
|
|
162
|
+
msg = (
|
|
163
|
+
f"No value provided for target type {target_r!r}."
|
|
164
|
+
" Provide a value via CLI flag (--<prefix> <value>), environment variable, or config file."
|
|
165
|
+
)
|
|
166
|
+
raise MissingFieldError(msg)
|
|
167
|
+
return _tc(target_r, raw, union_tag=union_tag)
|
|
168
|
+
return _tc(target_r, resolved, union_tag=union_tag)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def resolve(data: dict[str, Any]) -> dict[str, Any]:
|
|
172
|
+
"""Resolve ``${...}`` expressions in a merged config dict.
|
|
173
|
+
|
|
174
|
+
Use this between ``merge()`` and ``from_dict()`` when you need the
|
|
175
|
+
resolved dict itself (e.g. to inspect values or write it to a file):
|
|
176
|
+
|
|
177
|
+
raw = confarg.merge(MyConfig, ...)
|
|
178
|
+
resolved = confarg.resolve(raw)
|
|
179
|
+
confarg.dump_file(resolved, "out.yaml")
|
|
180
|
+
cfg = confarg.from_dict(MyConfig, resolved)
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
data: A plain config dict, e.g. the output of ``merge()``.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
A new dict with all ``${...}`` expression strings replaced by their values.
|
|
187
|
+
|
|
188
|
+
Raises:
|
|
189
|
+
CircularReferenceError: If expression references form a cycle.
|
|
190
|
+
UnsafeExpressionError: If an expression contains disallowed constructs.
|
|
191
|
+
MissingReferenceError: If an expression references a field that does not exist.
|
|
192
|
+
ExpressionEvalError: If an expression fails at runtime.
|
|
193
|
+
"""
|
|
194
|
+
return resolve_expressions(data)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@overload
|
|
198
|
+
def from_dict[T](target: type[T], data: dict[str, Any], *, union_tag: str = ...) -> T: ...
|
|
199
|
+
@overload
|
|
200
|
+
def from_dict(target: object, data: dict[str, Any], *, union_tag: str = ...) -> Any: ...
|
|
201
|
+
def from_dict[T](
|
|
202
|
+
target: type[T] | TypeAliasType | UnionType,
|
|
203
|
+
data: dict[str, Any],
|
|
204
|
+
*,
|
|
205
|
+
union_tag: str = _defaults.UNION_TAG,
|
|
206
|
+
) -> T:
|
|
207
|
+
"""Construct a typed object from an already-resolved config dict.
|
|
208
|
+
|
|
209
|
+
Unlike ``build()``, this does NOT resolve ``${...}`` expressions — call
|
|
210
|
+
``resolve()`` first if needed.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
target: The dataclass or plain-class type to construct.
|
|
214
|
+
data: A resolved config dict (output of resolve() or merge()).
|
|
215
|
+
union_tag: The field name used as a discriminator tag in unions.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
An instance of the target type.
|
|
219
|
+
|
|
220
|
+
Raises:
|
|
221
|
+
MissingFieldError: If a required field is not provided.
|
|
222
|
+
TypeCoercionError: If a value cannot be coerced to the target type.
|
|
223
|
+
AmbiguousUnionError: If a Union cannot be disambiguated.
|
|
224
|
+
"""
|
|
225
|
+
target_r = _resolve_type(target)
|
|
226
|
+
return _tc(target_r, data, union_tag=union_tag)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@overload
|
|
230
|
+
def load[T](
|
|
231
|
+
target: type[T],
|
|
232
|
+
*,
|
|
233
|
+
argv: Sequence[str] | None = ...,
|
|
234
|
+
env: Mapping[str, str] | None = ...,
|
|
235
|
+
env_prefix: str | None = ...,
|
|
236
|
+
env_separator: str = ...,
|
|
237
|
+
cli_prefix: str = ...,
|
|
238
|
+
config_flag: str = ...,
|
|
239
|
+
files: Sequence[str | Path] = ...,
|
|
240
|
+
env_config: str | None = ...,
|
|
241
|
+
union_tag: str = ...,
|
|
242
|
+
) -> T: ...
|
|
243
|
+
@overload
|
|
244
|
+
def load(
|
|
245
|
+
target: object,
|
|
246
|
+
*,
|
|
247
|
+
argv: Sequence[str] | None = ...,
|
|
248
|
+
env: Mapping[str, str] | None = ...,
|
|
249
|
+
env_prefix: str | None = ...,
|
|
250
|
+
env_separator: str = ...,
|
|
251
|
+
cli_prefix: str = ...,
|
|
252
|
+
config_flag: str = ...,
|
|
253
|
+
files: Sequence[str | Path] = ...,
|
|
254
|
+
env_config: str | None = ...,
|
|
255
|
+
union_tag: str = ...,
|
|
256
|
+
) -> Any: ...
|
|
257
|
+
def load[T]( # noqa: PLR0913
|
|
258
|
+
target: type[T] | TypeAliasType | UnionType,
|
|
259
|
+
*,
|
|
260
|
+
argv: Sequence[str] | None = None,
|
|
261
|
+
env: Mapping[str, str] | None = None,
|
|
262
|
+
env_prefix: str | None = _defaults.ENV_PREFIX,
|
|
263
|
+
env_separator: str = _defaults.ENV_SEPARATOR,
|
|
264
|
+
cli_prefix: str = "",
|
|
265
|
+
config_flag: str = _defaults.CONFIG_FLAG,
|
|
266
|
+
files: Sequence[str | Path] = (),
|
|
267
|
+
env_config: str | None = None,
|
|
268
|
+
union_tag: str = _defaults.UNION_TAG,
|
|
269
|
+
) -> T:
|
|
270
|
+
"""Merge configuration from all sources and construct the target type.
|
|
271
|
+
|
|
272
|
+
Convenience wrapper for ``merge()`` + ``build()``. For more control —
|
|
273
|
+
e.g. to inspect or save the raw merged dict — call those directly.
|
|
274
|
+
See ``merge()`` for source priority and config file loading order.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
target: The dataclass type (or scalar type) to load configuration into.
|
|
278
|
+
argv: CLI arguments to parse. Defaults to sys.argv[1:].
|
|
279
|
+
env: Environment variable mapping to scan. Defaults to os.environ.
|
|
280
|
+
env_prefix: Prefix that env vars must start with. Defaults to ``None``,
|
|
281
|
+
which disables environment variable parsing entirely. Set to ``""``
|
|
282
|
+
to read all env vars without filtering, or to e.g. ``"MYAPP_"`` to
|
|
283
|
+
read only vars with that prefix.
|
|
284
|
+
env_separator: Separator used to split env var names into nested keys.
|
|
285
|
+
Defaults to ``"__"`` (double underscore).
|
|
286
|
+
cli_prefix: Required prefix for all CLI flags. Defaults to ``""``,
|
|
287
|
+
which means no prefix is required.
|
|
288
|
+
config_flag: Flag name used to specify config files on the CLI
|
|
289
|
+
(``--config path/to/file.yaml``). Set to ``""`` to disable.
|
|
290
|
+
Defaults to ``"config"``.
|
|
291
|
+
files: Paths to config files to load.
|
|
292
|
+
env_config: Name of an env var whose value is a config file path to load.
|
|
293
|
+
Loaded after ``files`` but before CLI ``--config`` files.
|
|
294
|
+
union_tag: Field name used as a discriminator tag in union types.
|
|
295
|
+
Defaults to ``"class"`` — a Python keyword that can never clash
|
|
296
|
+
with a dataclass field name.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
An instance of the target type populated with the merged configuration.
|
|
300
|
+
|
|
301
|
+
Raises:
|
|
302
|
+
MissingFieldError: If a required field is not provided by any source.
|
|
303
|
+
TypeCoercionError: If a value cannot be coerced to the target type.
|
|
304
|
+
InvalidConfigFileError: If a config file cannot be loaded.
|
|
305
|
+
UnknownArgumentError: If an unrecognized CLI argument is encountered.
|
|
306
|
+
AmbiguousUnionError: If a Union cannot be disambiguated.
|
|
307
|
+
CircularReferenceError: If expression references form a cycle.
|
|
308
|
+
UnsafeExpressionError: If an expression contains disallowed constructs.
|
|
309
|
+
MissingReferenceError: If an expression references a field that does not exist.
|
|
310
|
+
ExpressionEvalError: If an expression fails at runtime.
|
|
311
|
+
"""
|
|
312
|
+
data = merge(
|
|
313
|
+
target,
|
|
314
|
+
argv=argv,
|
|
315
|
+
env=env,
|
|
316
|
+
env_prefix=env_prefix,
|
|
317
|
+
env_separator=env_separator,
|
|
318
|
+
cli_prefix=cli_prefix,
|
|
319
|
+
config_flag=config_flag,
|
|
320
|
+
files=files,
|
|
321
|
+
env_config=env_config,
|
|
322
|
+
union_tag=union_tag,
|
|
323
|
+
)
|
|
324
|
+
return build(target, data, union_tag=union_tag)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _strip_str_tokens(value: Any) -> Any:
|
|
328
|
+
"""Recursively convert _StrToken instances to plain str for serialization."""
|
|
329
|
+
if type(value) is _ST:
|
|
330
|
+
return str(value)
|
|
331
|
+
if isinstance(value, dict):
|
|
332
|
+
return {k: _strip_str_tokens(v) for k, v in value.items()}
|
|
333
|
+
if isinstance(value, list):
|
|
334
|
+
return [_strip_str_tokens(v) for v in value]
|
|
335
|
+
return value
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def dump(
|
|
339
|
+
value: Any,
|
|
340
|
+
*,
|
|
341
|
+
union_tag: str = _defaults.UNION_TAG,
|
|
342
|
+
tag_policy: TagPolicy = "auto",
|
|
343
|
+
) -> dict[str, Any]:
|
|
344
|
+
"""Serialize a dataclass instance to a config-compatible plain dict.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
value: A dataclass instance.
|
|
348
|
+
union_tag: The field name used as a discriminator tag in unions.
|
|
349
|
+
tag_policy: ``"auto"`` (tag only when needed) or ``"always"`` (tag every union member).
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
A plain dict representation.
|
|
353
|
+
|
|
354
|
+
Raises:
|
|
355
|
+
TypeError: If value is not a dataclass instance.
|
|
356
|
+
"""
|
|
357
|
+
if isinstance(value, dict):
|
|
358
|
+
msg = (
|
|
359
|
+
"dump() takes a dataclass instance, not a dict."
|
|
360
|
+
" To write a raw config dict to a file, use dump_file() directly."
|
|
361
|
+
)
|
|
362
|
+
raise TypeError(msg)
|
|
363
|
+
if isinstance(value, type) or not _is_dc(type(value)):
|
|
364
|
+
tp_name = type(value).__name__
|
|
365
|
+
if _is_struct(type(value)):
|
|
366
|
+
msg = (
|
|
367
|
+
f"dump() only supports dataclass instances, not plain classes.\n"
|
|
368
|
+
f"{tp_name} is a plain class — keep the merged dict and dump that instead:\n"
|
|
369
|
+
f" raw = confarg.merge(...)\n"
|
|
370
|
+
f" confarg.dump_file(raw, path)"
|
|
371
|
+
)
|
|
372
|
+
raise TypeError(msg)
|
|
373
|
+
msg = f"Expected a dataclass instance, got {tp_name}"
|
|
374
|
+
raise TypeError(msg)
|
|
375
|
+
tp = type(value)
|
|
376
|
+
return _serialize(tp, value, "", union_tag, tag_policy)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def dump_file(
|
|
380
|
+
value: Any,
|
|
381
|
+
path: str | Path,
|
|
382
|
+
*,
|
|
383
|
+
union_tag: str = _defaults.UNION_TAG,
|
|
384
|
+
tag_policy: TagPolicy = "auto",
|
|
385
|
+
) -> None:
|
|
386
|
+
"""Serialize and write to a config file.
|
|
387
|
+
|
|
388
|
+
Accepts a dataclass instance or a raw config dict (e.g. from ``merge()``
|
|
389
|
+
or ``resolve()``). The output format is determined by the file extension
|
|
390
|
+
(.toml, .yaml, .yml, .json).
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
value: A dataclass instance or a raw config dict.
|
|
394
|
+
path: Path to the output file.
|
|
395
|
+
union_tag: The field name used as a discriminator tag in unions.
|
|
396
|
+
tag_policy: ``"auto"`` or ``"always"``.
|
|
397
|
+
|
|
398
|
+
Raises:
|
|
399
|
+
TypeError: If value is not a dataclass instance or a dict.
|
|
400
|
+
InvalidConfigFileError: If the format is unsupported or the required library is not installed.
|
|
401
|
+
"""
|
|
402
|
+
if isinstance(value, dict):
|
|
403
|
+
_dump_file(_strip_str_tokens(value), Path(path))
|
|
404
|
+
else:
|
|
405
|
+
_dump_file(dump(value, union_tag=union_tag, tag_policy=tag_policy), Path(path))
|