confarg 0.0.1.dev4__tar.gz → 0.0.1.dev5__tar.gz

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.
Files changed (34) hide show
  1. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/PKG-INFO +47 -14
  2. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/README.md +43 -11
  3. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/pyproject.toml +4 -3
  4. confarg-0.0.1.dev5/src/confarg/__init__.py +33 -0
  5. confarg-0.0.1.dev4/src/confarg/__init__.py → confarg-0.0.1.dev5/src/confarg/_api.py +120 -135
  6. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/_callable.py +43 -76
  7. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/_defaults.py +11 -1
  8. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/_files.py +24 -10
  9. confarg-0.0.1.dev5/src/confarg/_import.py +40 -0
  10. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/_merge.py +30 -16
  11. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/_parse_cli.py +34 -21
  12. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/_parse_env.py +4 -4
  13. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/_serialize.py +2 -3
  14. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/_types.py +4 -37
  15. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/cli/__init__.py +1 -1
  16. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/cli/argparse/__init__.py +1 -1
  17. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/cli/argparse/_build.py +76 -65
  18. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/cli/argparse/_completion.py +129 -95
  19. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/cli/argparse/_namespace.py +42 -38
  20. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/cli/argparse/_register.py +4 -4
  21. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/cli/argparse/_spec.py +3 -5
  22. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/cli/click/__init__.py +7 -1
  23. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/cli/click/_completion.py +6 -3
  24. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/cli/click/_context.py +5 -16
  25. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/cli/click/_register.py +20 -35
  26. confarg-0.0.1.dev5/src/confarg/cli/cyclopts/__init__.py +20 -0
  27. confarg-0.0.1.dev5/src/confarg/cli/cyclopts/_context.py +137 -0
  28. confarg-0.0.1.dev5/src/confarg/cli/cyclopts/_register.py +173 -0
  29. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/dictexpr/__init__.py +3 -3
  30. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/dictexpr/_expressions.py +16 -15
  31. confarg-0.0.1.dev4/src/confarg/_errors.py → confarg-0.0.1.dev5/src/confarg/exceptions.py +8 -2
  32. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/typedload/__init__.py +2 -2
  33. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/typedload/_coerce.py +32 -7
  34. {confarg-0.0.1.dev4 → confarg-0.0.1.dev5}/src/confarg/typedload/_construct.py +40 -50
@@ -1,9 +1,10 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: confarg
3
- Version: 0.0.1.dev4
4
- Summary: A tool to manage complex, dynamic configurations.
3
+ Version: 0.0.1.dev5
4
+ Summary: Load and resolve complex configurations from files, environment variables and command line arguments. Keep your favorite CLI library.
5
5
  Author: confarg
6
6
  Author-email: confarg <280620574+confarg@users.noreply.github.com>
7
+ License-Expression: MPL-2.0
7
8
  Requires-Dist: argcomplete>=3.0 ; extra == 'completion'
8
9
  Requires-Python: >=3.12
9
10
  Project-URL: Documentation, https://confarg.github.io/confarg
@@ -13,30 +14,62 @@ Description-Content-Type: text/markdown
13
14
 
14
15
  # A tool to manage complex configurations
15
16
 
16
- > Load and resolve complex configurations from files, environment variables and command line arguments. Keep your favorite CLI library.
17
+ > Load and resolve complex configurations from files, environment variables and command line arguments. Keep your data structures and favorite CLI library.
17
18
 
19
+ `confarg` is a Python library that helps you load configurations in a modular fashion from multiple sources: files, environment variables, and command line arguments.
18
20
 
19
- `confarg` is a Python library that helps you load your app configuration in a modular fashion from multiple sources: configuration files, environment variables, and command line arguments.
21
+ It can handle deeply nested configurations, type unions, derived classes, expressions and variable interpolation, configuration compositions and more, and can integrate with your favorite argument parser library such as `argparse`, `click` or `typer`.
20
22
 
21
- It can handle deeply nested configurations, type unions, derived classes, expressions and variable interpolation, configuration compositions, and can coexist with your favorite argument parser library such as `argparse`, `click`, `typer` or `cyclopts`.
23
+ `confarg` is not a framework. No decorator, base class or special annotation type required: none are provided. It is just a tool for the deserialization and serialization of complex configurations. Its footprint in your code is typically a few lines of code, making it easy to switch to it, or away from it.
22
24
 
23
- If none of this makes sense to you, read along.
25
+ ## Install
24
26
 
25
- ## Keep your data structures and CLI
27
+ ```bash
28
+ pip install confarg
29
+ ```
26
30
 
27
- `confarg` is deliberately not a framework, but just a tool.
31
+ `confarg` comes with no dependency, but installing additional libraries such as `pyyaml` unlocks the support of extra configuration file formats.
28
32
 
29
- It does not offer custom data types or decorators, and does not own your CLI. Instead, it strives to play along with your own data structures and CLI framework, to make it easy to switch to it, or away from it.
33
+ ## TL;DR
30
34
 
31
- The scope of `confarg` is limited to the deserialization and serialization of complex configurations. By limiting itself to those transient moments in the lifetime of your application, the footprint of `confarg` in your app is limited to a few lines of code.
35
+ This library lets you read configurations stored in classes like this
32
36
 
33
- ## Install
37
+ ```python
38
+ @dataclass
39
+ class Config:
40
+ value: float
41
+ flag: bool
42
+ subconfig: SubConfig1 | SubConfig2
43
+ ```
44
+
45
+ with this kind of code
46
+
47
+ ```python
48
+ config = confarg.load(Config)
49
+ ```
50
+
51
+ which lets you build a configuration object from files,
52
+
53
+ ```yaml
54
+ value: 1.0
55
+ flag: false
56
+ subconfig:
57
+ foo: 42
58
+ ```
59
+
60
+ but also and simultaneously from environment variables,
61
+
62
+ ```properties
63
+ MYAPP_VALUE=0.0
64
+ ```
65
+
66
+ and command line arguments,
34
67
 
35
68
  ```bash
36
- pip install confarg
69
+ myapp --subconfig.foo=33
37
70
  ```
38
71
 
39
- Installing additional libraries such as `pyyaml` unlocks the support of extra configuration file formats.
72
+ Still here? Read along, we are just getting started.
40
73
 
41
74
  ## Getting started
42
75
 
@@ -1,29 +1,61 @@
1
1
  # A tool to manage complex configurations
2
2
 
3
- > Load and resolve complex configurations from files, environment variables and command line arguments. Keep your favorite CLI library.
3
+ > Load and resolve complex configurations from files, environment variables and command line arguments. Keep your data structures and favorite CLI library.
4
4
 
5
+ `confarg` is a Python library that helps you load configurations in a modular fashion from multiple sources: files, environment variables, and command line arguments.
5
6
 
6
- `confarg` is a Python library that helps you load your app configuration in a modular fashion from multiple sources: configuration files, environment variables, and command line arguments.
7
+ It can handle deeply nested configurations, type unions, derived classes, expressions and variable interpolation, configuration compositions and more, and can integrate with your favorite argument parser library such as `argparse`, `click` or `typer`.
7
8
 
8
- It can handle deeply nested configurations, type unions, derived classes, expressions and variable interpolation, configuration compositions, and can coexist with your favorite argument parser library such as `argparse`, `click`, `typer` or `cyclopts`.
9
+ `confarg` is not a framework. No decorator, base class or special annotation type required: none are provided. It is just a tool for the deserialization and serialization of complex configurations. Its footprint in your code is typically a few lines of code, making it easy to switch to it, or away from it.
9
10
 
10
- If none of this makes sense to you, read along.
11
+ ## Install
11
12
 
12
- ## Keep your data structures and CLI
13
+ ```bash
14
+ pip install confarg
15
+ ```
13
16
 
14
- `confarg` is deliberately not a framework, but just a tool.
17
+ `confarg` comes with no dependency, but installing additional libraries such as `pyyaml` unlocks the support of extra configuration file formats.
15
18
 
16
- It does not offer custom data types or decorators, and does not own your CLI. Instead, it strives to play along with your own data structures and CLI framework, to make it easy to switch to it, or away from it.
19
+ ## TL;DR
17
20
 
18
- The scope of `confarg` is limited to the deserialization and serialization of complex configurations. By limiting itself to those transient moments in the lifetime of your application, the footprint of `confarg` in your app is limited to a few lines of code.
21
+ This library lets you read configurations stored in classes like this
19
22
 
20
- ## Install
23
+ ```python
24
+ @dataclass
25
+ class Config:
26
+ value: float
27
+ flag: bool
28
+ subconfig: SubConfig1 | SubConfig2
29
+ ```
30
+
31
+ with this kind of code
32
+
33
+ ```python
34
+ config = confarg.load(Config)
35
+ ```
36
+
37
+ which lets you build a configuration object from files,
38
+
39
+ ```yaml
40
+ value: 1.0
41
+ flag: false
42
+ subconfig:
43
+ foo: 42
44
+ ```
45
+
46
+ but also and simultaneously from environment variables,
47
+
48
+ ```properties
49
+ MYAPP_VALUE=0.0
50
+ ```
51
+
52
+ and command line arguments,
21
53
 
22
54
  ```bash
23
- pip install confarg
55
+ myapp --subconfig.foo=33
24
56
  ```
25
57
 
26
- Installing additional libraries such as `pyyaml` unlocks the support of extra configuration file formats.
58
+ Still here? Read along, we are just getting started.
27
59
 
28
60
  ## Getting started
29
61
 
@@ -2,12 +2,12 @@
2
2
  authors = [
3
3
  { name = "confarg", email = "280620574+confarg@users.noreply.github.com" },
4
4
  ]
5
- dependencies = []
6
- description = "A tool to manage complex, dynamic configurations."
5
+ description = "Load and resolve complex configurations from files, environment variables and command line arguments. Keep your favorite CLI library."
6
+ license = "MPL-2.0"
7
7
  name = "confarg"
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.12"
10
- version = "0.0.1.dev4"
10
+ version = "0.0.1.dev5"
11
11
 
12
12
  [project.urls]
13
13
  Documentation = "https://confarg.github.io/confarg"
@@ -31,6 +31,7 @@ dev = [
31
31
  "pytest>=8.0",
32
32
  "pyyaml>=6.0",
33
33
  "tomli-w>=1.0",
34
+ "ty>=0.0.39",
34
35
  "typer>=0.25.1",
35
36
  "zensical>=0.0.41",
36
37
  ]
@@ -0,0 +1,33 @@
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 confarg import exceptions
14
+ from confarg._api import build, dump, dump_file, from_dict, load, merge, resolve
15
+ from confarg._types import TagPolicy
16
+
17
+ __all__ = [ # noqa: RUF022
18
+ # Two-step API
19
+ "merge",
20
+ "build",
21
+ # Three-step API (dict-centric)
22
+ "resolve",
23
+ "from_dict",
24
+ # One-step convenience
25
+ "load",
26
+ # Dump
27
+ "dump",
28
+ "dump_file",
29
+ # Types
30
+ "TagPolicy",
31
+ # Exceptions / warnings
32
+ "exceptions",
33
+ ]
@@ -2,40 +2,31 @@
2
2
  # License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
 
5
- """confarg read configuration from CLI args, env vars, and config files into dataclasses."""
5
+ """Public API implementation."""
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
9
  import os
10
10
  import sys
11
11
  from pathlib import Path
12
- from typing import TYPE_CHECKING, Any
12
+ from typing import TYPE_CHECKING, Any, overload
13
13
 
14
14
  if TYPE_CHECKING:
15
15
  from collections.abc import Mapping, Sequence
16
+ from types import UnionType
17
+ from typing import TypeAliasType
16
18
 
17
19
  from confarg import _defaults
18
- from confarg._errors import (
19
- AmbiguousUnionError,
20
- CircularReferenceError,
21
- ConfargError,
22
- ConfargWarning,
23
- ExpressionEvalError,
24
- InvalidConfigFileError,
25
- MissingFieldError,
26
- MissingReferenceError,
27
- SymbolImportError,
28
- TypeCoercionError,
29
- UnknownArgumentError,
30
- UnsafeExpressionError,
31
- )
32
- from confarg._files import INCLUDE_KEY, _dump_file, _load_file, _load_file_item
33
- from confarg._merge import LIST_APPEND_KEY, _deep_merge
20
+ from confarg._files import _dump_file, _load_file, _load_file_item, _load_subpath_files
21
+ from confarg._merge import LIST_APPEND_KEY as _LIST_APPEND_KEY
22
+ from confarg._merge import _deep_merge
34
23
  from confarg._parse_cli import _parse_cli
35
24
  from confarg._parse_env import _parse_env
36
25
  from confarg._serialize import _serialize
37
26
  from confarg._types import _MISSING, TagPolicy, _is_dc, _is_struct, _is_struct_like, _resolve_type
27
+ from confarg._types import _StrToken as _ST
38
28
  from confarg.dictexpr import resolve_expressions
29
+ from confarg.exceptions import ConfargError, MissingFieldError
39
30
  from confarg.typedload import construct as _tc
40
31
 
41
32
 
@@ -54,7 +45,7 @@ def _load_cli_config(fpath: Path, subpath: str, config_flag: str) -> dict[str, A
54
45
  append_items = fitem[last_key]
55
46
  else:
56
47
  append_items = [fitem]
57
- fdata: dict[str, Any] = {LIST_APPEND_KEY: append_items}
48
+ fdata: dict[str, Any] = {_LIST_APPEND_KEY: append_items}
58
49
  for part in reversed(real_subpath.split(".")):
59
50
  fdata = {part: fdata}
60
51
  return fdata
@@ -67,7 +58,7 @@ def _load_cli_config(fpath: Path, subpath: str, config_flag: str) -> dict[str, A
67
58
 
68
59
 
69
60
  def merge( # noqa: PLR0913
70
- target: type,
61
+ target: type | TypeAliasType | UnionType,
71
62
  *,
72
63
  args: Sequence[str] | None = None,
73
64
  env: Mapping[str, str] | None = None,
@@ -77,7 +68,7 @@ def merge( # noqa: PLR0913
77
68
  config_flag: str = "config",
78
69
  files: Sequence[str | Path] = (),
79
70
  env_config: str | None = None,
80
- union_tag: str = "class",
71
+ union_tag: str = _defaults.UNION_TAG,
81
72
  ) -> dict[str, Any]:
82
73
  """Collect and merge configuration from all sources into a raw dict.
83
74
 
@@ -96,12 +87,18 @@ def merge( # noqa: PLR0913
96
87
  to read all env vars without filtering, or to e.g. ``"MYAPP_"`` to
97
88
  read only vars with that prefix.
98
89
  env_separator: Separator used to split env var names into nested keys.
99
- cli_prefix: Required prefix for CLI flags.
100
- config_flag: The flag name used to specify config files on the CLI.
90
+ Defaults to ``"__"`` (double underscore).
91
+ cli_prefix: Required prefix for all CLI flags. Defaults to ``""``,
92
+ which means no prefix is required.
93
+ config_flag: Flag name used to specify config files on the CLI
94
+ (``--config path/to/file.yaml``). Set to ``""`` to disable.
95
+ Defaults to ``"config"``.
101
96
  files: Paths to config files to load.
102
97
  env_config: Name of an env var whose value is a config file path to load.
103
98
  Loaded after ``files`` but before CLI ``--config`` files.
104
- union_tag: The field name used as a discriminator tag in unions.
99
+ union_tag: Field name used as a discriminator tag in union types.
100
+ Defaults to ``"class"`` — a Python keyword that can never clash
101
+ with a dataclass field name.
105
102
 
106
103
  Returns:
107
104
  A plain dict of the merged configuration, with expression strings intact.
@@ -141,20 +138,11 @@ def merge( # noqa: PLR0913
141
138
  env_data, env_configs = _parse_env(env_for_fields, env_prefix, env_separator, target, config_flag)
142
139
 
143
140
  # 3. Load config files in priority order (all become config-level, below inline env/CLI)
144
- config_data: dict[str, Any] = {}
145
- for f in files:
146
- config_data = _deep_merge(config_data, _load_file(Path(f)), union_tag=union_tag)
147
- if env_config is not None:
148
- env_config_path = env.get(env_config)
149
- if env_config_path:
150
- config_data = _deep_merge(config_data, _load_file(Path(env_config_path)), union_tag=union_tag)
141
+ file_entries: list[tuple[str, Path]] = [("", Path(f)) for f in files]
142
+ if env_config and (env_config_path := env.get(env_config)):
143
+ file_entries.append(("", Path(env_config_path)))
151
144
  env_configs.sort(key=lambda ec: ec[0])
152
- for subpath, fpath in env_configs:
153
- fdata: dict[str, Any] = _load_file(fpath)
154
- if subpath:
155
- for part in reversed(subpath.split(".")):
156
- fdata = {part: fdata}
157
- config_data = _deep_merge(config_data, fdata, union_tag=union_tag)
145
+ config_data = _load_subpath_files(file_entries + env_configs, union_tag)
158
146
  for subpath, fpath in cli_configs:
159
147
  fdata = _load_cli_config(fpath, subpath, config_flag)
160
148
  config_data = _deep_merge(config_data, fdata, union_tag=union_tag)
@@ -164,21 +152,24 @@ def merge( # noqa: PLR0913
164
152
  return _deep_merge(merged, cli_data, union_tag=union_tag)
165
153
 
166
154
 
167
- def from_dict[T](
168
- target: type[T],
155
+ @overload
156
+ def build[T](target: type[T], data: dict[str, Any], *, union_tag: str = ...) -> T: ...
157
+ @overload
158
+ def build(target: object, data: dict[str, Any], *, union_tag: str = ...) -> Any: ...
159
+ def build[T](
160
+ target: type[T] | TypeAliasType | UnionType,
169
161
  data: dict[str, Any],
170
162
  *,
171
163
  union_tag: str = "class",
172
164
  ) -> T:
173
- """Construct a dataclass instance from a plain config dict.
165
+ """Resolve ``${...}`` expressions and construct the target type from a merged config dict.
174
166
 
175
- Resolves ${...} expressions then constructs the target type. Use this as
176
- the second step after merge(), or to load configuration from a dict you
177
- have assembled yourself.
167
+ Use this as the second step after ``merge()``, or to load configuration
168
+ from a dict you have assembled yourself.
178
169
 
179
170
  Args:
180
171
  target: The dataclass type (or scalar type) to construct.
181
- data: The raw config dict (e.g. the output of merge()).
172
+ data: The raw config dict (e.g. the output of ``merge()``).
182
173
  union_tag: The field name used as a discriminator tag in unions.
183
174
 
184
175
  Returns:
@@ -206,22 +197,26 @@ def from_dict[T](
206
197
  " Provide a value via CLI flag (--<prefix> <value>), environment variable, or config file."
207
198
  )
208
199
  raise MissingFieldError(msg)
209
- return _tc(target_r, raw, union_tag=union_tag) # type: ignore[return-value]
200
+ return _tc(target_r, raw, union_tag=union_tag)
201
+ return _tc(target_r, resolved, union_tag=union_tag)
210
202
 
211
- return _tc(target_r, resolved, union_tag=union_tag) # type: ignore[return-value]
212
203
 
204
+ def resolve(data: dict[str, Any]) -> dict[str, Any]:
205
+ """Resolve ``${...}`` expressions in a merged config dict.
213
206
 
214
- def interpolate(data: dict[str, Any]) -> dict[str, Any]:
215
- """Resolve ${...} expressions in a merged config dict.
207
+ Use this between ``merge()`` and ``from_dict()`` when you need the
208
+ resolved dict itself (e.g. to inspect values or write it to a file):
216
209
 
217
- This is the first half of from_dict(). Call it to get the fully-resolved
218
- dict before passing it to construct() or inspecting values.
210
+ raw = confarg.merge(MyConfig, ...)
211
+ resolved = confarg.resolve(raw)
212
+ confarg.dump_file(resolved, "out.yaml")
213
+ cfg = confarg.from_dict(MyConfig, resolved)
219
214
 
220
215
  Args:
221
- data: A plain config dict, e.g. the output of merge().
216
+ data: A plain config dict, e.g. the output of ``merge()``.
222
217
 
223
218
  Returns:
224
- A new dict with all ${...} expression strings replaced by their values.
219
+ A new dict with all ``${...}`` expression strings replaced by their values.
225
220
 
226
221
  Raises:
227
222
  CircularReferenceError: If expression references form a cycle.
@@ -232,28 +227,24 @@ def interpolate(data: dict[str, Any]) -> dict[str, Any]:
232
227
  return resolve_expressions(data)
233
228
 
234
229
 
235
- def construct[T](
236
- target: type[T],
230
+ @overload
231
+ def from_dict[T](target: type[T], data: dict[str, Any], *, union_tag: str = ...) -> T: ...
232
+ @overload
233
+ def from_dict(target: object, data: dict[str, Any], *, union_tag: str = ...) -> Any: ...
234
+ def from_dict[T](
235
+ target: type[T] | TypeAliasType | UnionType,
237
236
  data: dict[str, Any],
238
237
  *,
239
238
  union_tag: str = "class",
240
239
  ) -> T:
241
- """Construct a typed object from an already-interpolated config dict.
240
+ """Construct a typed object from an already-resolved config dict.
242
241
 
243
- This is the second half of from_dict(). Unlike from_dict(), it does NOT
244
- resolve ${...} expressions — call interpolate() first if needed.
245
-
246
- Use this together with interpolate() when you want to keep the interpolated
247
- dict around (e.g. to dump it with dump_file()):
248
-
249
- raw = confarg.merge(MyConfig, ...)
250
- resolved = confarg.interpolate(raw)
251
- confarg.dump_file(resolved, "out.yaml") # serialize the dict
252
- cfg = confarg.construct(MyConfig, resolved) # build the typed object
242
+ Unlike ``build()``, this does NOT resolve ``${...}`` expressions call
243
+ ``resolve()`` first if needed.
253
244
 
254
245
  Args:
255
246
  target: The dataclass or plain-class type to construct.
256
- data: An interpolated config dict (output of interpolate() or merge()).
247
+ data: A resolved config dict (output of resolve() or merge()).
257
248
  union_tag: The field name used as a discriminator tag in unions.
258
249
 
259
250
  Returns:
@@ -265,12 +256,40 @@ def construct[T](
265
256
  AmbiguousUnionError: If a Union cannot be disambiguated.
266
257
  """
267
258
  target_r = _resolve_type(target)
268
- return _tc(target_r, data, union_tag=union_tag) # type: ignore[return-value]
259
+ return _tc(target_r, data, union_tag=union_tag)
269
260
 
270
261
 
271
- def load[T]( # noqa: PLR0913
262
+ @overload
263
+ def load[T](
272
264
  target: type[T],
273
265
  *,
266
+ args: Sequence[str] | None = ...,
267
+ env: Mapping[str, str] | None = ...,
268
+ env_prefix: str | None = ...,
269
+ env_separator: str = ...,
270
+ cli_prefix: str = ...,
271
+ config_flag: str = ...,
272
+ files: Sequence[str | Path] = ...,
273
+ env_config: str | None = ...,
274
+ union_tag: str = ...,
275
+ ) -> T: ...
276
+ @overload
277
+ def load(
278
+ target: object,
279
+ *,
280
+ args: Sequence[str] | None = ...,
281
+ env: Mapping[str, str] | None = ...,
282
+ env_prefix: str | None = ...,
283
+ env_separator: str = ...,
284
+ cli_prefix: str = ...,
285
+ config_flag: str = ...,
286
+ files: Sequence[str | Path] = ...,
287
+ env_config: str | None = ...,
288
+ union_tag: str = ...,
289
+ ) -> Any: ...
290
+ def load[T]( # noqa: PLR0913
291
+ target: type[T] | TypeAliasType | UnionType,
292
+ *,
274
293
  args: Sequence[str] | None = None,
275
294
  env: Mapping[str, str] | None = None,
276
295
  env_prefix: str | None = _defaults.ENV_PREFIX,
@@ -279,16 +298,13 @@ def load[T]( # noqa: PLR0913
279
298
  config_flag: str = "config",
280
299
  files: Sequence[str | Path] = (),
281
300
  env_config: str | None = None,
282
- union_tag: str = "class",
301
+ union_tag: str = _defaults.UNION_TAG,
283
302
  ) -> T:
284
- """Load configuration into the target type from CLI args, env vars, and config files.
285
-
286
- Sources are merged in priority order: config files (lowest), then
287
- environment variables, then CLI arguments (highest).
303
+ """Merge configuration from all sources and construct the target type.
288
304
 
289
- This is a convenience wrapper around merge() + from_dict(). For more
290
- control — e.g. to inspect or save the raw merged dict before construction
291
- call those two functions directly.
305
+ Convenience wrapper for ``merge()`` + ``build()``. For more control —
306
+ e.g. to inspect or save the raw merged dict call those directly.
307
+ See ``merge()`` for source priority and config file loading order.
292
308
 
293
309
  Args:
294
310
  target: The dataclass type (or scalar type) to load configuration into.
@@ -299,19 +315,22 @@ def load[T]( # noqa: PLR0913
299
315
  to read all env vars without filtering, or to e.g. ``"MYAPP_"`` to
300
316
  read only vars with that prefix.
301
317
  env_separator: Separator used to split env var names into nested keys.
302
- cli_prefix: Required prefix for CLI flags.
303
- config_flag: The flag name used to specify config files on the CLI.
318
+ Defaults to ``"__"`` (double underscore).
319
+ cli_prefix: Required prefix for all CLI flags. Defaults to ``""``,
320
+ which means no prefix is required.
321
+ config_flag: Flag name used to specify config files on the CLI
322
+ (``--config path/to/file.yaml``). Set to ``""`` to disable.
323
+ Defaults to ``"config"``.
304
324
  files: Paths to config files to load.
305
325
  env_config: Name of an env var whose value is a config file path to load.
306
326
  Loaded after ``files`` but before CLI ``--config`` files.
307
- union_tag: The field name used as a discriminator tag in unions.
327
+ union_tag: Field name used as a discriminator tag in union types.
328
+ Defaults to ``"class"`` — a Python keyword that can never clash
329
+ with a dataclass field name.
308
330
 
309
331
  Returns:
310
332
  An instance of the target type populated with the merged configuration.
311
333
 
312
- Config file loading order:
313
- See ``merge()`` for the full description of config file loading order.
314
-
315
334
  Raises:
316
335
  MissingFieldError: If a required field is not provided by any source.
317
336
  TypeCoercionError: If a value cannot be coerced to the target type.
@@ -335,13 +354,11 @@ def load[T]( # noqa: PLR0913
335
354
  env_config=env_config,
336
355
  union_tag=union_tag,
337
356
  )
338
- return from_dict(target, data, union_tag=union_tag)
357
+ return build(target, data, union_tag=union_tag)
339
358
 
340
359
 
341
360
  def _strip_str_tokens(value: Any) -> Any:
342
361
  """Recursively convert _StrToken instances to plain str for serialization."""
343
- from confarg._types import _StrToken as _ST
344
-
345
362
  if type(value) is _ST:
346
363
  return str(value)
347
364
  if isinstance(value, dict):
@@ -357,28 +374,25 @@ def dump(
357
374
  union_tag: str = "class",
358
375
  tag_policy: TagPolicy = "auto",
359
376
  ) -> dict[str, Any]:
360
- """Serialize to a plain dict.
361
-
362
- Dispatches on the value type:
363
-
364
- - **Dataclass instance**: serializes to a config-compatible dict.
365
- ``union_tag`` and ``tag_policy`` apply.
366
- - **Raw dict** (e.g. from ``merge()``): normalizes internal tokens to plain
367
- ``str``. ``union_tag`` and ``tag_policy`` are ignored.
377
+ """Serialize a dataclass instance to a config-compatible plain dict.
368
378
 
369
379
  Args:
370
- value: A dataclass instance or a raw config dict.
380
+ value: A dataclass instance.
371
381
  union_tag: The field name used as a discriminator tag in unions.
372
- tag_policy: "auto" (tag only when needed) or "always" (tag every union DC).
382
+ tag_policy: ``"auto"`` (tag only when needed) or ``"always"`` (tag every union member).
373
383
 
374
384
  Returns:
375
385
  A plain dict representation.
376
386
 
377
387
  Raises:
378
- TypeError: If value is not a dataclass instance or a dict.
388
+ TypeError: If value is not a dataclass instance.
379
389
  """
380
390
  if isinstance(value, dict):
381
- return _strip_str_tokens(value)
391
+ msg = (
392
+ "dump() takes a dataclass instance, not a dict."
393
+ " To write a raw config dict to a file, use dump_file() directly."
394
+ )
395
+ raise TypeError(msg)
382
396
  if isinstance(value, type) or not _is_dc(type(value)):
383
397
  tp_name = type(value).__name__
384
398
  if _is_struct(type(value)):
@@ -389,7 +403,7 @@ def dump(
389
403
  f" confarg.dump_file(raw, path)"
390
404
  )
391
405
  raise TypeError(msg)
392
- msg = f"Expected a dataclass instance or dict, got {tp_name}"
406
+ msg = f"Expected a dataclass instance, got {tp_name}"
393
407
  raise TypeError(msg)
394
408
  tp = type(value)
395
409
  return _serialize(tp, value, "", union_tag, tag_policy)
@@ -402,52 +416,23 @@ def dump_file(
402
416
  union_tag: str = "class",
403
417
  tag_policy: TagPolicy = "auto",
404
418
  ) -> None:
405
- """Write to a config file.
419
+ """Serialize and write to a config file.
406
420
 
407
- Accepts dataclass instances or raw config dicts see ``dump()`` for
408
- dispatch behaviour. The output format is determined by the file extension
421
+ Accepts a dataclass instance or a raw config dict (e.g. from ``merge()``
422
+ or ``resolve()``). The output format is determined by the file extension
409
423
  (.toml, .yaml, .yml, .json).
410
424
 
411
425
  Args:
412
426
  value: A dataclass instance or a raw config dict.
413
427
  path: Path to the output file.
414
428
  union_tag: The field name used as a discriminator tag in unions.
415
- tag_policy: "auto" or "always".
429
+ tag_policy: ``"auto"`` or ``"always"``.
416
430
 
417
431
  Raises:
418
432
  TypeError: If value is not a dataclass instance or a dict.
419
433
  InvalidConfigFileError: If the format is unsupported or the required library is not installed.
420
434
  """
421
- _dump_file(dump(value, union_tag=union_tag, tag_policy=tag_policy), Path(path))
422
-
423
-
424
- __all__ = [
425
- # Two-step API
426
- "merge",
427
- "from_dict",
428
- # Three-step API (dict-centric)
429
- "interpolate",
430
- "construct",
431
- # One-step convenience
432
- "load",
433
- # Dump
434
- "dump",
435
- "dump_file",
436
- # Types
437
- "TagPolicy",
438
- # Errors / warnings
439
- "ConfargError",
440
- "ConfargWarning",
441
- "MissingFieldError",
442
- "SymbolImportError",
443
- "TypeCoercionError",
444
- "InvalidConfigFileError",
445
- "UnknownArgumentError",
446
- "AmbiguousUnionError",
447
- "CircularReferenceError",
448
- "MissingReferenceError",
449
- "UnsafeExpressionError",
450
- "ExpressionEvalError",
451
- # Constants
452
- "INCLUDE_KEY",
453
- ]
435
+ if isinstance(value, dict):
436
+ _dump_file(_strip_str_tokens(value), Path(path))
437
+ else:
438
+ _dump_file(dump(value, union_tag=union_tag, tag_policy=tag_policy), Path(path))