confarg 0.0.1.dev3__tar.gz → 0.0.1.dev4__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 (30) hide show
  1. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/PKG-INFO +7 -52
  2. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/README.md +6 -51
  3. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/pyproject.toml +1 -1
  4. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/__init__.py +0 -7
  5. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_merge.py +16 -0
  6. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_types.py +39 -0
  7. confarg-0.0.1.dev4/src/confarg/cli/__init__.py +15 -0
  8. confarg-0.0.1.dev4/src/confarg/cli/argparse/__init__.py +34 -0
  9. confarg-0.0.1.dev4/src/confarg/cli/argparse/_build.py +642 -0
  10. {confarg-0.0.1.dev3/src/confarg → confarg-0.0.1.dev4/src/confarg/cli/argparse}/_completion.py +25 -21
  11. confarg-0.0.1.dev4/src/confarg/cli/argparse/_namespace.py +267 -0
  12. confarg-0.0.1.dev4/src/confarg/cli/argparse/_register.py +196 -0
  13. confarg-0.0.1.dev4/src/confarg/cli/argparse/_spec.py +149 -0
  14. confarg-0.0.1.dev4/src/confarg/cli/click/__init__.py +16 -0
  15. confarg-0.0.1.dev4/src/confarg/cli/click/_completion.py +79 -0
  16. confarg-0.0.1.dev4/src/confarg/cli/click/_context.py +139 -0
  17. confarg-0.0.1.dev4/src/confarg/cli/click/_register.py +162 -0
  18. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/typedload/_coerce.py +4 -0
  19. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/typedload/_construct.py +5 -9
  20. confarg-0.0.1.dev3/src/confarg/_argparse.py +0 -970
  21. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_callable.py +0 -0
  22. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_defaults.py +0 -0
  23. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_errors.py +0 -0
  24. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_files.py +0 -0
  25. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_parse_cli.py +0 -0
  26. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_parse_env.py +0 -0
  27. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_serialize.py +0 -0
  28. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/dictexpr/__init__.py +0 -0
  29. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/dictexpr/_expressions.py +0 -0
  30. {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/typedload/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: confarg
3
- Version: 0.0.1.dev3
3
+ Version: 0.0.1.dev4
4
4
  Summary: A tool to manage complex, dynamic configurations.
5
5
  Author: confarg
6
6
  Author-email: confarg <280620574+confarg@users.noreply.github.com>
@@ -11,27 +11,22 @@ Project-URL: Repository, https://github.com/confarg/confarg
11
11
  Provides-Extra: completion
12
12
  Description-Content-Type: text/markdown
13
13
 
14
- # A tool to manage complex, dynamic configurations
14
+ # A tool to manage complex configurations
15
15
 
16
+ > Load and resolve complex configurations from files, environment variables and command line arguments. Keep your favorite CLI library.
16
17
 
17
- ## What is `confarg`?
18
18
 
19
- `confarg` is a Python library that helps you load your app configuration in a modular fashion from multiple sources: one or more configuration files, environment variables, and command line arguments.
20
-
21
- It strives to have minimal footprint on your data and app, to make it easy to switch to it, or switch from it.
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.
22
20
 
23
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`.
24
22
 
25
23
  If none of this makes sense to you, read along.
26
24
 
27
-
28
- ## What is not `confarg`?
25
+ ## Keep your data structures and CLI
29
26
 
30
27
  `confarg` is deliberately not a framework, but just a tool.
31
28
 
32
- It does not own the interface with the command line, and it won't help you build a beautiful CLI. However, it can coexist with the one you might be already using.
33
-
34
- It doesn't require you to use custom data classes, or to use custom annotations.
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.
35
30
 
36
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.
37
32
 
@@ -41,7 +36,7 @@ The scope of `confarg` is limited to the deserialization and serialization of co
41
36
  pip install confarg
42
37
  ```
43
38
 
44
- `confarg` is a stand-alone library that comes with no required dependencies. Installing libraries such as `pyyaml` unlocks the support of additioanl configuration file formats.
39
+ Installing additional libraries such as `pyyaml` unlocks the support of extra configuration file formats.
45
40
 
46
41
  ## Getting started
47
42
 
@@ -411,46 +406,6 @@ db:
411
406
  __include__: ./db_config.yaml
412
407
  ```
413
408
 
414
- ## `confarg` and command-line interfaces
415
-
416
- Command line arguments are an essential part of `confarg`. We have seen how they are parsed and consumed implicitly by `confarg.load`.
417
-
418
- Although it is not needed for `confarg` to work, application generally provide a command line interface to offer some help and parse parameters.
419
-
420
- ### What to expect from a CLI regarding complex configurations
421
-
422
- We are used to the great user experience provided by CLI libraries such as `click`, `typer` or `cyclopts`. However, porting this great UX to complex configurations is no small feat because of their size and dynamic nature. Inline help is bound to be both very long, reflecting the configuration's complexity, and incomplete, as options coming from derived classes are not available. This could be frustrating.
423
-
424
- At the same time, the command line is not the main configuration interface: configuration files are. Building a great CLI UX for complex configuration has a somewhat poor benefit/effort ratio.
425
-
426
- ### Using a CLI library
427
-
428
- The python ecosystem offers many libraries to build powerful and beautiful CLI apps, such as `click`, `typer` or `cyclopts`. Those libraries parse and consume command line arguments, but they also offer a rich user experience by providing help on available commands, sometimes even auto-completion. Some like `cyclopts` also parse concrete nested dataclasses using the dot-separated field command line argument convention used by `confarg` and similar libraries.
429
-
430
- Should you use such a library, `confarg` can coexist with them by parsing unused arguments. Currently however, `confarg` will essentially work in "suppress" (`argparse` terminology) or "hidden" (`click` terminology) mode: the arguments won't show in the help generated by those libraries.
431
-
432
- ### Building your interface with `argparse`
433
-
434
- If you manage your interface yourself with `argparse`, `confarg` can step in and provide (limited) help for command line arguments. This is currently an experimental feature.
435
-
436
- Not registering `confarg` with your `ArgumentParser` and running in hidden mode is of course an option.
437
-
438
- ### Optional command line argument prefix
439
-
440
- When mixing `confarg` arguments with other application arguments, you may worry about name conflicts, or you may want to clearly identify which arguments belong to the configuration handled by `confarg`, especially if `confarg` is running in hidden arguments mode.
441
-
442
- For this purpose, you can specify a prefix for `confarg` command line arguments, using the `cli_prefix` parameter:
443
-
444
- ```python
445
- config = confarg.load(Config, args=rgs, cli_prefix="settings")
446
- ```
447
-
448
- The command line now cleanly conveys which arguments are routed to the configuration.
449
-
450
- ```python
451
- myapp.py --app_arg=hello --settings.config=config.yaml --settings.resources.cpu_count=2
452
- ```
453
-
454
409
  ## Next steps
455
410
 
456
411
  We have more than scratched the surface, and you should have enough knowledge to cover most of your needs.
@@ -1,24 +1,19 @@
1
- # A tool to manage complex, dynamic configurations
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
4
 
4
- ## What is `confarg`?
5
5
 
6
- `confarg` is a Python library that helps you load your app configuration in a modular fashion from multiple sources: one or more configuration files, environment variables, and command line arguments.
7
-
8
- It strives to have minimal footprint on your data and app, to make it easy to switch to it, or switch from it.
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.
9
7
 
10
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`.
11
9
 
12
10
  If none of this makes sense to you, read along.
13
11
 
14
-
15
- ## What is not `confarg`?
12
+ ## Keep your data structures and CLI
16
13
 
17
14
  `confarg` is deliberately not a framework, but just a tool.
18
15
 
19
- It does not own the interface with the command line, and it won't help you build a beautiful CLI. However, it can coexist with the one you might be already using.
20
-
21
- It doesn't require you to use custom data classes, or to use custom annotations.
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.
22
17
 
23
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.
24
19
 
@@ -28,7 +23,7 @@ The scope of `confarg` is limited to the deserialization and serialization of co
28
23
  pip install confarg
29
24
  ```
30
25
 
31
- `confarg` is a stand-alone library that comes with no required dependencies. Installing libraries such as `pyyaml` unlocks the support of additioanl configuration file formats.
26
+ Installing additional libraries such as `pyyaml` unlocks the support of extra configuration file formats.
32
27
 
33
28
  ## Getting started
34
29
 
@@ -398,46 +393,6 @@ db:
398
393
  __include__: ./db_config.yaml
399
394
  ```
400
395
 
401
- ## `confarg` and command-line interfaces
402
-
403
- Command line arguments are an essential part of `confarg`. We have seen how they are parsed and consumed implicitly by `confarg.load`.
404
-
405
- Although it is not needed for `confarg` to work, application generally provide a command line interface to offer some help and parse parameters.
406
-
407
- ### What to expect from a CLI regarding complex configurations
408
-
409
- We are used to the great user experience provided by CLI libraries such as `click`, `typer` or `cyclopts`. However, porting this great UX to complex configurations is no small feat because of their size and dynamic nature. Inline help is bound to be both very long, reflecting the configuration's complexity, and incomplete, as options coming from derived classes are not available. This could be frustrating.
410
-
411
- At the same time, the command line is not the main configuration interface: configuration files are. Building a great CLI UX for complex configuration has a somewhat poor benefit/effort ratio.
412
-
413
- ### Using a CLI library
414
-
415
- The python ecosystem offers many libraries to build powerful and beautiful CLI apps, such as `click`, `typer` or `cyclopts`. Those libraries parse and consume command line arguments, but they also offer a rich user experience by providing help on available commands, sometimes even auto-completion. Some like `cyclopts` also parse concrete nested dataclasses using the dot-separated field command line argument convention used by `confarg` and similar libraries.
416
-
417
- Should you use such a library, `confarg` can coexist with them by parsing unused arguments. Currently however, `confarg` will essentially work in "suppress" (`argparse` terminology) or "hidden" (`click` terminology) mode: the arguments won't show in the help generated by those libraries.
418
-
419
- ### Building your interface with `argparse`
420
-
421
- If you manage your interface yourself with `argparse`, `confarg` can step in and provide (limited) help for command line arguments. This is currently an experimental feature.
422
-
423
- Not registering `confarg` with your `ArgumentParser` and running in hidden mode is of course an option.
424
-
425
- ### Optional command line argument prefix
426
-
427
- When mixing `confarg` arguments with other application arguments, you may worry about name conflicts, or you may want to clearly identify which arguments belong to the configuration handled by `confarg`, especially if `confarg` is running in hidden arguments mode.
428
-
429
- For this purpose, you can specify a prefix for `confarg` command line arguments, using the `cli_prefix` parameter:
430
-
431
- ```python
432
- config = confarg.load(Config, args=rgs, cli_prefix="settings")
433
- ```
434
-
435
- The command line now cleanly conveys which arguments are routed to the configuration.
436
-
437
- ```python
438
- myapp.py --app_arg=hello --settings.config=config.yaml --settings.resources.cpu_count=2
439
- ```
440
-
441
396
  ## Next steps
442
397
 
443
398
  We have more than scratched the surface, and you should have enough knowledge to cover most of your needs.
@@ -7,7 +7,7 @@ description = "A tool to manage complex, dynamic configurations."
7
7
  name = "confarg"
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.12"
10
- version = "0.0.1.dev3"
10
+ version = "0.0.1.dev4"
11
11
 
12
12
  [project.urls]
13
13
  Documentation = "https://confarg.github.io/confarg"
@@ -15,8 +15,6 @@ if TYPE_CHECKING:
15
15
  from collections.abc import Mapping, Sequence
16
16
 
17
17
  from confarg import _defaults
18
- from confarg._argparse import FieldMeta, from_namespace, populate_parser
19
- from confarg._completion import setup_completion
20
18
  from confarg._errors import (
21
19
  AmbiguousUnionError,
22
20
  CircularReferenceError,
@@ -437,11 +435,6 @@ __all__ = [
437
435
  "dump_file",
438
436
  # Types
439
437
  "TagPolicy",
440
- # argparse helpers
441
- "populate_parser",
442
- "from_namespace",
443
- "FieldMeta",
444
- "setup_completion",
445
438
  # Errors / warnings
446
439
  "ConfargError",
447
440
  "ConfargWarning",
@@ -301,6 +301,22 @@ def _set_nested(d: dict[str, Any], path: list[str], value: Any) -> None:
301
301
  value: The value to set at the target path.
302
302
  """
303
303
  for part in path[:-1]:
304
+ # Negative index into an active append-spec: navigate directly into the
305
+ # appended item so multiple --field+ / --field.-1.sub sequences each
306
+ # patch their own newly-added item rather than colliding on the "-N" key.
307
+ if isinstance(d, dict) and LIST_APPEND_KEY in d:
308
+ try:
309
+ idx = int(part)
310
+ except ValueError:
311
+ pass
312
+ else:
313
+ if idx < 0:
314
+ items = d[LIST_APPEND_KEY]
315
+ if isinstance(items, list):
316
+ resolved = idx + len(items)
317
+ if 0 <= resolved < len(items) and isinstance(items[resolved], dict):
318
+ d = items[resolved]
319
+ continue
304
320
  if part not in d:
305
321
  d[part] = {}
306
322
  elif isinstance(d[part], list):
@@ -398,6 +398,45 @@ def _literal_values(tp: Any) -> tuple[Any, ...]:
398
398
  return get_args(_resolve_type(tp))
399
399
 
400
400
 
401
+ def _is_singleton_literal(tp: Any) -> bool:
402
+ """Check whether a type is a Literal with exactly one allowed value.
403
+
404
+ Args:
405
+ tp: The type to check.
406
+
407
+ Returns:
408
+ True if tp is Literal[X] for a single value X.
409
+ """
410
+ return _is_literal(tp) and len(_literal_values(tp)) == 1
411
+
412
+
413
+ def _is_final(tp: Any) -> bool:
414
+ """Check whether a type is a Final annotation.
415
+
416
+ Args:
417
+ tp: The type to check.
418
+
419
+ Returns:
420
+ True if tp is Final[X] for some X.
421
+ """
422
+ from typing import Final
423
+
424
+ return get_origin(tp) is Final
425
+
426
+
427
+ def _final_inner(tp: Any) -> Any:
428
+ """Return the inner type of a Final annotation.
429
+
430
+ Args:
431
+ tp: A Final[X] type.
432
+
433
+ Returns:
434
+ X, or Any if Final has no argument.
435
+ """
436
+ args = get_args(tp)
437
+ return args[0] if args else Any
438
+
439
+
401
440
  def _is_enum(tp: Any) -> bool:
402
441
  """Check whether a type is an Enum subclass.
403
442
 
@@ -0,0 +1,15 @@
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
+ """Framework-agnostic CLI building blocks for confarg."""
6
+
7
+ from confarg.cli.argparse._build import build_dynamic_flags, build_static_flags
8
+ from confarg.cli.argparse._spec import FieldMeta, FlagSpec
9
+
10
+ __all__ = [
11
+ "FieldMeta",
12
+ "FlagSpec",
13
+ "build_dynamic_flags",
14
+ "build_static_flags",
15
+ ]
@@ -0,0 +1,34 @@
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
+ """Argparse integration for confarg.
6
+
7
+ Public API
8
+ ----------
9
+ - :class:`FlagSpec` — framework-agnostic description of one CLI flag
10
+ - :class:`FieldMeta` — per-field metadata (help text, metavar)
11
+ - :func:`build_static_flags` — collect flag specs from a dataclass type
12
+ - :func:`build_dynamic_flags` — collect flag specs discoverable from partial argv
13
+ - :func:`load_flags_into_parser` — load specs into an :class:`argparse.ArgumentParser`
14
+ - :func:`populate_parser` — one-shot: build + load (+ optional dynamic extension)
15
+ - :func:`from_namespace` — construct a dataclass from a parsed :class:`argparse.Namespace`
16
+ - :func:`setup_completion` — enable tab-completion (requires ``argcomplete``)
17
+ """
18
+
19
+ from confarg.cli.argparse._build import build_dynamic_flags, build_static_flags
20
+ from confarg.cli.argparse._completion import setup_completion
21
+ from confarg.cli.argparse._namespace import from_namespace
22
+ from confarg.cli.argparse._register import load_flags_into_parser, populate_parser
23
+ from confarg.cli.argparse._spec import FieldMeta, FlagSpec
24
+
25
+ __all__ = [
26
+ "FieldMeta",
27
+ "FlagSpec",
28
+ "build_dynamic_flags",
29
+ "build_static_flags",
30
+ "from_namespace",
31
+ "load_flags_into_parser",
32
+ "populate_parser",
33
+ "setup_completion",
34
+ ]