toml-combine 0.4.0__py3-none-any.whl → 0.5.0__py3-none-any.whl
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.
- toml_combine/combiner.py +61 -21
- toml_combine/exceptions.py +1 -1
- toml_combine/lib.py +1 -2
- {toml_combine-0.4.0.dist-info → toml_combine-0.5.0.dist-info}/METADATA +27 -10
- toml_combine-0.5.0.dist-info/RECORD +11 -0
- toml_combine-0.4.0.dist-info/RECORD +0 -11
- {toml_combine-0.4.0.dist-info → toml_combine-0.5.0.dist-info}/WHEEL +0 -0
- {toml_combine-0.4.0.dist-info → toml_combine-0.5.0.dist-info}/entry_points.txt +0 -0
toml_combine/combiner.py
CHANGED
@@ -2,7 +2,8 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import copy
|
4
4
|
import dataclasses
|
5
|
-
|
5
|
+
import itertools
|
6
|
+
from collections.abc import Iterable, Mapping, Sequence
|
6
7
|
from functools import partial
|
7
8
|
from typing import Any, TypeVar
|
8
9
|
|
@@ -80,6 +81,18 @@ def override_sort_key(
|
|
80
81
|
{"env": "dev", "region": "us"} (less specific)
|
81
82
|
- Override with {"env": "dev"} comes before override with {"region": "us"} ("env"
|
82
83
|
is defined before "region" in the dimensions list)
|
84
|
+
|
85
|
+
Parameters:
|
86
|
+
-----------
|
87
|
+
override: An Override object that defines the condition when it applies
|
88
|
+
(override.when)
|
89
|
+
dimensions: The dict of all existing dimensions and their values, in order of
|
90
|
+
definition
|
91
|
+
|
92
|
+
Returns:
|
93
|
+
--------
|
94
|
+
A tuple that supports comparisons. Less specific Overrides should return smaller
|
95
|
+
values and vice versa.
|
83
96
|
"""
|
84
97
|
result = [len(override.when)]
|
85
98
|
for i, dimension in enumerate(dimensions):
|
@@ -111,6 +124,35 @@ def merge_configs(a: T, b: T, /) -> T:
|
|
111
124
|
return result
|
112
125
|
|
113
126
|
|
127
|
+
def extract_keys(config: Any) -> Iterable[tuple[str, ...]]:
|
128
|
+
"""
|
129
|
+
Extract the keys from a config.
|
130
|
+
"""
|
131
|
+
if isinstance(config, dict):
|
132
|
+
for key, value in config.items():
|
133
|
+
for sub_key in extract_keys(value):
|
134
|
+
yield (key, *sub_key)
|
135
|
+
else:
|
136
|
+
yield tuple()
|
137
|
+
|
138
|
+
|
139
|
+
def extract_conditions_and_keys(
|
140
|
+
when: dict[str, list[str]], config: dict[str, Any]
|
141
|
+
) -> Iterable[tuple[Any, ...]]:
|
142
|
+
"""
|
143
|
+
Extract the definitions from an override.
|
144
|
+
"""
|
145
|
+
when_definitions = []
|
146
|
+
for key, values in when.items():
|
147
|
+
when_definitions.append([(key, value) for value in values])
|
148
|
+
|
149
|
+
when_combined_definitions = list(itertools.product(*when_definitions))
|
150
|
+
config_keys = extract_keys(config)
|
151
|
+
for config_key in config_keys:
|
152
|
+
for when_definition in when_combined_definitions:
|
153
|
+
yield (when_definition, *config_key)
|
154
|
+
|
155
|
+
|
114
156
|
def build_config(config: dict[str, Any]) -> Config:
|
115
157
|
config = copy.deepcopy(config)
|
116
158
|
# Parse dimensions
|
@@ -119,7 +161,9 @@ def build_config(config: dict[str, Any]) -> Config:
|
|
119
161
|
# Parse template
|
120
162
|
default = config.pop("default", {})
|
121
163
|
|
122
|
-
|
164
|
+
# The rule is: the same exact set of conditions cannot be used twice to define
|
165
|
+
# the same values (on the same or different overrides)
|
166
|
+
seen_conditions_and_keys = set()
|
123
167
|
overrides = []
|
124
168
|
for override in config.pop("override", []):
|
125
169
|
try:
|
@@ -132,20 +176,17 @@ def build_config(config: dict[str, Any]) -> Config:
|
|
132
176
|
type="override",
|
133
177
|
)
|
134
178
|
|
135
|
-
|
136
|
-
|
137
|
-
|
179
|
+
conditions_and_keys = set(
|
180
|
+
extract_conditions_and_keys(when=when, config=override)
|
181
|
+
)
|
182
|
+
if duplicates := (conditions_and_keys & seen_conditions_and_keys):
|
183
|
+
duplicate_str = ", ".join(sorted(key for *_, key in duplicates))
|
184
|
+
raise exceptions.DuplicateError(id=when, details=duplicate_str)
|
138
185
|
|
139
|
-
|
186
|
+
seen_conditions_and_keys |= conditions_and_keys
|
187
|
+
|
188
|
+
overrides.append(Override(when=when, config=override))
|
140
189
|
|
141
|
-
overrides.append(
|
142
|
-
Override(
|
143
|
-
when=clean_dimensions_dict(
|
144
|
-
to_sort=when, clean=dimensions, type="override"
|
145
|
-
),
|
146
|
-
config=override,
|
147
|
-
)
|
148
|
-
)
|
149
190
|
# Sort overrides by increasing specificity
|
150
191
|
overrides = sorted(
|
151
192
|
overrides,
|
@@ -159,7 +200,7 @@ def build_config(config: dict[str, Any]) -> Config:
|
|
159
200
|
)
|
160
201
|
|
161
202
|
|
162
|
-
def mapping_matches_override(mapping:
|
203
|
+
def mapping_matches_override(mapping: Mapping[str, str], override: Override) -> bool:
|
163
204
|
"""
|
164
205
|
Check if the values in the override match the given dimensions.
|
165
206
|
"""
|
@@ -174,13 +215,12 @@ def mapping_matches_override(mapping: dict[str, str], override: Override) -> boo
|
|
174
215
|
|
175
216
|
|
176
217
|
def generate_for_mapping(
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
result = copy.deepcopy(default)
|
218
|
+
config: Config,
|
219
|
+
mapping: Mapping[str, str],
|
220
|
+
) -> Mapping[str, Any]:
|
221
|
+
result = copy.deepcopy(config.default)
|
182
222
|
# Apply each matching override
|
183
|
-
for override in overrides:
|
223
|
+
for override in config.overrides:
|
184
224
|
# Check if all dimension values in the override match
|
185
225
|
|
186
226
|
if mapping_matches_override(mapping=mapping, override=override):
|
toml_combine/exceptions.py
CHANGED
@@ -20,7 +20,7 @@ class TomlEncodeError(TomlCombineError):
|
|
20
20
|
|
21
21
|
|
22
22
|
class DuplicateError(TomlCombineError):
|
23
|
-
"""In
|
23
|
+
"""In override {id}: Overrides with the same dimensions cannot define the same configuration keys: {details}"""
|
24
24
|
|
25
25
|
|
26
26
|
class DimensionNotFound(TomlCombineError):
|
toml_combine/lib.py
CHANGED
@@ -52,7 +52,6 @@ def combine(*, config=None, config_file=None, **mapping):
|
|
52
52
|
config_obj = combiner.build_config(dict_config)
|
53
53
|
|
54
54
|
return combiner.generate_for_mapping(
|
55
|
-
|
56
|
-
overrides=config_obj.overrides,
|
55
|
+
config=config_obj,
|
57
56
|
mapping=mapping,
|
58
57
|
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: toml-combine
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.5.0
|
4
4
|
Summary: A tool for combining complex configurations in TOML format.
|
5
5
|
Author-email: Joachim Jablon <ewjoachim@gmail.com>
|
6
6
|
License-Expression: MIT
|
@@ -29,7 +29,7 @@ parts that are common to everyone.
|
|
29
29
|
|
30
30
|
### The config file
|
31
31
|
|
32
|
-
The configuration file is usually a TOML file. Here's a small example:
|
32
|
+
The configuration file is (usually) a TOML file. Here's a small example:
|
33
33
|
|
34
34
|
```toml
|
35
35
|
[dimensions]
|
@@ -65,11 +65,14 @@ Overrides define a set of condition where they apply (`when.<dimension> =
|
|
65
65
|
"<value>"`) and the values that are overriden. Overrides are applied in order from less
|
66
66
|
specific to more specific, each one overriding the values of the previous ones:
|
67
67
|
|
68
|
-
-
|
69
|
-
|
70
|
-
- In case 2 overrides
|
71
|
-
|
72
|
-
|
68
|
+
- In case 2 overrides are applicable, the more specific one (the one with more
|
69
|
+
dimensions defined) has greater priority
|
70
|
+
- In case 2 overrides use the same number of dimensions, then it depends on how the
|
71
|
+
dimensions are defined at the top of the file: dimensions defined last have a greater
|
72
|
+
priority
|
73
|
+
- In case 2 overrides use the same dimensions, if they define the same configuration
|
74
|
+
values, an error will be raised. If they define different configuation values, then
|
75
|
+
the priority is irrelevant.
|
73
76
|
|
74
77
|
> [!Note]
|
75
78
|
> Defining a list as the value of one or more conditions in an override
|
@@ -141,9 +144,9 @@ Example with the config from the previous section:
|
|
141
144
|
|
142
145
|
```console
|
143
146
|
$ toml-combine path/to/config.toml --environment=staging
|
144
|
-
|
145
|
-
environment = "staging"
|
147
|
+
```
|
146
148
|
|
149
|
+
```toml
|
147
150
|
[fruits]
|
148
151
|
apple.color = "red"
|
149
152
|
orange.color = "orange"
|
@@ -202,6 +205,9 @@ This produces the following configs:
|
|
202
205
|
|
203
206
|
```console
|
204
207
|
$ toml-combine example.toml --environment=production --service=frontend
|
208
|
+
```
|
209
|
+
|
210
|
+
```toml
|
205
211
|
registry = "gcr.io/my-project/"
|
206
212
|
service_account = "my-service-account"
|
207
213
|
name = "service-frontend"
|
@@ -212,6 +218,9 @@ image_name = "my-image-frontend"
|
|
212
218
|
|
213
219
|
```console
|
214
220
|
$ toml-combine example.toml --environment=production --service=backend
|
221
|
+
```
|
222
|
+
|
223
|
+
```toml
|
215
224
|
registry = "gcr.io/my-project/"
|
216
225
|
service_account = "my-service-account"
|
217
226
|
name = "service-backend"
|
@@ -223,6 +232,9 @@ port = 8080
|
|
223
232
|
|
224
233
|
```console
|
225
234
|
$ toml-combine example.toml --environment=staging --service=frontend
|
235
|
+
```
|
236
|
+
|
237
|
+
```toml
|
226
238
|
registry = "gcr.io/my-project/"
|
227
239
|
service_account = "my-service-account"
|
228
240
|
name = "service-frontend"
|
@@ -233,6 +245,9 @@ image_name = "my-image-frontend"
|
|
233
245
|
|
234
246
|
```console
|
235
247
|
$ toml-combine example.toml --environment=staging --service=backend
|
248
|
+
```
|
249
|
+
|
250
|
+
```toml
|
236
251
|
registry = "gcr.io/my-project/"
|
237
252
|
service_account = "my-service-account"
|
238
253
|
name = "service-backend"
|
@@ -247,6 +262,9 @@ ENABLE_EXPENSIVE_MONITORING = false
|
|
247
262
|
|
248
263
|
```console
|
249
264
|
$ toml-combine example.toml --environment=dev --service=backend
|
265
|
+
```
|
266
|
+
|
267
|
+
```toml
|
250
268
|
registry = "gcr.io/my-project/"
|
251
269
|
service_account = "my-service-account"
|
252
270
|
name = "service-backend"
|
@@ -257,5 +275,4 @@ port = 8080
|
|
257
275
|
[container.env]
|
258
276
|
DEBUG = true
|
259
277
|
ENABLE_EXPENSIVE_MONITORING = false
|
260
|
-
|
261
278
|
```
|
@@ -0,0 +1,11 @@
|
|
1
|
+
toml_combine/__init__.py,sha256=TDkOwwEM-nS6hOh79u9Qae6g2Q6VfANpPpnKGfSgu80,84
|
2
|
+
toml_combine/__main__.py,sha256=hmF8N8xX6UEApzbKTVZ-4E1HU5-rjgUkdXNLO-mF6vo,100
|
3
|
+
toml_combine/cli.py,sha256=hG03eDKz7xU-ydJIa1kDuu6WlFzNS3GTMJ6zals9M9c,2843
|
4
|
+
toml_combine/combiner.py,sha256=BCsZOm7Cr2_JxttsKzjJH_HYQkD2XPjylXKlkxvi1EY,6974
|
5
|
+
toml_combine/exceptions.py,sha256=Qg_gGIdXcwTmWDlIfJOidXkViBOVSPdLx0WOELxFPp0,1026
|
6
|
+
toml_combine/lib.py,sha256=jh6OG57JefpGa-WE-mLSIK6KjyJ0-1yGBynr_kiVTww,1634
|
7
|
+
toml_combine/toml.py,sha256=iBV8xj0qWcvGp2AZaML8FCT3i2X9DL7iA6jd-wcP5Bc,814
|
8
|
+
toml_combine-0.5.0.dist-info/METADATA,sha256=XRrnb4YkB_wwoGdoZBDQBGoXi4qY9FmjBhKGQVAJjc4,7585
|
9
|
+
toml_combine-0.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
10
|
+
toml_combine-0.5.0.dist-info/entry_points.txt,sha256=dXUQNom54uZt_7ylEG81iNYMamYpaFo9-ItcZJU6Uzc,58
|
11
|
+
toml_combine-0.5.0.dist-info/RECORD,,
|
@@ -1,11 +0,0 @@
|
|
1
|
-
toml_combine/__init__.py,sha256=TDkOwwEM-nS6hOh79u9Qae6g2Q6VfANpPpnKGfSgu80,84
|
2
|
-
toml_combine/__main__.py,sha256=hmF8N8xX6UEApzbKTVZ-4E1HU5-rjgUkdXNLO-mF6vo,100
|
3
|
-
toml_combine/cli.py,sha256=hG03eDKz7xU-ydJIa1kDuu6WlFzNS3GTMJ6zals9M9c,2843
|
4
|
-
toml_combine/combiner.py,sha256=RhhCevncnVvxFYNywvtVWkVMpiqtF0mq_APjg76Tg4Q,5546
|
5
|
-
toml_combine/exceptions.py,sha256=tAFTDRSg6d10bBruBhsasZXrNNgLTmr_nKfvIsRR_yU,991
|
6
|
-
toml_combine/lib.py,sha256=Iw7F8SCyQMlhaqSD2vtnmM6jbnrgzCZeX0d-LTM3VVg,1683
|
7
|
-
toml_combine/toml.py,sha256=iBV8xj0qWcvGp2AZaML8FCT3i2X9DL7iA6jd-wcP5Bc,814
|
8
|
-
toml_combine-0.4.0.dist-info/METADATA,sha256=WKLq2pGpPRwfLhdpBuqxoAtCwVKXipgcega_4GTl_r4,7347
|
9
|
-
toml_combine-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
10
|
-
toml_combine-0.4.0.dist-info/entry_points.txt,sha256=dXUQNom54uZt_7ylEG81iNYMamYpaFo9-ItcZJU6Uzc,58
|
11
|
-
toml_combine-0.4.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|