confarg 0.0.1.dev4__tar.gz → 0.0.1.dev6__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.dev4 → confarg-0.0.1.dev6}/PKG-INFO +92 -54
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/README.md +88 -51
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/pyproject.toml +6 -4
- confarg-0.0.1.dev6/src/confarg/__init__.py +60 -0
- confarg-0.0.1.dev4/src/confarg/__init__.py → confarg-0.0.1.dev6/src/confarg/_api.py +128 -143
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/_callable.py +43 -76
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/_defaults.py +11 -1
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/_files.py +24 -10
- confarg-0.0.1.dev6/src/confarg/_import.py +40 -0
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/_merge.py +30 -16
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/_parse_cli.py +675 -656
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/_parse_env.py +69 -9
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/_serialize.py +24 -4
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/_types.py +36 -38
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/__init__.py +1 -1
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/__init__.py +1 -1
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/_build.py +181 -87
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/_completion.py +141 -107
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/_namespace.py +133 -47
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/_register.py +8 -8
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/_spec.py +3 -5
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/click/__init__.py +7 -1
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/click/_completion.py +10 -7
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/click/_context.py +11 -22
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/click/_register.py +24 -39
- confarg-0.0.1.dev6/src/confarg/cli/cyclopts/__init__.py +20 -0
- confarg-0.0.1.dev6/src/confarg/cli/cyclopts/_context.py +137 -0
- confarg-0.0.1.dev6/src/confarg/cli/cyclopts/_register.py +176 -0
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/dictexpr/__init__.py +3 -3
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/dictexpr/_expressions.py +16 -15
- confarg-0.0.1.dev4/src/confarg/_errors.py → confarg-0.0.1.dev6/src/confarg/exceptions.py +8 -2
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/typedload/__init__.py +2 -2
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/typedload/_coerce.py +44 -11
- {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/typedload/_construct.py +132 -54
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: confarg
|
|
3
|
-
Version: 0.0.1.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.0.1.dev6
|
|
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
|
|
@@ -11,32 +12,69 @@ Project-URL: Repository, https://github.com/confarg/confarg
|
|
|
11
12
|
Provides-Extra: completion
|
|
12
13
|
Description-Content-Type: text/markdown
|
|
13
14
|
|
|
15
|
+
<!-- pytest-markdown-console-file: notest -->
|
|
16
|
+
<p align="center">
|
|
17
|
+
<img src="docs/assets/banner.svg" alt="confarg" />
|
|
18
|
+
</p>
|
|
19
|
+
|
|
14
20
|
# A tool to manage complex configurations
|
|
15
21
|
|
|
16
|
-
> Load and resolve complex configurations from files, environment variables and command line arguments. Keep your favorite CLI library.
|
|
22
|
+
> Load and resolve complex configurations from files, environment variables and command line arguments. Keep your data structures and favorite CLI library.
|
|
17
23
|
|
|
24
|
+
`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
25
|
|
|
19
|
-
|
|
26
|
+
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
27
|
|
|
21
|
-
|
|
28
|
+
`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.
|
|
22
29
|
|
|
23
|
-
|
|
30
|
+
## Install
|
|
24
31
|
|
|
25
|
-
|
|
32
|
+
```bash
|
|
33
|
+
pip install confarg
|
|
34
|
+
```
|
|
26
35
|
|
|
27
|
-
`confarg`
|
|
36
|
+
`confarg` comes with no dependency, but installing additional libraries such as `pyyaml` unlocks the support of extra configuration file formats.
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
## TL;DR
|
|
30
39
|
|
|
31
|
-
|
|
40
|
+
This library lets you read configurations stored in classes like this
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
```python
|
|
43
|
+
@dataclass
|
|
44
|
+
class Config:
|
|
45
|
+
value: float
|
|
46
|
+
flag: bool
|
|
47
|
+
subconfig: SubConfig1 | SubConfig2
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
with this kind of code
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
config = confarg.load(Config)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
which lets you build a configuration object from files,
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
value: 1.0
|
|
60
|
+
flag: false
|
|
61
|
+
subconfig:
|
|
62
|
+
foo: 42
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
but also and simultaneously from environment variables,
|
|
66
|
+
|
|
67
|
+
```properties
|
|
68
|
+
MYAPP_VALUE=0.0
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
and command line arguments,
|
|
34
72
|
|
|
35
73
|
```bash
|
|
36
|
-
|
|
74
|
+
myapp --subconfig.foo=33
|
|
37
75
|
```
|
|
38
76
|
|
|
39
|
-
|
|
77
|
+
Still here? Read along, we are just getting started.
|
|
40
78
|
|
|
41
79
|
## Getting started
|
|
42
80
|
|
|
@@ -62,46 +100,46 @@ This allows you to construct a `DBConfig` object by collecting data from three p
|
|
|
62
100
|
|
|
63
101
|
1. From a configuration file. By passing `--config <config_file>` to your app, `confarg` will load the content of the file and fill the `DBConfig` object. For example, a config file could look like so:
|
|
64
102
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
103
|
+
```yaml
|
|
104
|
+
# config.yaml
|
|
105
|
+
host: example.com
|
|
106
|
+
port: 1234
|
|
107
|
+
name: mydb
|
|
108
|
+
```
|
|
71
109
|
|
|
72
|
-
|
|
110
|
+
You would then call your application as
|
|
73
111
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
112
|
+
```console
|
|
113
|
+
$ myapp.py --config config.yaml
|
|
114
|
+
DBConfig(host='example.com', port=1234, name='mydb')
|
|
115
|
+
```
|
|
78
116
|
|
|
79
|
-
|
|
117
|
+
Configuration files in TOML and JSON formats are also supported.
|
|
80
118
|
|
|
81
|
-
|
|
119
|
+
> You can change the default `config` flag to something else using the `config_flag` parameter.
|
|
82
120
|
|
|
83
121
|
2. From environment variables. You can declare
|
|
84
122
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
123
|
+
```properties
|
|
124
|
+
MYAPP_HOST=example.com
|
|
125
|
+
MYAPP_PORT=1234
|
|
126
|
+
MYAPP_NAME=mydb
|
|
127
|
+
```
|
|
90
128
|
|
|
91
|
-
|
|
129
|
+
for the same effect.
|
|
92
130
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
131
|
+
> Note that the environment variable prefix of your app should actually be passed to `confarg.load` like so:
|
|
132
|
+
>
|
|
133
|
+
> ```python
|
|
134
|
+
> db_config = confarg.load(DBConfig, env_prefix="MYAPP_")
|
|
135
|
+
> ```
|
|
98
136
|
|
|
99
137
|
3. From command line arguments.
|
|
100
138
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
139
|
+
```console
|
|
140
|
+
$ my_app --host example.com --port 1234 --name mydb
|
|
141
|
+
DBConfig(host='example.com', port=1234, name='mydb')
|
|
142
|
+
```
|
|
105
143
|
|
|
106
144
|
### Progressive build-up
|
|
107
145
|
|
|
@@ -119,7 +157,7 @@ port: 1234
|
|
|
119
157
|
|
|
120
158
|
and provide the schema name from the command line:
|
|
121
159
|
|
|
122
|
-
```console
|
|
160
|
+
```console
|
|
123
161
|
$ myapp.py --config partial_config.yaml --name mydb
|
|
124
162
|
DBConfig(host='example.com', port=1234, name='mydb')
|
|
125
163
|
```
|
|
@@ -134,7 +172,7 @@ Configuration data is read in the following order, later read overwriting existi
|
|
|
134
172
|
|
|
135
173
|
This allows for surgical modifications of configuration files. For example, one could overwrite the schema configuration from our existing `full_config` from the command line like so:
|
|
136
174
|
|
|
137
|
-
```console
|
|
175
|
+
```console
|
|
138
176
|
$ # Overwrite the schema name defined in the config file from the command line
|
|
139
177
|
$ myapp.py --config config.yaml --name otherdb
|
|
140
178
|
DBConfig(host='example.com', port=1234, name='otherdb')
|
|
@@ -164,7 +202,7 @@ type DBConfig = SQLiteConfig | DBServerConfig
|
|
|
164
202
|
|
|
165
203
|
`confarg` can handle this new union type and figure out which configuration is desired based on the arguments it got:
|
|
166
204
|
|
|
167
|
-
```console
|
|
205
|
+
```console
|
|
168
206
|
$ # Pass DBServerConfig parameters, and you get a DBServerConfig
|
|
169
207
|
$ myapp.py --host example.com --port 1234 --name mydb
|
|
170
208
|
DBServerConfig(host='example.com', port=1234, name='mydb')
|
|
@@ -183,7 +221,7 @@ Even when disambiguation is possible, it may not be obvious to the human eye whi
|
|
|
183
221
|
|
|
184
222
|
Therefore, by necessity or for the sake of clarity, you can provide the class path of the required configuration by using the `class` tag, like so
|
|
185
223
|
|
|
186
|
-
```console
|
|
224
|
+
```console
|
|
187
225
|
$ # Explicitly ask for a SQLiteConfig
|
|
188
226
|
$ myapp.py --class myapp.SQLiteConfig --dbpath db.sqlite
|
|
189
227
|
SQLiteConfig(dbpath='db.sqlite')
|
|
@@ -191,7 +229,7 @@ SQLiteConfig(dbpath='db.sqlite')
|
|
|
191
229
|
|
|
192
230
|
One example where it is necessary to provide the `class` path is to overwrite the configuration with a new class. Without it, command line arguments are added to the configuration, resulting in an invalid input.
|
|
193
231
|
|
|
194
|
-
```console
|
|
232
|
+
```console
|
|
195
233
|
$ # Config file contains a DBServerConfig
|
|
196
234
|
$ myapp.py --config db_server.yaml
|
|
197
235
|
DBServerConfig(host='example.com', port=1234, name='mydb')
|
|
@@ -227,7 +265,7 @@ This allows configurations to be easily extensible. Contrast with unions, where
|
|
|
227
265
|
|
|
228
266
|
The downside is that the concrete class must be tagged, as `confarg` cannot discover classes derived from a given class.
|
|
229
267
|
|
|
230
|
-
```console
|
|
268
|
+
```console
|
|
231
269
|
$ # Fails: derived class not specified
|
|
232
270
|
$ uv run myapp.py --dbpath db.sqlite
|
|
233
271
|
...
|
|
@@ -259,7 +297,7 @@ Our DB configuration, which used to be the root configuration, is now located un
|
|
|
259
297
|
|
|
260
298
|
For command line arguments, we follow the common convention of using dot-separated paths to address nested fields. Previous command line arguments for `DBConfig` are now prefixed by `db.`, like so:
|
|
261
299
|
|
|
262
|
-
```console
|
|
300
|
+
```console
|
|
263
301
|
$ myapp.py --db.class myapp.SQLiteConfig --db.dbpath db.sqlite
|
|
264
302
|
Config(db=SQLiteConfig(dbpath='db.sqlite'), log_level='INFO')
|
|
265
303
|
```
|
|
@@ -277,7 +315,7 @@ db:
|
|
|
277
315
|
|
|
278
316
|
and is used just like before:
|
|
279
317
|
|
|
280
|
-
```console
|
|
318
|
+
```console
|
|
281
319
|
$ myapp.py --config config.yaml
|
|
282
320
|
Config(db=DBServerConfig(host='example.com', port=1234, name='mydb'),
|
|
283
321
|
log_level='DEBUG')
|
|
@@ -345,7 +383,7 @@ resources:
|
|
|
345
383
|
max_heap_size_mb: ${int(resources.memory_gb * 1024 * 0.8)}
|
|
346
384
|
```
|
|
347
385
|
|
|
348
|
-
```console
|
|
386
|
+
```console
|
|
349
387
|
$ myapp.py --config expression_config.yaml
|
|
350
388
|
Config(db=SQLiteConfig(dbpath='db.sqlite'),
|
|
351
389
|
resources=Resources(cpu_count=4, memory_gb=16, max_heap_size_mb=13107),
|
|
@@ -354,7 +392,7 @@ Config(db=SQLiteConfig(dbpath='db.sqlite'),
|
|
|
354
392
|
|
|
355
393
|
Note that variable interpolation occurs after all configuration data is read. This means here that you can override `memory_gb` from the command line, and `max_heap_size_mb` will be adjusted accordingly, even though the expression is defined in the configuration file.
|
|
356
394
|
|
|
357
|
-
```console
|
|
395
|
+
```console
|
|
358
396
|
$ # Max heap is recomputed according to the expression in the config file
|
|
359
397
|
$ myapp.py --config expression_config.yaml --resources.memory_gb 8
|
|
360
398
|
Config(db=SQLiteConfig(dbpath='db.sqlite'),
|
|
@@ -372,7 +410,7 @@ Some configuration components may even be generated automatically, in which case
|
|
|
372
410
|
|
|
373
411
|
From the command line, the `--config` flag can be suffixed with a key path to load configurations there. For example,
|
|
374
412
|
|
|
375
|
-
```console
|
|
413
|
+
```console
|
|
376
414
|
# Load a config file specific to the `db` key
|
|
377
415
|
$ myapp.py --config.db db_config.yaml
|
|
378
416
|
Config(db=DBServerConfig(host='example.com', port=1234, name='mydb'), log_level='INFO')
|
|
@@ -380,7 +418,7 @@ Config(db=DBServerConfig(host='example.com', port=1234, name='mydb'), log_level=
|
|
|
380
418
|
|
|
381
419
|
A similar pattern applies to environment variables:
|
|
382
420
|
|
|
383
|
-
```console
|
|
421
|
+
```console
|
|
384
422
|
$ MYAPP_CONFIG_DB=db_config.py myapp.py
|
|
385
423
|
Config(db=DBServerConfig(host='example.com', port=1234, name='mydb'), log_level='INFO')
|
|
386
424
|
```
|
|
@@ -1,29 +1,66 @@
|
|
|
1
|
+
<!-- pytest-markdown-console-file: notest -->
|
|
2
|
+
<p align="center">
|
|
3
|
+
<img src="docs/assets/banner.svg" alt="confarg" />
|
|
4
|
+
</p>
|
|
5
|
+
|
|
1
6
|
# A tool to manage complex configurations
|
|
2
7
|
|
|
3
|
-
> Load and resolve complex configurations from files, environment variables and command line arguments. Keep your favorite CLI library.
|
|
8
|
+
> Load and resolve complex configurations from files, environment variables and command line arguments. Keep your data structures and favorite CLI library.
|
|
4
9
|
|
|
10
|
+
`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
11
|
|
|
6
|
-
|
|
12
|
+
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
13
|
|
|
8
|
-
|
|
14
|
+
`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.
|
|
9
15
|
|
|
10
|
-
|
|
16
|
+
## Install
|
|
11
17
|
|
|
12
|
-
|
|
18
|
+
```bash
|
|
19
|
+
pip install confarg
|
|
20
|
+
```
|
|
13
21
|
|
|
14
|
-
`confarg`
|
|
22
|
+
`confarg` comes with no dependency, but installing additional libraries such as `pyyaml` unlocks the support of extra configuration file formats.
|
|
15
23
|
|
|
16
|
-
|
|
24
|
+
## TL;DR
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
This library lets you read configurations stored in classes like this
|
|
19
27
|
|
|
20
|
-
|
|
28
|
+
```python
|
|
29
|
+
@dataclass
|
|
30
|
+
class Config:
|
|
31
|
+
value: float
|
|
32
|
+
flag: bool
|
|
33
|
+
subconfig: SubConfig1 | SubConfig2
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
with this kind of code
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
config = confarg.load(Config)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
which lets you build a configuration object from files,
|
|
43
|
+
|
|
44
|
+
```yaml
|
|
45
|
+
value: 1.0
|
|
46
|
+
flag: false
|
|
47
|
+
subconfig:
|
|
48
|
+
foo: 42
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
but also and simultaneously from environment variables,
|
|
52
|
+
|
|
53
|
+
```properties
|
|
54
|
+
MYAPP_VALUE=0.0
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
and command line arguments,
|
|
21
58
|
|
|
22
59
|
```bash
|
|
23
|
-
|
|
60
|
+
myapp --subconfig.foo=33
|
|
24
61
|
```
|
|
25
62
|
|
|
26
|
-
|
|
63
|
+
Still here? Read along, we are just getting started.
|
|
27
64
|
|
|
28
65
|
## Getting started
|
|
29
66
|
|
|
@@ -49,46 +86,46 @@ This allows you to construct a `DBConfig` object by collecting data from three p
|
|
|
49
86
|
|
|
50
87
|
1. From a configuration file. By passing `--config <config_file>` to your app, `confarg` will load the content of the file and fill the `DBConfig` object. For example, a config file could look like so:
|
|
51
88
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
89
|
+
```yaml
|
|
90
|
+
# config.yaml
|
|
91
|
+
host: example.com
|
|
92
|
+
port: 1234
|
|
93
|
+
name: mydb
|
|
94
|
+
```
|
|
58
95
|
|
|
59
|
-
|
|
96
|
+
You would then call your application as
|
|
60
97
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
98
|
+
```console
|
|
99
|
+
$ myapp.py --config config.yaml
|
|
100
|
+
DBConfig(host='example.com', port=1234, name='mydb')
|
|
101
|
+
```
|
|
65
102
|
|
|
66
|
-
|
|
103
|
+
Configuration files in TOML and JSON formats are also supported.
|
|
67
104
|
|
|
68
|
-
|
|
105
|
+
> You can change the default `config` flag to something else using the `config_flag` parameter.
|
|
69
106
|
|
|
70
107
|
2. From environment variables. You can declare
|
|
71
108
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
109
|
+
```properties
|
|
110
|
+
MYAPP_HOST=example.com
|
|
111
|
+
MYAPP_PORT=1234
|
|
112
|
+
MYAPP_NAME=mydb
|
|
113
|
+
```
|
|
77
114
|
|
|
78
|
-
|
|
115
|
+
for the same effect.
|
|
79
116
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
117
|
+
> Note that the environment variable prefix of your app should actually be passed to `confarg.load` like so:
|
|
118
|
+
>
|
|
119
|
+
> ```python
|
|
120
|
+
> db_config = confarg.load(DBConfig, env_prefix="MYAPP_")
|
|
121
|
+
> ```
|
|
85
122
|
|
|
86
123
|
3. From command line arguments.
|
|
87
124
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
125
|
+
```console
|
|
126
|
+
$ my_app --host example.com --port 1234 --name mydb
|
|
127
|
+
DBConfig(host='example.com', port=1234, name='mydb')
|
|
128
|
+
```
|
|
92
129
|
|
|
93
130
|
### Progressive build-up
|
|
94
131
|
|
|
@@ -106,7 +143,7 @@ port: 1234
|
|
|
106
143
|
|
|
107
144
|
and provide the schema name from the command line:
|
|
108
145
|
|
|
109
|
-
```console
|
|
146
|
+
```console
|
|
110
147
|
$ myapp.py --config partial_config.yaml --name mydb
|
|
111
148
|
DBConfig(host='example.com', port=1234, name='mydb')
|
|
112
149
|
```
|
|
@@ -121,7 +158,7 @@ Configuration data is read in the following order, later read overwriting existi
|
|
|
121
158
|
|
|
122
159
|
This allows for surgical modifications of configuration files. For example, one could overwrite the schema configuration from our existing `full_config` from the command line like so:
|
|
123
160
|
|
|
124
|
-
```console
|
|
161
|
+
```console
|
|
125
162
|
$ # Overwrite the schema name defined in the config file from the command line
|
|
126
163
|
$ myapp.py --config config.yaml --name otherdb
|
|
127
164
|
DBConfig(host='example.com', port=1234, name='otherdb')
|
|
@@ -151,7 +188,7 @@ type DBConfig = SQLiteConfig | DBServerConfig
|
|
|
151
188
|
|
|
152
189
|
`confarg` can handle this new union type and figure out which configuration is desired based on the arguments it got:
|
|
153
190
|
|
|
154
|
-
```console
|
|
191
|
+
```console
|
|
155
192
|
$ # Pass DBServerConfig parameters, and you get a DBServerConfig
|
|
156
193
|
$ myapp.py --host example.com --port 1234 --name mydb
|
|
157
194
|
DBServerConfig(host='example.com', port=1234, name='mydb')
|
|
@@ -170,7 +207,7 @@ Even when disambiguation is possible, it may not be obvious to the human eye whi
|
|
|
170
207
|
|
|
171
208
|
Therefore, by necessity or for the sake of clarity, you can provide the class path of the required configuration by using the `class` tag, like so
|
|
172
209
|
|
|
173
|
-
```console
|
|
210
|
+
```console
|
|
174
211
|
$ # Explicitly ask for a SQLiteConfig
|
|
175
212
|
$ myapp.py --class myapp.SQLiteConfig --dbpath db.sqlite
|
|
176
213
|
SQLiteConfig(dbpath='db.sqlite')
|
|
@@ -178,7 +215,7 @@ SQLiteConfig(dbpath='db.sqlite')
|
|
|
178
215
|
|
|
179
216
|
One example where it is necessary to provide the `class` path is to overwrite the configuration with a new class. Without it, command line arguments are added to the configuration, resulting in an invalid input.
|
|
180
217
|
|
|
181
|
-
```console
|
|
218
|
+
```console
|
|
182
219
|
$ # Config file contains a DBServerConfig
|
|
183
220
|
$ myapp.py --config db_server.yaml
|
|
184
221
|
DBServerConfig(host='example.com', port=1234, name='mydb')
|
|
@@ -214,7 +251,7 @@ This allows configurations to be easily extensible. Contrast with unions, where
|
|
|
214
251
|
|
|
215
252
|
The downside is that the concrete class must be tagged, as `confarg` cannot discover classes derived from a given class.
|
|
216
253
|
|
|
217
|
-
```console
|
|
254
|
+
```console
|
|
218
255
|
$ # Fails: derived class not specified
|
|
219
256
|
$ uv run myapp.py --dbpath db.sqlite
|
|
220
257
|
...
|
|
@@ -246,7 +283,7 @@ Our DB configuration, which used to be the root configuration, is now located un
|
|
|
246
283
|
|
|
247
284
|
For command line arguments, we follow the common convention of using dot-separated paths to address nested fields. Previous command line arguments for `DBConfig` are now prefixed by `db.`, like so:
|
|
248
285
|
|
|
249
|
-
```console
|
|
286
|
+
```console
|
|
250
287
|
$ myapp.py --db.class myapp.SQLiteConfig --db.dbpath db.sqlite
|
|
251
288
|
Config(db=SQLiteConfig(dbpath='db.sqlite'), log_level='INFO')
|
|
252
289
|
```
|
|
@@ -264,7 +301,7 @@ db:
|
|
|
264
301
|
|
|
265
302
|
and is used just like before:
|
|
266
303
|
|
|
267
|
-
```console
|
|
304
|
+
```console
|
|
268
305
|
$ myapp.py --config config.yaml
|
|
269
306
|
Config(db=DBServerConfig(host='example.com', port=1234, name='mydb'),
|
|
270
307
|
log_level='DEBUG')
|
|
@@ -332,7 +369,7 @@ resources:
|
|
|
332
369
|
max_heap_size_mb: ${int(resources.memory_gb * 1024 * 0.8)}
|
|
333
370
|
```
|
|
334
371
|
|
|
335
|
-
```console
|
|
372
|
+
```console
|
|
336
373
|
$ myapp.py --config expression_config.yaml
|
|
337
374
|
Config(db=SQLiteConfig(dbpath='db.sqlite'),
|
|
338
375
|
resources=Resources(cpu_count=4, memory_gb=16, max_heap_size_mb=13107),
|
|
@@ -341,7 +378,7 @@ Config(db=SQLiteConfig(dbpath='db.sqlite'),
|
|
|
341
378
|
|
|
342
379
|
Note that variable interpolation occurs after all configuration data is read. This means here that you can override `memory_gb` from the command line, and `max_heap_size_mb` will be adjusted accordingly, even though the expression is defined in the configuration file.
|
|
343
380
|
|
|
344
|
-
```console
|
|
381
|
+
```console
|
|
345
382
|
$ # Max heap is recomputed according to the expression in the config file
|
|
346
383
|
$ myapp.py --config expression_config.yaml --resources.memory_gb 8
|
|
347
384
|
Config(db=SQLiteConfig(dbpath='db.sqlite'),
|
|
@@ -359,7 +396,7 @@ Some configuration components may even be generated automatically, in which case
|
|
|
359
396
|
|
|
360
397
|
From the command line, the `--config` flag can be suffixed with a key path to load configurations there. For example,
|
|
361
398
|
|
|
362
|
-
```console
|
|
399
|
+
```console
|
|
363
400
|
# Load a config file specific to the `db` key
|
|
364
401
|
$ myapp.py --config.db db_config.yaml
|
|
365
402
|
Config(db=DBServerConfig(host='example.com', port=1234, name='mydb'), log_level='INFO')
|
|
@@ -367,7 +404,7 @@ Config(db=DBServerConfig(host='example.com', port=1234, name='mydb'), log_level=
|
|
|
367
404
|
|
|
368
405
|
A similar pattern applies to environment variables:
|
|
369
406
|
|
|
370
|
-
```console
|
|
407
|
+
```console
|
|
371
408
|
$ MYAPP_CONFIG_DB=db_config.py myapp.py
|
|
372
409
|
Config(db=DBServerConfig(host='example.com', port=1234, name='mydb'), log_level='INFO')
|
|
373
410
|
```
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
authors = [
|
|
3
3
|
{ name = "confarg", email = "280620574+confarg@users.noreply.github.com" },
|
|
4
4
|
]
|
|
5
|
-
|
|
6
|
-
|
|
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.
|
|
10
|
+
version = "0.0.1.dev6"
|
|
11
11
|
|
|
12
12
|
[project.urls]
|
|
13
13
|
Documentation = "https://confarg.github.io/confarg"
|
|
@@ -23,14 +23,16 @@ dev = [
|
|
|
23
23
|
"hypothesis>=6.100",
|
|
24
24
|
"ipython>=9.12.0",
|
|
25
25
|
"jsonargparse>=4.48.0",
|
|
26
|
+
"just>=0.8.165",
|
|
26
27
|
"mkdocstrings-python>=2.0.3",
|
|
27
28
|
"mkdocstrings>=1.0.4",
|
|
28
29
|
"pre-commit>=4.5.1",
|
|
29
30
|
"pytest-cov>=7.0.0",
|
|
30
|
-
"pytest-markdown-
|
|
31
|
+
"pytest-markdown-console>=0.0.1.dev2",
|
|
31
32
|
"pytest>=8.0",
|
|
32
33
|
"pyyaml>=6.0",
|
|
33
34
|
"tomli-w>=1.0",
|
|
35
|
+
"ty>=0.0.39",
|
|
34
36
|
"typer>=0.25.1",
|
|
35
37
|
"zensical>=0.0.41",
|
|
36
38
|
]
|
|
@@ -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
|
+
]
|