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.
- {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/PKG-INFO +7 -52
- {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/README.md +6 -51
- {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/pyproject.toml +1 -1
- {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/__init__.py +0 -7
- {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_merge.py +16 -0
- {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_types.py +39 -0
- confarg-0.0.1.dev4/src/confarg/cli/__init__.py +15 -0
- confarg-0.0.1.dev4/src/confarg/cli/argparse/__init__.py +34 -0
- confarg-0.0.1.dev4/src/confarg/cli/argparse/_build.py +642 -0
- {confarg-0.0.1.dev3/src/confarg → confarg-0.0.1.dev4/src/confarg/cli/argparse}/_completion.py +25 -21
- confarg-0.0.1.dev4/src/confarg/cli/argparse/_namespace.py +267 -0
- confarg-0.0.1.dev4/src/confarg/cli/argparse/_register.py +196 -0
- confarg-0.0.1.dev4/src/confarg/cli/argparse/_spec.py +149 -0
- confarg-0.0.1.dev4/src/confarg/cli/click/__init__.py +16 -0
- confarg-0.0.1.dev4/src/confarg/cli/click/_completion.py +79 -0
- confarg-0.0.1.dev4/src/confarg/cli/click/_context.py +139 -0
- confarg-0.0.1.dev4/src/confarg/cli/click/_register.py +162 -0
- {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/typedload/_coerce.py +4 -0
- {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/typedload/_construct.py +5 -9
- confarg-0.0.1.dev3/src/confarg/_argparse.py +0 -970
- {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_callable.py +0 -0
- {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_defaults.py +0 -0
- {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_errors.py +0 -0
- {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_files.py +0 -0
- {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_parse_cli.py +0 -0
- {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_parse_env.py +0 -0
- {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/_serialize.py +0 -0
- {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/dictexpr/__init__.py +0 -0
- {confarg-0.0.1.dev3 → confarg-0.0.1.dev4}/src/confarg/dictexpr/_expressions.py +0 -0
- {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.
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
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.
|
|
@@ -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
|
+
]
|