confarg 0.0.1.dev5__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.dev5 → confarg-0.0.1.dev6}/PKG-INFO +47 -42
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/README.md +46 -41
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/pyproject.toml +3 -2
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/__init__.py +27 -0
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/_api.py +10 -10
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/_parse_cli.py +675 -669
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/_parse_env.py +65 -5
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/_serialize.py +22 -1
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/_types.py +32 -1
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/_build.py +107 -24
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/_completion.py +12 -12
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/_namespace.py +91 -9
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/_register.py +4 -4
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/cli/click/_completion.py +4 -4
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/cli/click/_context.py +6 -6
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/cli/click/_register.py +4 -4
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/cli/cyclopts/_context.py +11 -11
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/cli/cyclopts/_register.py +7 -4
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/typedload/_coerce.py +17 -9
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/typedload/_construct.py +92 -4
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/_callable.py +0 -0
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/_defaults.py +0 -0
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/_files.py +0 -0
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/_import.py +0 -0
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/_merge.py +0 -0
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/cli/__init__.py +0 -0
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/__init__.py +0 -0
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/cli/argparse/_spec.py +0 -0
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/cli/click/__init__.py +0 -0
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/cli/cyclopts/__init__.py +0 -0
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/dictexpr/__init__.py +0 -0
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/dictexpr/_expressions.py +0 -0
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/exceptions.py +0 -0
- {confarg-0.0.1.dev5 → confarg-0.0.1.dev6}/src/confarg/typedload/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: confarg
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.dev6
|
|
4
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>
|
|
@@ -12,6 +12,11 @@ Project-URL: Repository, https://github.com/confarg/confarg
|
|
|
12
12
|
Provides-Extra: completion
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
|
|
15
|
+
<!-- pytest-markdown-console-file: notest -->
|
|
16
|
+
<p align="center">
|
|
17
|
+
<img src="docs/assets/banner.svg" alt="confarg" />
|
|
18
|
+
</p>
|
|
19
|
+
|
|
15
20
|
# A tool to manage complex configurations
|
|
16
21
|
|
|
17
22
|
> Load and resolve complex configurations from files, environment variables and command line arguments. Keep your data structures and favorite CLI library.
|
|
@@ -20,7 +25,7 @@ Description-Content-Type: text/markdown
|
|
|
20
25
|
|
|
21
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`.
|
|
22
27
|
|
|
23
|
-
`confarg` is not a framework. No decorator, base class or special annotation type required: none are provided. It is just a tool for the deserialization and serialization of complex configurations. Its footprint in your code is typically a few lines of code
|
|
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.
|
|
24
29
|
|
|
25
30
|
## Install
|
|
26
31
|
|
|
@@ -95,46 +100,46 @@ This allows you to construct a `DBConfig` object by collecting data from three p
|
|
|
95
100
|
|
|
96
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:
|
|
97
102
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
```yaml
|
|
104
|
+
# config.yaml
|
|
105
|
+
host: example.com
|
|
106
|
+
port: 1234
|
|
107
|
+
name: mydb
|
|
108
|
+
```
|
|
104
109
|
|
|
105
|
-
|
|
110
|
+
You would then call your application as
|
|
106
111
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
```console
|
|
113
|
+
$ myapp.py --config config.yaml
|
|
114
|
+
DBConfig(host='example.com', port=1234, name='mydb')
|
|
115
|
+
```
|
|
111
116
|
|
|
112
|
-
|
|
117
|
+
Configuration files in TOML and JSON formats are also supported.
|
|
113
118
|
|
|
114
|
-
|
|
119
|
+
> You can change the default `config` flag to something else using the `config_flag` parameter.
|
|
115
120
|
|
|
116
121
|
2. From environment variables. You can declare
|
|
117
122
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
```properties
|
|
124
|
+
MYAPP_HOST=example.com
|
|
125
|
+
MYAPP_PORT=1234
|
|
126
|
+
MYAPP_NAME=mydb
|
|
127
|
+
```
|
|
123
128
|
|
|
124
|
-
|
|
129
|
+
for the same effect.
|
|
125
130
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
+
> ```
|
|
131
136
|
|
|
132
137
|
3. From command line arguments.
|
|
133
138
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
139
|
+
```console
|
|
140
|
+
$ my_app --host example.com --port 1234 --name mydb
|
|
141
|
+
DBConfig(host='example.com', port=1234, name='mydb')
|
|
142
|
+
```
|
|
138
143
|
|
|
139
144
|
### Progressive build-up
|
|
140
145
|
|
|
@@ -152,7 +157,7 @@ port: 1234
|
|
|
152
157
|
|
|
153
158
|
and provide the schema name from the command line:
|
|
154
159
|
|
|
155
|
-
```console
|
|
160
|
+
```console
|
|
156
161
|
$ myapp.py --config partial_config.yaml --name mydb
|
|
157
162
|
DBConfig(host='example.com', port=1234, name='mydb')
|
|
158
163
|
```
|
|
@@ -167,7 +172,7 @@ Configuration data is read in the following order, later read overwriting existi
|
|
|
167
172
|
|
|
168
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:
|
|
169
174
|
|
|
170
|
-
```console
|
|
175
|
+
```console
|
|
171
176
|
$ # Overwrite the schema name defined in the config file from the command line
|
|
172
177
|
$ myapp.py --config config.yaml --name otherdb
|
|
173
178
|
DBConfig(host='example.com', port=1234, name='otherdb')
|
|
@@ -197,7 +202,7 @@ type DBConfig = SQLiteConfig | DBServerConfig
|
|
|
197
202
|
|
|
198
203
|
`confarg` can handle this new union type and figure out which configuration is desired based on the arguments it got:
|
|
199
204
|
|
|
200
|
-
```console
|
|
205
|
+
```console
|
|
201
206
|
$ # Pass DBServerConfig parameters, and you get a DBServerConfig
|
|
202
207
|
$ myapp.py --host example.com --port 1234 --name mydb
|
|
203
208
|
DBServerConfig(host='example.com', port=1234, name='mydb')
|
|
@@ -216,7 +221,7 @@ Even when disambiguation is possible, it may not be obvious to the human eye whi
|
|
|
216
221
|
|
|
217
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
|
|
218
223
|
|
|
219
|
-
```console
|
|
224
|
+
```console
|
|
220
225
|
$ # Explicitly ask for a SQLiteConfig
|
|
221
226
|
$ myapp.py --class myapp.SQLiteConfig --dbpath db.sqlite
|
|
222
227
|
SQLiteConfig(dbpath='db.sqlite')
|
|
@@ -224,7 +229,7 @@ SQLiteConfig(dbpath='db.sqlite')
|
|
|
224
229
|
|
|
225
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.
|
|
226
231
|
|
|
227
|
-
```console
|
|
232
|
+
```console
|
|
228
233
|
$ # Config file contains a DBServerConfig
|
|
229
234
|
$ myapp.py --config db_server.yaml
|
|
230
235
|
DBServerConfig(host='example.com', port=1234, name='mydb')
|
|
@@ -260,7 +265,7 @@ This allows configurations to be easily extensible. Contrast with unions, where
|
|
|
260
265
|
|
|
261
266
|
The downside is that the concrete class must be tagged, as `confarg` cannot discover classes derived from a given class.
|
|
262
267
|
|
|
263
|
-
```console
|
|
268
|
+
```console
|
|
264
269
|
$ # Fails: derived class not specified
|
|
265
270
|
$ uv run myapp.py --dbpath db.sqlite
|
|
266
271
|
...
|
|
@@ -292,7 +297,7 @@ Our DB configuration, which used to be the root configuration, is now located un
|
|
|
292
297
|
|
|
293
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:
|
|
294
299
|
|
|
295
|
-
```console
|
|
300
|
+
```console
|
|
296
301
|
$ myapp.py --db.class myapp.SQLiteConfig --db.dbpath db.sqlite
|
|
297
302
|
Config(db=SQLiteConfig(dbpath='db.sqlite'), log_level='INFO')
|
|
298
303
|
```
|
|
@@ -310,7 +315,7 @@ db:
|
|
|
310
315
|
|
|
311
316
|
and is used just like before:
|
|
312
317
|
|
|
313
|
-
```console
|
|
318
|
+
```console
|
|
314
319
|
$ myapp.py --config config.yaml
|
|
315
320
|
Config(db=DBServerConfig(host='example.com', port=1234, name='mydb'),
|
|
316
321
|
log_level='DEBUG')
|
|
@@ -378,7 +383,7 @@ resources:
|
|
|
378
383
|
max_heap_size_mb: ${int(resources.memory_gb * 1024 * 0.8)}
|
|
379
384
|
```
|
|
380
385
|
|
|
381
|
-
```console
|
|
386
|
+
```console
|
|
382
387
|
$ myapp.py --config expression_config.yaml
|
|
383
388
|
Config(db=SQLiteConfig(dbpath='db.sqlite'),
|
|
384
389
|
resources=Resources(cpu_count=4, memory_gb=16, max_heap_size_mb=13107),
|
|
@@ -387,7 +392,7 @@ Config(db=SQLiteConfig(dbpath='db.sqlite'),
|
|
|
387
392
|
|
|
388
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.
|
|
389
394
|
|
|
390
|
-
```console
|
|
395
|
+
```console
|
|
391
396
|
$ # Max heap is recomputed according to the expression in the config file
|
|
392
397
|
$ myapp.py --config expression_config.yaml --resources.memory_gb 8
|
|
393
398
|
Config(db=SQLiteConfig(dbpath='db.sqlite'),
|
|
@@ -405,7 +410,7 @@ Some configuration components may even be generated automatically, in which case
|
|
|
405
410
|
|
|
406
411
|
From the command line, the `--config` flag can be suffixed with a key path to load configurations there. For example,
|
|
407
412
|
|
|
408
|
-
```console
|
|
413
|
+
```console
|
|
409
414
|
# Load a config file specific to the `db` key
|
|
410
415
|
$ myapp.py --config.db db_config.yaml
|
|
411
416
|
Config(db=DBServerConfig(host='example.com', port=1234, name='mydb'), log_level='INFO')
|
|
@@ -413,7 +418,7 @@ Config(db=DBServerConfig(host='example.com', port=1234, name='mydb'), log_level=
|
|
|
413
418
|
|
|
414
419
|
A similar pattern applies to environment variables:
|
|
415
420
|
|
|
416
|
-
```console
|
|
421
|
+
```console
|
|
417
422
|
$ MYAPP_CONFIG_DB=db_config.py myapp.py
|
|
418
423
|
Config(db=DBServerConfig(host='example.com', port=1234, name='mydb'), log_level='INFO')
|
|
419
424
|
```
|
|
@@ -1,3 +1,8 @@
|
|
|
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
8
|
> Load and resolve complex configurations from files, environment variables and command line arguments. Keep your data structures and favorite CLI library.
|
|
@@ -6,7 +11,7 @@
|
|
|
6
11
|
|
|
7
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`.
|
|
8
13
|
|
|
9
|
-
`confarg` is not a framework. No decorator, base class or special annotation type required: none are provided. It is just a tool for the deserialization and serialization of complex configurations. Its footprint in your code is typically a few lines of code
|
|
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.
|
|
10
15
|
|
|
11
16
|
## Install
|
|
12
17
|
|
|
@@ -81,46 +86,46 @@ This allows you to construct a `DBConfig` object by collecting data from three p
|
|
|
81
86
|
|
|
82
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:
|
|
83
88
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
```yaml
|
|
90
|
+
# config.yaml
|
|
91
|
+
host: example.com
|
|
92
|
+
port: 1234
|
|
93
|
+
name: mydb
|
|
94
|
+
```
|
|
90
95
|
|
|
91
|
-
|
|
96
|
+
You would then call your application as
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
```console
|
|
99
|
+
$ myapp.py --config config.yaml
|
|
100
|
+
DBConfig(host='example.com', port=1234, name='mydb')
|
|
101
|
+
```
|
|
97
102
|
|
|
98
|
-
|
|
103
|
+
Configuration files in TOML and JSON formats are also supported.
|
|
99
104
|
|
|
100
|
-
|
|
105
|
+
> You can change the default `config` flag to something else using the `config_flag` parameter.
|
|
101
106
|
|
|
102
107
|
2. From environment variables. You can declare
|
|
103
108
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
```properties
|
|
110
|
+
MYAPP_HOST=example.com
|
|
111
|
+
MYAPP_PORT=1234
|
|
112
|
+
MYAPP_NAME=mydb
|
|
113
|
+
```
|
|
109
114
|
|
|
110
|
-
|
|
115
|
+
for the same effect.
|
|
111
116
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
+
> ```
|
|
117
122
|
|
|
118
123
|
3. From command line arguments.
|
|
119
124
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
```console
|
|
126
|
+
$ my_app --host example.com --port 1234 --name mydb
|
|
127
|
+
DBConfig(host='example.com', port=1234, name='mydb')
|
|
128
|
+
```
|
|
124
129
|
|
|
125
130
|
### Progressive build-up
|
|
126
131
|
|
|
@@ -138,7 +143,7 @@ port: 1234
|
|
|
138
143
|
|
|
139
144
|
and provide the schema name from the command line:
|
|
140
145
|
|
|
141
|
-
```console
|
|
146
|
+
```console
|
|
142
147
|
$ myapp.py --config partial_config.yaml --name mydb
|
|
143
148
|
DBConfig(host='example.com', port=1234, name='mydb')
|
|
144
149
|
```
|
|
@@ -153,7 +158,7 @@ Configuration data is read in the following order, later read overwriting existi
|
|
|
153
158
|
|
|
154
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:
|
|
155
160
|
|
|
156
|
-
```console
|
|
161
|
+
```console
|
|
157
162
|
$ # Overwrite the schema name defined in the config file from the command line
|
|
158
163
|
$ myapp.py --config config.yaml --name otherdb
|
|
159
164
|
DBConfig(host='example.com', port=1234, name='otherdb')
|
|
@@ -183,7 +188,7 @@ type DBConfig = SQLiteConfig | DBServerConfig
|
|
|
183
188
|
|
|
184
189
|
`confarg` can handle this new union type and figure out which configuration is desired based on the arguments it got:
|
|
185
190
|
|
|
186
|
-
```console
|
|
191
|
+
```console
|
|
187
192
|
$ # Pass DBServerConfig parameters, and you get a DBServerConfig
|
|
188
193
|
$ myapp.py --host example.com --port 1234 --name mydb
|
|
189
194
|
DBServerConfig(host='example.com', port=1234, name='mydb')
|
|
@@ -202,7 +207,7 @@ Even when disambiguation is possible, it may not be obvious to the human eye whi
|
|
|
202
207
|
|
|
203
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
|
|
204
209
|
|
|
205
|
-
```console
|
|
210
|
+
```console
|
|
206
211
|
$ # Explicitly ask for a SQLiteConfig
|
|
207
212
|
$ myapp.py --class myapp.SQLiteConfig --dbpath db.sqlite
|
|
208
213
|
SQLiteConfig(dbpath='db.sqlite')
|
|
@@ -210,7 +215,7 @@ SQLiteConfig(dbpath='db.sqlite')
|
|
|
210
215
|
|
|
211
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.
|
|
212
217
|
|
|
213
|
-
```console
|
|
218
|
+
```console
|
|
214
219
|
$ # Config file contains a DBServerConfig
|
|
215
220
|
$ myapp.py --config db_server.yaml
|
|
216
221
|
DBServerConfig(host='example.com', port=1234, name='mydb')
|
|
@@ -246,7 +251,7 @@ This allows configurations to be easily extensible. Contrast with unions, where
|
|
|
246
251
|
|
|
247
252
|
The downside is that the concrete class must be tagged, as `confarg` cannot discover classes derived from a given class.
|
|
248
253
|
|
|
249
|
-
```console
|
|
254
|
+
```console
|
|
250
255
|
$ # Fails: derived class not specified
|
|
251
256
|
$ uv run myapp.py --dbpath db.sqlite
|
|
252
257
|
...
|
|
@@ -278,7 +283,7 @@ Our DB configuration, which used to be the root configuration, is now located un
|
|
|
278
283
|
|
|
279
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:
|
|
280
285
|
|
|
281
|
-
```console
|
|
286
|
+
```console
|
|
282
287
|
$ myapp.py --db.class myapp.SQLiteConfig --db.dbpath db.sqlite
|
|
283
288
|
Config(db=SQLiteConfig(dbpath='db.sqlite'), log_level='INFO')
|
|
284
289
|
```
|
|
@@ -296,7 +301,7 @@ db:
|
|
|
296
301
|
|
|
297
302
|
and is used just like before:
|
|
298
303
|
|
|
299
|
-
```console
|
|
304
|
+
```console
|
|
300
305
|
$ myapp.py --config config.yaml
|
|
301
306
|
Config(db=DBServerConfig(host='example.com', port=1234, name='mydb'),
|
|
302
307
|
log_level='DEBUG')
|
|
@@ -364,7 +369,7 @@ resources:
|
|
|
364
369
|
max_heap_size_mb: ${int(resources.memory_gb * 1024 * 0.8)}
|
|
365
370
|
```
|
|
366
371
|
|
|
367
|
-
```console
|
|
372
|
+
```console
|
|
368
373
|
$ myapp.py --config expression_config.yaml
|
|
369
374
|
Config(db=SQLiteConfig(dbpath='db.sqlite'),
|
|
370
375
|
resources=Resources(cpu_count=4, memory_gb=16, max_heap_size_mb=13107),
|
|
@@ -373,7 +378,7 @@ Config(db=SQLiteConfig(dbpath='db.sqlite'),
|
|
|
373
378
|
|
|
374
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.
|
|
375
380
|
|
|
376
|
-
```console
|
|
381
|
+
```console
|
|
377
382
|
$ # Max heap is recomputed according to the expression in the config file
|
|
378
383
|
$ myapp.py --config expression_config.yaml --resources.memory_gb 8
|
|
379
384
|
Config(db=SQLiteConfig(dbpath='db.sqlite'),
|
|
@@ -391,7 +396,7 @@ Some configuration components may even be generated automatically, in which case
|
|
|
391
396
|
|
|
392
397
|
From the command line, the `--config` flag can be suffixed with a key path to load configurations there. For example,
|
|
393
398
|
|
|
394
|
-
```console
|
|
399
|
+
```console
|
|
395
400
|
# Load a config file specific to the `db` key
|
|
396
401
|
$ myapp.py --config.db db_config.yaml
|
|
397
402
|
Config(db=DBServerConfig(host='example.com', port=1234, name='mydb'), log_level='INFO')
|
|
@@ -399,7 +404,7 @@ Config(db=DBServerConfig(host='example.com', port=1234, name='mydb'), log_level=
|
|
|
399
404
|
|
|
400
405
|
A similar pattern applies to environment variables:
|
|
401
406
|
|
|
402
|
-
```console
|
|
407
|
+
```console
|
|
403
408
|
$ MYAPP_CONFIG_DB=db_config.py myapp.py
|
|
404
409
|
Config(db=DBServerConfig(host='example.com', port=1234, name='mydb'), log_level='INFO')
|
|
405
410
|
```
|
|
@@ -7,7 +7,7 @@ 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,11 +23,12 @@ 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",
|
|
@@ -10,9 +10,34 @@
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
|
|
13
18
|
from confarg import exceptions
|
|
14
19
|
from confarg._api import build, dump, dump_file, from_dict, load, merge, resolve
|
|
15
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
|
+
|
|
16
41
|
|
|
17
42
|
__all__ = [ # noqa: RUF022
|
|
18
43
|
# Two-step API
|
|
@@ -28,6 +53,8 @@ __all__ = [ # noqa: RUF022
|
|
|
28
53
|
"dump_file",
|
|
29
54
|
# Types
|
|
30
55
|
"TagPolicy",
|
|
56
|
+
# Leaf-type extension
|
|
57
|
+
"register_leaf_type",
|
|
31
58
|
# Exceptions / warnings
|
|
32
59
|
"exceptions",
|
|
33
60
|
]
|
|
@@ -60,7 +60,7 @@ def _load_cli_config(fpath: Path, subpath: str, config_flag: str) -> dict[str, A
|
|
|
60
60
|
def merge( # noqa: PLR0913
|
|
61
61
|
target: type | TypeAliasType | UnionType,
|
|
62
62
|
*,
|
|
63
|
-
|
|
63
|
+
argv: Sequence[str] | None = None,
|
|
64
64
|
env: Mapping[str, str] | None = None,
|
|
65
65
|
env_prefix: str | None = _defaults.ENV_PREFIX,
|
|
66
66
|
env_separator: str = "__",
|
|
@@ -80,7 +80,7 @@ def merge( # noqa: PLR0913
|
|
|
80
80
|
|
|
81
81
|
Args:
|
|
82
82
|
target: The dataclass type (or scalar type) used to guide CLI parsing.
|
|
83
|
-
|
|
83
|
+
argv: CLI arguments to parse. Defaults to sys.argv[1:].
|
|
84
84
|
env: Environment variable mapping to scan. Defaults to os.environ.
|
|
85
85
|
env_prefix: Prefix that env vars must start with. Defaults to ``None``,
|
|
86
86
|
which disables environment variable parsing entirely. Set to ``""``
|
|
@@ -120,13 +120,13 @@ def merge( # noqa: PLR0913
|
|
|
120
120
|
InvalidConfigFileError: If a config file cannot be loaded.
|
|
121
121
|
UnknownArgumentError: If an unrecognized CLI argument is encountered.
|
|
122
122
|
"""
|
|
123
|
-
if
|
|
124
|
-
|
|
123
|
+
if argv is None:
|
|
124
|
+
argv = sys.argv[1:]
|
|
125
125
|
if env is None:
|
|
126
126
|
env = os.environ
|
|
127
127
|
|
|
128
128
|
# 1. Parse CLI
|
|
129
|
-
cli_data, cli_configs = _parse_cli(
|
|
129
|
+
cli_data, cli_configs = _parse_cli(argv, target, cli_prefix, config_flag, union_tag)
|
|
130
130
|
|
|
131
131
|
# 2. Parse env vars (done here so env-specified config files are loaded in order)
|
|
132
132
|
if env_prefix is None:
|
|
@@ -263,7 +263,7 @@ def from_dict[T](
|
|
|
263
263
|
def load[T](
|
|
264
264
|
target: type[T],
|
|
265
265
|
*,
|
|
266
|
-
|
|
266
|
+
argv: Sequence[str] | None = ...,
|
|
267
267
|
env: Mapping[str, str] | None = ...,
|
|
268
268
|
env_prefix: str | None = ...,
|
|
269
269
|
env_separator: str = ...,
|
|
@@ -277,7 +277,7 @@ def load[T](
|
|
|
277
277
|
def load(
|
|
278
278
|
target: object,
|
|
279
279
|
*,
|
|
280
|
-
|
|
280
|
+
argv: Sequence[str] | None = ...,
|
|
281
281
|
env: Mapping[str, str] | None = ...,
|
|
282
282
|
env_prefix: str | None = ...,
|
|
283
283
|
env_separator: str = ...,
|
|
@@ -290,7 +290,7 @@ def load(
|
|
|
290
290
|
def load[T]( # noqa: PLR0913
|
|
291
291
|
target: type[T] | TypeAliasType | UnionType,
|
|
292
292
|
*,
|
|
293
|
-
|
|
293
|
+
argv: Sequence[str] | None = None,
|
|
294
294
|
env: Mapping[str, str] | None = None,
|
|
295
295
|
env_prefix: str | None = _defaults.ENV_PREFIX,
|
|
296
296
|
env_separator: str = "__",
|
|
@@ -308,7 +308,7 @@ def load[T]( # noqa: PLR0913
|
|
|
308
308
|
|
|
309
309
|
Args:
|
|
310
310
|
target: The dataclass type (or scalar type) to load configuration into.
|
|
311
|
-
|
|
311
|
+
argv: CLI arguments to parse. Defaults to sys.argv[1:].
|
|
312
312
|
env: Environment variable mapping to scan. Defaults to os.environ.
|
|
313
313
|
env_prefix: Prefix that env vars must start with. Defaults to ``None``,
|
|
314
314
|
which disables environment variable parsing entirely. Set to ``""``
|
|
@@ -344,7 +344,7 @@ def load[T]( # noqa: PLR0913
|
|
|
344
344
|
"""
|
|
345
345
|
data = merge(
|
|
346
346
|
target,
|
|
347
|
-
|
|
347
|
+
argv=argv,
|
|
348
348
|
env=env,
|
|
349
349
|
env_prefix=env_prefix,
|
|
350
350
|
env_separator=env_separator,
|