toml-combine 0.1.9__py3-none-any.whl → 0.3.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/__init__.py +2 -52
- toml_combine/cli.py +29 -11
- toml_combine/combiner.py +34 -110
- toml_combine/exceptions.py +4 -0
- toml_combine/lib.py +58 -0
- toml_combine/toml.py +17 -7
- {toml_combine-0.1.9.dist-info → toml_combine-0.3.0.dist-info}/METADATA +88 -132
- toml_combine-0.3.0.dist-info/RECORD +11 -0
- toml_combine-0.1.9.dist-info/RECORD +0 -10
- {toml_combine-0.1.9.dist-info → toml_combine-0.3.0.dist-info}/WHEEL +0 -0
- {toml_combine-0.1.9.dist-info → toml_combine-0.3.0.dist-info}/entry_points.txt +0 -0
toml_combine/__init__.py
CHANGED
@@ -1,55 +1,5 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import
|
4
|
-
from typing import Any, cast, overload
|
3
|
+
from .lib import combine
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
# Provide already parsed config
|
10
|
-
@overload
|
11
|
-
def combine(
|
12
|
-
*, config: dict[str, Any], **filters: str | list[str]
|
13
|
-
) -> dict[str, Any]: ...
|
14
|
-
# Provide toml config content
|
15
|
-
@overload
|
16
|
-
def combine(*, config: str, **filters: str | list[str]) -> dict[str, Any]: ...
|
17
|
-
# Provide toml config file path
|
18
|
-
@overload
|
19
|
-
def combine(
|
20
|
-
*, config_file: str | pathlib.Path, **filters: str | list[str]
|
21
|
-
) -> dict[str, Any]: ...
|
22
|
-
|
23
|
-
|
24
|
-
def combine(*, config=None, config_file=None, **filters):
|
25
|
-
"""
|
26
|
-
Generate outputs of configurations based on the provided TOML
|
27
|
-
configuration.
|
28
|
-
|
29
|
-
Args:
|
30
|
-
config: The TOML configuration as a string or an already parsed dictionary.
|
31
|
-
OR:
|
32
|
-
config_file: The path to the TOML configuration file.
|
33
|
-
**filters: Filters to apply to the combined configuration
|
34
|
-
(dimension="value" or dimension=["list", "of", "values"]).
|
35
|
-
|
36
|
-
Returns:
|
37
|
-
dict[str, Any]: The combined configuration ({"output_id": {...}}).
|
38
|
-
"""
|
39
|
-
if (config is None) is (config_file is None):
|
40
|
-
raise ValueError("Either 'config' or 'config_file' must be provided.")
|
41
|
-
|
42
|
-
if isinstance(config, dict):
|
43
|
-
dict_config = config
|
44
|
-
else:
|
45
|
-
if config_file:
|
46
|
-
config_string = pathlib.Path(config_file).read_text()
|
47
|
-
else:
|
48
|
-
config = cast(str, config)
|
49
|
-
config_string = config
|
50
|
-
|
51
|
-
dict_config = toml.loads(config_string)
|
52
|
-
|
53
|
-
config_obj = combiner.build_config(dict_config)
|
54
|
-
|
55
|
-
return combiner.generate_outputs(config_obj, **filters)
|
5
|
+
__all__ = ["combine"]
|
toml_combine/cli.py
CHANGED
@@ -6,7 +6,7 @@ import pathlib
|
|
6
6
|
import sys
|
7
7
|
from collections.abc import Mapping
|
8
8
|
|
9
|
-
from . import combiner, exceptions, toml
|
9
|
+
from . import combiner, exceptions, lib, toml
|
10
10
|
|
11
11
|
|
12
12
|
def get_argument_parser(
|
@@ -17,20 +17,30 @@ def get_argument_parser(
|
|
17
17
|
description="Create combined configurations from a TOML file",
|
18
18
|
add_help=(dimensions is not None),
|
19
19
|
)
|
20
|
+
arg_parser.add_argument(
|
21
|
+
"--format",
|
22
|
+
choices=["json", "toml"],
|
23
|
+
default="toml",
|
24
|
+
help="Output format",
|
25
|
+
)
|
20
26
|
arg_parser.add_argument(
|
21
27
|
"config",
|
22
28
|
type=pathlib.Path,
|
23
29
|
help="Path to the TOML configuration file",
|
24
30
|
)
|
25
|
-
if dimensions:
|
31
|
+
if dimensions is None:
|
32
|
+
arg_parser.epilog = (
|
33
|
+
"Additional arguments will be provided with a valid config file."
|
34
|
+
)
|
35
|
+
else:
|
26
36
|
group = arg_parser.add_argument_group(
|
27
37
|
"dimensions",
|
28
|
-
"
|
38
|
+
"Output the file with these dimensions",
|
29
39
|
)
|
30
40
|
|
31
41
|
for name, values in dimensions.items():
|
32
42
|
group.add_argument(
|
33
|
-
f"--{name}", choices=values, help=f"
|
43
|
+
f"--{name}", choices=values, help=f"Provide value for {name}"
|
34
44
|
)
|
35
45
|
|
36
46
|
return arg_parser
|
@@ -41,7 +51,12 @@ def cli(argv) -> int:
|
|
41
51
|
|
42
52
|
# Parse the config file argument to get the dimensions
|
43
53
|
arg_parser = get_argument_parser(dimensions=None)
|
44
|
-
|
54
|
+
try:
|
55
|
+
args, _ = arg_parser.parse_known_args(argv)
|
56
|
+
except SystemExit:
|
57
|
+
# If the user didn't provide a config file, print the help message
|
58
|
+
arg_parser.print_help()
|
59
|
+
return 1
|
45
60
|
|
46
61
|
try:
|
47
62
|
dict_config = toml.loads(args.config.read_text())
|
@@ -62,22 +77,25 @@ def cli(argv) -> int:
|
|
62
77
|
arg_parser = get_argument_parser(dimensions=config.dimensions)
|
63
78
|
args = arg_parser.parse_args(argv)
|
64
79
|
|
65
|
-
|
80
|
+
mapping = {
|
66
81
|
key: value
|
67
82
|
for key, value in vars(args).items()
|
68
83
|
if key in config.dimensions and value
|
69
84
|
}
|
70
|
-
# Generate final configurations for
|
85
|
+
# Generate final configurations for requested mapping
|
71
86
|
try:
|
72
|
-
result =
|
87
|
+
result = lib.combine(config=dict_config, **mapping)
|
73
88
|
except exceptions.TomlCombineError as exc:
|
74
89
|
print(exc, file=sys.stderr)
|
75
90
|
return 1
|
76
91
|
|
77
|
-
if
|
78
|
-
|
92
|
+
if args.format == "toml":
|
93
|
+
# Convert the result to TOML format
|
79
94
|
|
80
|
-
|
95
|
+
print(toml.dumps(result))
|
96
|
+
else:
|
97
|
+
# Convert the result to JSON format
|
98
|
+
print(json.dumps(result, indent=2))
|
81
99
|
|
82
100
|
return 0
|
83
101
|
|
toml_combine/combiner.py
CHANGED
@@ -2,26 +2,13 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import copy
|
4
4
|
import dataclasses
|
5
|
-
import itertools
|
6
5
|
from collections.abc import Mapping, Sequence
|
7
6
|
from functools import partial
|
8
|
-
from typing import Any, TypeVar
|
7
|
+
from typing import Any, TypeVar
|
9
8
|
|
10
9
|
from . import exceptions
|
11
10
|
|
12
11
|
|
13
|
-
@dataclasses.dataclass()
|
14
|
-
class Output:
|
15
|
-
dimensions: Mapping[str, str]
|
16
|
-
|
17
|
-
@property
|
18
|
-
def id(self) -> str:
|
19
|
-
return f"{'-'.join(self.dimensions.values())}"
|
20
|
-
|
21
|
-
def __str__(self) -> str:
|
22
|
-
return f"Output(id={self.id})"
|
23
|
-
|
24
|
-
|
25
12
|
@dataclasses.dataclass()
|
26
13
|
class Override:
|
27
14
|
when: Mapping[str, list[str]]
|
@@ -34,55 +21,40 @@ class Override:
|
|
34
21
|
@dataclasses.dataclass()
|
35
22
|
class Config:
|
36
23
|
dimensions: Mapping[str, list[str]]
|
37
|
-
outputs: list[Output]
|
38
24
|
default: Mapping[str, Any]
|
39
25
|
overrides: Sequence[Override]
|
40
26
|
|
41
27
|
|
42
|
-
def wrap_in_list(value: str | list[str]) -> list[str]:
|
43
|
-
"""
|
44
|
-
Wrap a string in a list if it's not already a list.
|
45
|
-
"""
|
46
|
-
if isinstance(value, str):
|
47
|
-
return [value]
|
48
|
-
return value
|
49
|
-
|
50
|
-
|
51
|
-
T = TypeVar("T", bound=Union[str, list[str]])
|
52
|
-
|
53
|
-
|
54
28
|
def clean_dimensions_dict(
|
55
|
-
to_sort: Mapping[str,
|
56
|
-
) -> dict[str,
|
29
|
+
to_sort: Mapping[str, list[str]], clean: dict[str, list[str]], type: str
|
30
|
+
) -> dict[str, list[str]]:
|
57
31
|
"""
|
58
32
|
Recreate a dictionary of dimension values with the same order as the
|
59
33
|
dimensions list.
|
60
34
|
"""
|
61
35
|
result = {}
|
62
|
-
|
36
|
+
if invalid_dimensions := set(to_sort) - set(clean):
|
37
|
+
raise exceptions.DimensionNotFound(
|
38
|
+
type=type,
|
39
|
+
id=to_sort,
|
40
|
+
dimension=", ".join(invalid_dimensions),
|
41
|
+
)
|
63
42
|
|
43
|
+
# Fix the order of the dimensions
|
64
44
|
for dimension, valid_values in clean.items():
|
65
|
-
valid_values = set(valid_values)
|
66
45
|
if dimension not in to_sort:
|
67
46
|
continue
|
68
47
|
|
69
|
-
|
70
|
-
|
71
|
-
if invalid_values := values - valid_values:
|
48
|
+
original_values = to_sort[dimension]
|
49
|
+
if invalid_values := set(original_values) - set(valid_values):
|
72
50
|
raise exceptions.DimensionValueNotFound(
|
73
51
|
type=type,
|
74
52
|
id=to_sort,
|
75
53
|
dimension=dimension,
|
76
54
|
value=", ".join(invalid_values),
|
77
55
|
)
|
78
|
-
|
79
|
-
|
80
|
-
if remaining:
|
81
|
-
raise exceptions.DimensionNotFound(
|
82
|
-
type=type,
|
83
|
-
id=to_sort,
|
84
|
-
dimension=", ".join(to_sort),
|
85
|
-
)
|
56
|
+
# Fix the order of the values
|
57
|
+
result[dimension] = [e for e in valid_values if e in original_values]
|
86
58
|
|
87
59
|
return result
|
88
60
|
|
@@ -117,7 +89,10 @@ def override_sort_key(
|
|
117
89
|
return tuple(result)
|
118
90
|
|
119
91
|
|
120
|
-
|
92
|
+
T = TypeVar("T", dict, list, str, int, float, bool)
|
93
|
+
|
94
|
+
|
95
|
+
def merge_configs(a: T, b: T, /) -> T:
|
121
96
|
"""
|
122
97
|
Recursively merge two configuration dictionaries, with b taking precedence.
|
123
98
|
"""
|
@@ -128,7 +103,7 @@ def merge_configs(a: Any, b: Any, /) -> Any:
|
|
128
103
|
return b
|
129
104
|
|
130
105
|
result = a.copy()
|
131
|
-
for key, b_value in b.items():
|
106
|
+
for key, b_value in b.items(): # type: ignore
|
132
107
|
if a_value := a.get(key):
|
133
108
|
result[key] = merge_configs(a_value, b_value)
|
134
109
|
else:
|
@@ -137,6 +112,7 @@ def merge_configs(a: Any, b: Any, /) -> Any:
|
|
137
112
|
|
138
113
|
|
139
114
|
def build_config(config: dict[str, Any]) -> Config:
|
115
|
+
config = copy.deepcopy(config)
|
140
116
|
# Parse dimensions
|
141
117
|
dimensions = config.pop("dimensions")
|
142
118
|
|
@@ -150,8 +126,11 @@ def build_config(config: dict[str, Any]) -> Config:
|
|
150
126
|
when = override.pop("when")
|
151
127
|
except KeyError:
|
152
128
|
raise exceptions.MissingOverrideCondition(id=override)
|
153
|
-
|
154
|
-
|
129
|
+
when = clean_dimensions_dict(
|
130
|
+
to_sort={k: v if isinstance(v, list) else [v] for k, v in when.items()},
|
131
|
+
clean=dimensions,
|
132
|
+
type="override",
|
133
|
+
)
|
155
134
|
|
156
135
|
conditions = tuple((k, tuple(v)) for k, v in when.items())
|
157
136
|
if conditions in seen_conditions:
|
@@ -173,93 +152,38 @@ def build_config(config: dict[str, Any]) -> Config:
|
|
173
152
|
key=partial(override_sort_key, dimensions=dimensions),
|
174
153
|
)
|
175
154
|
|
176
|
-
outputs = []
|
177
|
-
seen_conditions = set()
|
178
|
-
|
179
|
-
for output in config.pop("output", []):
|
180
|
-
for key in output:
|
181
|
-
output[key] = wrap_in_list(output[key])
|
182
|
-
|
183
|
-
for cartesian_product in itertools.product(*output.values()):
|
184
|
-
single_output = dict(zip(output.keys(), cartesian_product))
|
185
|
-
|
186
|
-
conditions = tuple(single_output.items())
|
187
|
-
if conditions in seen_conditions:
|
188
|
-
raise exceptions.DuplicateError(type="output", id=output.id)
|
189
|
-
seen_conditions.add(conditions)
|
190
|
-
|
191
|
-
outputs.append(
|
192
|
-
Output(
|
193
|
-
dimensions=clean_dimensions_dict(
|
194
|
-
single_output, dimensions, type="output"
|
195
|
-
),
|
196
|
-
)
|
197
|
-
)
|
198
|
-
|
199
155
|
return Config(
|
200
156
|
dimensions=dimensions,
|
201
|
-
outputs=outputs,
|
202
157
|
default=default,
|
203
158
|
overrides=overrides,
|
204
159
|
)
|
205
160
|
|
206
161
|
|
207
|
-
def
|
162
|
+
def mapping_matches_override(mapping: dict[str, str], override: Override) -> bool:
|
208
163
|
"""
|
209
|
-
Check if the values in the override match the
|
164
|
+
Check if the values in the override match the given dimensions.
|
210
165
|
"""
|
211
166
|
for dim, values in override.when.items():
|
212
|
-
if dim not in
|
167
|
+
if dim not in mapping:
|
213
168
|
return False
|
214
169
|
|
215
|
-
if
|
170
|
+
if mapping[dim] not in values:
|
216
171
|
return False
|
217
172
|
|
218
173
|
return True
|
219
174
|
|
220
175
|
|
221
|
-
def
|
222
|
-
default: Mapping[str, Any],
|
176
|
+
def generate_for_mapping(
|
177
|
+
default: Mapping[str, Any],
|
178
|
+
overrides: Sequence[Override],
|
179
|
+
mapping: dict[str, str],
|
223
180
|
) -> dict[str, Any]:
|
224
181
|
result = copy.deepcopy(default)
|
225
182
|
# Apply each matching override
|
226
183
|
for override in overrides:
|
227
184
|
# Check if all dimension values in the override match
|
228
185
|
|
229
|
-
if
|
186
|
+
if mapping_matches_override(mapping=mapping, override=override):
|
230
187
|
result = merge_configs(result, override.config)
|
231
188
|
|
232
|
-
return {"dimensions": output.dimensions, **result}
|
233
|
-
|
234
|
-
|
235
|
-
def generate_outputs(config: Config, **filter: str | list[str]) -> dict[str, Any]:
|
236
|
-
result = {}
|
237
|
-
filter_with_lists: dict[str, list[str]] = {}
|
238
|
-
|
239
|
-
for key, value in list(filter.items()):
|
240
|
-
if key not in config.dimensions:
|
241
|
-
raise exceptions.DimensionNotFound(type="arguments", id="", dimension=key)
|
242
|
-
|
243
|
-
value = wrap_in_list(value)
|
244
|
-
|
245
|
-
if set(value) - set(config.dimensions[key]):
|
246
|
-
raise exceptions.DimensionValueNotFound(
|
247
|
-
type="arguments",
|
248
|
-
id="",
|
249
|
-
dimension=key,
|
250
|
-
value=", ".join(set(value) - set(config.dimensions[key])),
|
251
|
-
)
|
252
|
-
filter_with_lists[key] = value
|
253
|
-
|
254
|
-
for output in config.outputs:
|
255
|
-
if all(
|
256
|
-
output.dimensions.get(key) in value
|
257
|
-
for key, value in filter_with_lists.items()
|
258
|
-
):
|
259
|
-
result[output.id] = generate_output(
|
260
|
-
default=config.default,
|
261
|
-
overrides=config.overrides,
|
262
|
-
output=output,
|
263
|
-
)
|
264
|
-
|
265
189
|
return result
|
toml_combine/exceptions.py
CHANGED
@@ -15,6 +15,10 @@ class TomlDecodeError(TomlCombineError):
|
|
15
15
|
"""Error while decoding configuration file."""
|
16
16
|
|
17
17
|
|
18
|
+
class TomlEncodeError(TomlCombineError):
|
19
|
+
"""Error while encoding configuration file."""
|
20
|
+
|
21
|
+
|
18
22
|
class DuplicateError(TomlCombineError):
|
19
23
|
"""In {type} {id}: Cannot have multiple {type}s with the same dimensions."""
|
20
24
|
|
toml_combine/lib.py
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import pathlib
|
4
|
+
from typing import Any, cast, overload
|
5
|
+
|
6
|
+
from . import combiner, toml
|
7
|
+
|
8
|
+
|
9
|
+
# Provide already parsed config
|
10
|
+
@overload
|
11
|
+
def combine(
|
12
|
+
*, config: dict[str, Any], **mapping: str | list[str]
|
13
|
+
) -> dict[str, Any]: ...
|
14
|
+
# Provide toml config content
|
15
|
+
@overload
|
16
|
+
def combine(*, config: str, **mapping: str | list[str]) -> dict[str, Any]: ...
|
17
|
+
# Provide toml config file path
|
18
|
+
@overload
|
19
|
+
def combine(
|
20
|
+
*, config_file: str | pathlib.Path, **mapping: str | list[str]
|
21
|
+
) -> dict[str, Any]: ...
|
22
|
+
|
23
|
+
|
24
|
+
def combine(*, config=None, config_file=None, **mapping):
|
25
|
+
"""
|
26
|
+
Generate outputs of configurations based on the provided TOML
|
27
|
+
configuration and a mapping of dimensions values.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
config: The TOML configuration as a string or an already parsed dictionary.
|
31
|
+
OR:
|
32
|
+
config_file: The path to the TOML configuration file.
|
33
|
+
**mapping: Define the values you want for dimensions {"<dimension>": "<value>", ...}.
|
34
|
+
|
35
|
+
Returns:
|
36
|
+
dict[str, Any]: The combined configuration.
|
37
|
+
"""
|
38
|
+
if (config is None) is (config_file is None):
|
39
|
+
raise ValueError("Either 'config' or 'config_file' must be provided.")
|
40
|
+
|
41
|
+
if isinstance(config, dict):
|
42
|
+
dict_config = config
|
43
|
+
else:
|
44
|
+
if config_file:
|
45
|
+
config_string = pathlib.Path(config_file).read_text()
|
46
|
+
else:
|
47
|
+
config = cast(str, config)
|
48
|
+
config_string = config
|
49
|
+
|
50
|
+
dict_config = toml.loads(config_string)
|
51
|
+
|
52
|
+
config_obj = combiner.build_config(dict_config)
|
53
|
+
|
54
|
+
return combiner.generate_for_mapping(
|
55
|
+
default=config_obj.default,
|
56
|
+
overrides=config_obj.overrides,
|
57
|
+
mapping=mapping,
|
58
|
+
)
|
toml_combine/toml.py
CHANGED
@@ -1,17 +1,27 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import json
|
3
4
|
from typing import Any
|
4
5
|
|
5
|
-
import
|
6
|
+
import tomlkit
|
7
|
+
import tomlkit.exceptions
|
6
8
|
|
7
9
|
from . import exceptions
|
8
10
|
|
9
11
|
|
10
12
|
def loads(raw_config: str) -> dict[str, Any]:
|
11
13
|
try:
|
12
|
-
return
|
13
|
-
except
|
14
|
-
raise exceptions.TomlDecodeError
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
return tomlkit.loads(raw_config)
|
15
|
+
except tomlkit.exceptions.ParseError as e:
|
16
|
+
raise exceptions.TomlDecodeError from e
|
17
|
+
|
18
|
+
|
19
|
+
def dumps(config: dict) -> str:
|
20
|
+
# https://github.com/python-poetry/tomlkit/issues/411
|
21
|
+
# While we're waiting for a fix, the workaround is to give up "style-preserving"
|
22
|
+
# features. The easiest way to turn tomlkit objects into plain dicts and strings
|
23
|
+
# is through a json round-trip.
|
24
|
+
try:
|
25
|
+
return tomlkit.dumps(json.loads(json.dumps(config)))
|
26
|
+
except tomlkit.exceptions.TOMLKitError as e:
|
27
|
+
raise exceptions.TomlEncodeError from e
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: toml-combine
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.3.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
|
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
16
16
|
Requires-Python: >=3.9
|
17
|
-
Requires-Dist:
|
17
|
+
Requires-Dist: tomlkit
|
18
18
|
Description-Content-Type: text/markdown
|
19
19
|
|
20
20
|
# Toml-combine
|
@@ -35,12 +35,6 @@ The configuration file is usually a TOML file. Here's a small example:
|
|
35
35
|
[dimensions]
|
36
36
|
environment = ["production", "staging"]
|
37
37
|
|
38
|
-
[[output]]
|
39
|
-
environment = "production"
|
40
|
-
|
41
|
-
[[output]]
|
42
|
-
environment = "staging"
|
43
|
-
|
44
38
|
[default]
|
45
39
|
name = "my-service"
|
46
40
|
registry = "gcr.io/my-project/"
|
@@ -61,16 +55,6 @@ Dimensions lets you describe the main "thing" that makes the outputs differents,
|
|
61
55
|
service might be `frontend` or `backend`. Some combinations of dimensions might not
|
62
56
|
exists, for example, maybe there's no `staging` in `eu`.
|
63
57
|
|
64
|
-
### Outputs
|
65
|
-
|
66
|
-
Create a `output` for each configuration you want to generate, and specify the
|
67
|
-
dimensions relevant for this output. It's ok to omit some dimensions when they're not
|
68
|
-
used for a given output.
|
69
|
-
|
70
|
-
> [!Note]
|
71
|
-
> Defining a list as the value of one or more dimensions in a output
|
72
|
-
> is a shorthand for defining all combinations of dimensions
|
73
|
-
|
74
58
|
### Default
|
75
59
|
|
76
60
|
The common configuration to start from, before we start overlaying overrides on top.
|
@@ -93,7 +77,7 @@ specific to more specific, each one overriding the values of the previous ones:
|
|
93
77
|
|
94
78
|
### The configuration itself
|
95
79
|
|
96
|
-
Under the layer of `dimensions/
|
80
|
+
Under the layer of `dimensions/default/override/mapping` system, what you actually define
|
97
81
|
in the configuration is completely up to you. That said, only nested
|
98
82
|
"dictionnaries"/"objects"/"tables"/"mapping" (those are all the same things in
|
99
83
|
Python/JS/Toml lingo) will be merged between the default and the overrides, while
|
@@ -110,9 +94,6 @@ Let's look at an example:
|
|
110
94
|
[dimensions]
|
111
95
|
environment = ["production", "staging"]
|
112
96
|
|
113
|
-
[[output]]
|
114
|
-
environment = ["production", "staging"]
|
115
|
-
|
116
97
|
[default]
|
117
98
|
fruits = [{name="apple", color="red"}]
|
118
99
|
|
@@ -121,17 +102,16 @@ when.environment = "staging"
|
|
121
102
|
fruits = [{name="orange", color="orange"}]
|
122
103
|
```
|
123
104
|
|
124
|
-
In this example,
|
125
|
-
|
105
|
+
In this example, with `{"environment": "staging"}`, `fruits` is
|
106
|
+
`[{name="orange", color="orange"}]` and not
|
107
|
+
`[{name="apple", color="red"}, {name="orange", color="orange"}]`.
|
108
|
+
The only way to get multiple values to be merged is if they are dicts: you'll need
|
126
109
|
to chose an element to become the key:
|
127
110
|
|
128
111
|
```toml
|
129
112
|
[dimensions]
|
130
113
|
environment = ["production", "staging"]
|
131
114
|
|
132
|
-
[[output]]
|
133
|
-
environment = ["production", "staging"]
|
134
|
-
|
135
115
|
[default]
|
136
116
|
fruits.apple.color = "red"
|
137
117
|
|
@@ -146,16 +126,28 @@ This example is simple because `name` is a natural choice for the key. In some c
|
|
146
126
|
the choice is less natural, but you can always decide to name the elements of your
|
147
127
|
list and use that name as a key. Also, yes, you'll loose ordering.
|
148
128
|
|
149
|
-
###
|
129
|
+
### Mapping
|
150
130
|
|
151
|
-
|
152
|
-
|
153
|
-
|
131
|
+
When you call the tool either with the CLI or the lib (see both below), you will have to
|
132
|
+
provide a mapping of the desired dimentions. These values will be compared to overrides
|
133
|
+
to apply overrides when relevant. It's ok to omit some dimensions, corresponding
|
134
|
+
overrides won't be selected.
|
135
|
+
|
136
|
+
By default, the output is `toml` though you can switch to `json` with `--format=json`
|
154
137
|
|
155
|
-
|
138
|
+
## CLI
|
156
139
|
|
157
|
-
|
158
|
-
|
140
|
+
Example with the config from the previous section:
|
141
|
+
|
142
|
+
```console
|
143
|
+
$ toml-combine path/to/config.toml --environment=staging
|
144
|
+
[dimensions]
|
145
|
+
environment = "staging"
|
146
|
+
|
147
|
+
[fruits]
|
148
|
+
apple.color = "red"
|
149
|
+
orange.color = "orange"
|
150
|
+
```
|
159
151
|
|
160
152
|
## Lib
|
161
153
|
|
@@ -163,40 +155,23 @@ Note that you can restrict generation to some dimension values by passing
|
|
163
155
|
import toml_combine
|
164
156
|
|
165
157
|
|
166
|
-
result = toml_combine.combine(
|
167
|
-
config_file=config_file,
|
168
|
-
environment=["production", "staging"],
|
169
|
-
type="job",
|
170
|
-
job=["manage", "special-command"],
|
171
|
-
)
|
158
|
+
result = toml_combine.combine(config_file=config_file, environment="staging")
|
172
159
|
|
173
160
|
print(result)
|
174
161
|
{
|
175
|
-
"
|
176
|
-
"production-job-special-command": {...},
|
177
|
-
"staging-job-manage": {...},
|
178
|
-
"staging-job-special-command": {...},
|
162
|
+
"fruits": {"apple": {"color": "red"}, "orange": {"color": "orange"}}
|
179
163
|
}
|
180
164
|
```
|
181
165
|
|
182
|
-
You can pass either `config` (TOML string or dict) or `config_file` (`pathlib.Path` or string path) to `combine()`.
|
166
|
+
You can pass either `config` (TOML string or dict) or `config_file` (`pathlib.Path` or string path) to `combine()`. All other `kwargs` specify the mapping you want.
|
183
167
|
|
184
|
-
|
168
|
+
## A bigger example
|
185
169
|
|
186
170
|
```toml
|
187
171
|
[dimensions]
|
188
172
|
environment = ["production", "staging", "dev"]
|
189
173
|
service = ["frontend", "backend"]
|
190
174
|
|
191
|
-
# All 4 combinations of those values will exist
|
192
|
-
[[output]]
|
193
|
-
environment = ["production", "staging"]
|
194
|
-
service = ["frontend", "backend"]
|
195
|
-
|
196
|
-
# On dev, the "service" is not defined. That's ok.
|
197
|
-
[[output]]
|
198
|
-
environment = "dev"
|
199
|
-
|
200
175
|
[default]
|
201
176
|
registry = "gcr.io/my-project/"
|
202
177
|
service_account = "my-service-account"
|
@@ -225,81 +200,62 @@ container.env.ENABLE_EXPENSIVE_MONITORING = false
|
|
225
200
|
|
226
201
|
This produces the following configs:
|
227
202
|
|
228
|
-
```
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
},
|
287
|
-
"dev-backend": {
|
288
|
-
"dimensions": {
|
289
|
-
"environment": "dev",
|
290
|
-
"service": "backend"
|
291
|
-
},
|
292
|
-
"registry": "gcr.io/my-project/",
|
293
|
-
"service_account": "my-service-account",
|
294
|
-
"name": "service-backend",
|
295
|
-
"container": {
|
296
|
-
"env": {
|
297
|
-
"DEBUG": true,
|
298
|
-
"ENABLE_EXPENSIVE_MONITORING": false
|
299
|
-
},
|
300
|
-
"image_name": "my-image-backend",
|
301
|
-
"port": 8080
|
302
|
-
}
|
303
|
-
}
|
304
|
-
}
|
203
|
+
```console
|
204
|
+
$ uv run toml-combine example.toml --environment=production --service=frontend
|
205
|
+
registry = "gcr.io/my-project/"
|
206
|
+
service_account = "my-service-account"
|
207
|
+
name = "service-frontend"
|
208
|
+
|
209
|
+
[container]
|
210
|
+
image_name = "my-image-frontend"
|
211
|
+
```
|
212
|
+
|
213
|
+
```console
|
214
|
+
$ toml-combine example.toml --environment=production --service=backend
|
215
|
+
registry = "gcr.io/my-project/"
|
216
|
+
service_account = "my-service-account"
|
217
|
+
name = "service-backend"
|
218
|
+
|
219
|
+
[container]
|
220
|
+
image_name = "my-image-backend"
|
221
|
+
port = 8080
|
222
|
+
```
|
223
|
+
|
224
|
+
```console
|
225
|
+
$ toml-combine example.toml --environment=staging --service=frontend
|
226
|
+
registry = "gcr.io/my-project/"
|
227
|
+
service_account = "my-service-account"
|
228
|
+
name = "service-frontend"
|
229
|
+
|
230
|
+
[container]
|
231
|
+
image_name = "my-image-frontend"
|
232
|
+
```
|
233
|
+
|
234
|
+
```console
|
235
|
+
$ toml-combine example.toml --environment=staging --service=backend
|
236
|
+
registry = "gcr.io/my-project/"
|
237
|
+
service_account = "my-service-account"
|
238
|
+
name = "service-backend"
|
239
|
+
|
240
|
+
[container]
|
241
|
+
image_name = "my-image-backend"
|
242
|
+
port = 8080
|
243
|
+
|
244
|
+
[container.env]
|
245
|
+
ENABLE_EXPENSIVE_MONITORING = false
|
246
|
+
```
|
247
|
+
|
248
|
+
```console
|
249
|
+
$ toml-combine example.toml --environment=dev --service=backend
|
250
|
+
registry = "gcr.io/my-project/"
|
251
|
+
service_account = "my-service-account"
|
252
|
+
name = "service-backend"
|
253
|
+
|
254
|
+
[container]
|
255
|
+
image_name = "my-image-backend"
|
256
|
+
port = 8080
|
257
|
+
[container.env]
|
258
|
+
DEBUG = true
|
259
|
+
ENABLE_EXPENSIVE_MONITORING = false
|
260
|
+
|
305
261
|
```
|
@@ -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=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=hqWEdBQiM960uga_9A6gXRhWhT2gVZT8IiLRc3jkyT8,797
|
8
|
+
toml_combine-0.3.0.dist-info/METADATA,sha256=xWGk3SSYPwWdcnqiRthODPYSLo3SSl84d_nSwUk3wJU,7354
|
9
|
+
toml_combine-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
10
|
+
toml_combine-0.3.0.dist-info/entry_points.txt,sha256=dXUQNom54uZt_7ylEG81iNYMamYpaFo9-ItcZJU6Uzc,58
|
11
|
+
toml_combine-0.3.0.dist-info/RECORD,,
|
@@ -1,10 +0,0 @@
|
|
1
|
-
toml_combine/__init__.py,sha256=l7i0GkM9k7cc__wj1yqK5XjpB3IJ0jqFU63tqKMuYlY,1625
|
2
|
-
toml_combine/__main__.py,sha256=hmF8N8xX6UEApzbKTVZ-4E1HU5-rjgUkdXNLO-mF6vo,100
|
3
|
-
toml_combine/cli.py,sha256=MZrAEP4wt6f9Qn0TEXIjeLoQMlvQulFpkMciwU8GRO4,2328
|
4
|
-
toml_combine/combiner.py,sha256=_sSOCVBlv8ljAdMd6BEthS6rxiCnt-VV9rMEPSK_Cvk,7643
|
5
|
-
toml_combine/exceptions.py,sha256=SepRFDxeWQEbD88jhF5g7laZSSULthho83BpW8u9RWs,897
|
6
|
-
toml_combine/toml.py,sha256=_vCINvfJeS3gWid35Pmm3Yz4xyJ8LpKJRHL0axSU8nk,384
|
7
|
-
toml_combine-0.1.9.dist-info/METADATA,sha256=ti8e_ng-_KdRPCAJybModogRhP4guqH8UilQTeXQHAk,8375
|
8
|
-
toml_combine-0.1.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
9
|
-
toml_combine-0.1.9.dist-info/entry_points.txt,sha256=dXUQNom54uZt_7ylEG81iNYMamYpaFo9-ItcZJU6Uzc,58
|
10
|
-
toml_combine-0.1.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|