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.
Files changed (34) hide show
  1. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/PKG-INFO +92 -54
  2. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/README.md +88 -51
  3. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/pyproject.toml +6 -4
  4. confarg-0.0.1.dev6/src/confarg/__init__.py +60 -0
  5. confarg-0.0.1.dev4/src/confarg/__init__.py → confarg-0.0.1.dev6/src/confarg/_api.py +128 -143
  6. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/_callable.py +43 -76
  7. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/_defaults.py +11 -1
  8. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/_files.py +24 -10
  9. confarg-0.0.1.dev6/src/confarg/_import.py +40 -0
  10. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/_merge.py +30 -16
  11. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/_parse_cli.py +675 -656
  12. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/_parse_env.py +69 -9
  13. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/_serialize.py +24 -4
  14. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/_types.py +36 -38
  15. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/__init__.py +1 -1
  16. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/__init__.py +1 -1
  17. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/_build.py +181 -87
  18. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/_completion.py +141 -107
  19. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/_namespace.py +133 -47
  20. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/_register.py +8 -8
  21. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/_spec.py +3 -5
  22. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/click/__init__.py +7 -1
  23. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/click/_completion.py +10 -7
  24. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/click/_context.py +11 -22
  25. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/cli/click/_register.py +24 -39
  26. confarg-0.0.1.dev6/src/confarg/cli/cyclopts/__init__.py +20 -0
  27. confarg-0.0.1.dev6/src/confarg/cli/cyclopts/_context.py +137 -0
  28. confarg-0.0.1.dev6/src/confarg/cli/cyclopts/_register.py +176 -0
  29. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/dictexpr/__init__.py +3 -3
  30. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/dictexpr/_expressions.py +16 -15
  31. confarg-0.0.1.dev4/src/confarg/_errors.py → confarg-0.0.1.dev6/src/confarg/exceptions.py +8 -2
  32. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/typedload/__init__.py +2 -2
  33. {confarg-0.0.1.dev4 → confarg-0.0.1.dev6}/src/confarg/typedload/_coerce.py +44 -11
  34. {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.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.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
- `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.
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
- 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`.
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
- If none of this makes sense to you, read along.
30
+ ## Install
24
31
 
25
- ## Keep your data structures and CLI
32
+ ```bash
33
+ pip install confarg
34
+ ```
26
35
 
27
- `confarg` is deliberately not a framework, but just a tool.
36
+ `confarg` comes with no dependency, but installing additional libraries such as `pyyaml` unlocks the support of extra configuration file formats.
28
37
 
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.
38
+ ## TL;DR
30
39
 
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.
40
+ This library lets you read configurations stored in classes like this
32
41
 
33
- ## Install
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
- pip install confarg
74
+ myapp --subconfig.foo=33
37
75
  ```
38
76
 
39
- Installing additional libraries such as `pyyaml` unlocks the support of extra configuration file formats.
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
- ```yaml
66
- # config.yaml
67
- host: example.com
68
- port: 1234
69
- name: mydb
70
- ```
103
+ ```yaml
104
+ # config.yaml
105
+ host: example.com
106
+ port: 1234
107
+ name: mydb
108
+ ```
71
109
 
72
- You would then call your application as
110
+ You would then call your application as
73
111
 
74
- ```console notest
75
- $ myapp.py --config config.yaml
76
- DBConfig(host='example.com', port=1234, name='mydb')
77
- ```
112
+ ```console
113
+ $ myapp.py --config config.yaml
114
+ DBConfig(host='example.com', port=1234, name='mydb')
115
+ ```
78
116
 
79
- Configuration files in TOML and JSON formats are also supported.
117
+ Configuration files in TOML and JSON formats are also supported.
80
118
 
81
- > You can change the default `config` flag to something else using the `config_flag` parameter.
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
- ```properties
86
- MYAPP_HOST=example.com
87
- MYAPP_PORT=1234
88
- MYAPP_NAME=mydb
89
- ```
123
+ ```properties
124
+ MYAPP_HOST=example.com
125
+ MYAPP_PORT=1234
126
+ MYAPP_NAME=mydb
127
+ ```
90
128
 
91
- for the same effect.
129
+ for the same effect.
92
130
 
93
- > Note that the environment variable prefix of your app should actually be passed to `confarg.load` like so:
94
- >
95
- > ```python
96
- > db_config = confarg.load(DBConfig, env_prefix="MYAPP_")
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
- ```console notest
102
- $ my_app --host example.com --port 1234 --name mydb
103
- DBConfig(host='example.com', port=1234, name='mydb')
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 notest
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 notest
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 notest
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 notest
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 notest
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 notest
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 notest
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 notest
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 notest
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 notest
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 notest
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 notest
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
- `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.
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
- 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`.
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
- If none of this makes sense to you, read along.
16
+ ## Install
11
17
 
12
- ## Keep your data structures and CLI
18
+ ```bash
19
+ pip install confarg
20
+ ```
13
21
 
14
- `confarg` is deliberately not a framework, but just a tool.
22
+ `confarg` comes with no dependency, but installing additional libraries such as `pyyaml` unlocks the support of extra configuration file formats.
15
23
 
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.
24
+ ## TL;DR
17
25
 
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.
26
+ This library lets you read configurations stored in classes like this
19
27
 
20
- ## Install
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
- pip install confarg
60
+ myapp --subconfig.foo=33
24
61
  ```
25
62
 
26
- Installing additional libraries such as `pyyaml` unlocks the support of extra configuration file formats.
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
- ```yaml
53
- # config.yaml
54
- host: example.com
55
- port: 1234
56
- name: mydb
57
- ```
89
+ ```yaml
90
+ # config.yaml
91
+ host: example.com
92
+ port: 1234
93
+ name: mydb
94
+ ```
58
95
 
59
- You would then call your application as
96
+ You would then call your application as
60
97
 
61
- ```console notest
62
- $ myapp.py --config config.yaml
63
- DBConfig(host='example.com', port=1234, name='mydb')
64
- ```
98
+ ```console
99
+ $ myapp.py --config config.yaml
100
+ DBConfig(host='example.com', port=1234, name='mydb')
101
+ ```
65
102
 
66
- Configuration files in TOML and JSON formats are also supported.
103
+ Configuration files in TOML and JSON formats are also supported.
67
104
 
68
- > You can change the default `config` flag to something else using the `config_flag` parameter.
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
- ```properties
73
- MYAPP_HOST=example.com
74
- MYAPP_PORT=1234
75
- MYAPP_NAME=mydb
76
- ```
109
+ ```properties
110
+ MYAPP_HOST=example.com
111
+ MYAPP_PORT=1234
112
+ MYAPP_NAME=mydb
113
+ ```
77
114
 
78
- for the same effect.
115
+ for the same effect.
79
116
 
80
- > Note that the environment variable prefix of your app should actually be passed to `confarg.load` like so:
81
- >
82
- > ```python
83
- > db_config = confarg.load(DBConfig, env_prefix="MYAPP_")
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
- ```console notest
89
- $ my_app --host example.com --port 1234 --name mydb
90
- DBConfig(host='example.com', port=1234, name='mydb')
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 notest
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 notest
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 notest
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 notest
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 notest
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 notest
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 notest
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 notest
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 notest
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 notest
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 notest
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 notest
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
- 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.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-docs>=0.9.2",
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
+ ]