toml-combine 0.1.7__py3-none-any.whl → 0.2.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 CHANGED
@@ -1,55 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import pathlib
4
- from typing import Any, cast, overload
3
+ from .lib import combine
5
4
 
6
- from . import combiner, toml
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
- "Filter the generated outputs by dimensions",
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"Limit to given {name}"
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
- args, _ = arg_parser.parse_known_args(argv)
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
- dimensions_filter = {
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 each output
85
+ # Generate final configurations for requested mapping
71
86
  try:
72
- result = combiner.generate_outputs(config=config, **dimensions_filter)
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 not result:
78
- print("No outputs found", file=sys.stderr)
92
+ if args.format == "toml":
93
+ # Convert the result to TOML format
79
94
 
80
- print(json.dumps(result, indent=2))
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,7 +2,6 @@ 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
7
  from typing import Any
@@ -10,21 +9,9 @@ from typing import Any
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
- when: Mapping[str, str | list[str]]
14
+ when: Mapping[str, list[str]]
28
15
  config: Mapping[str, Any]
29
16
 
30
17
  def __str__(self) -> str:
@@ -34,52 +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
28
  def clean_dimensions_dict(
52
- to_sort: Mapping[str, str | list[str]], clean: dict[str, list[str]], type: str
53
- ) -> dict[str, str]:
29
+ to_sort: Mapping[str, list[str]], clean: dict[str, list[str]], type: str
30
+ ) -> dict[str, list[str]]:
54
31
  """
55
32
  Recreate a dictionary of dimension values with the same order as the
56
33
  dimensions list.
57
34
  """
58
35
  result = {}
59
- remaining = dict(to_sort)
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
+ )
60
42
 
43
+ # Fix the order of the dimensions
61
44
  for dimension, valid_values in clean.items():
62
- valid_values = set(valid_values)
63
45
  if dimension not in to_sort:
64
46
  continue
65
47
 
66
- original_value = remaining.pop(dimension)
67
- values = set(wrap_in_list(original_value))
68
- if invalid_values := values - valid_values:
48
+ original_values = to_sort[dimension]
49
+ if invalid_values := set(original_values) - set(valid_values):
69
50
  raise exceptions.DimensionValueNotFound(
70
51
  type=type,
71
52
  id=to_sort,
72
53
  dimension=dimension,
73
54
  value=", ".join(invalid_values),
74
55
  )
75
- result[dimension] = original_value
76
-
77
- if remaining:
78
- raise exceptions.DimensionNotFound(
79
- type=type,
80
- id=to_sort,
81
- dimension=", ".join(to_sort),
82
- )
56
+ # Fix the order of the values
57
+ result[dimension] = [e for e in valid_values if e in original_values]
83
58
 
84
59
  return result
85
60
 
@@ -134,6 +109,7 @@ def merge_configs(a: Any, b: Any, /) -> Any:
134
109
 
135
110
 
136
111
  def build_config(config: dict[str, Any]) -> Config:
112
+ config = copy.deepcopy(config)
137
113
  # Parse dimensions
138
114
  dimensions = config.pop("dimensions")
139
115
 
@@ -147,8 +123,13 @@ def build_config(config: dict[str, Any]) -> Config:
147
123
  when = override.pop("when")
148
124
  except KeyError:
149
125
  raise exceptions.MissingOverrideCondition(id=override)
126
+ when = clean_dimensions_dict(
127
+ to_sort={k: v if isinstance(v, list) else [v] for k, v in when.items()},
128
+ clean=dimensions,
129
+ type="override",
130
+ )
150
131
 
151
- conditions = tuple((k, tuple(wrap_in_list(v))) for k, v in when.items())
132
+ conditions = tuple((k, tuple(v)) for k, v in when.items())
152
133
  if conditions in seen_conditions:
153
134
  raise exceptions.DuplicateError(type="override", id=when)
154
135
 
@@ -168,82 +149,38 @@ def build_config(config: dict[str, Any]) -> Config:
168
149
  key=partial(override_sort_key, dimensions=dimensions),
169
150
  )
170
151
 
171
- outputs = []
172
- seen_conditions = set()
173
-
174
- for output in config.pop("output", []):
175
- for key in output:
176
- output[key] = wrap_in_list(output[key])
177
-
178
- for cartesian_product in itertools.product(*output.values()):
179
- # Create a dictionary with the same keys as when
180
- single_output = dict(zip(output.keys(), cartesian_product))
181
-
182
- conditions = tuple(single_output.items())
183
- if conditions in seen_conditions:
184
- raise exceptions.DuplicateError(type="output", id=output.id)
185
- seen_conditions.add(conditions)
186
-
187
- outputs.append(
188
- Output(
189
- dimensions=clean_dimensions_dict(
190
- single_output, dimensions, type="output"
191
- ),
192
- )
193
- )
194
-
195
152
  return Config(
196
153
  dimensions=dimensions,
197
- outputs=outputs,
198
154
  default=default,
199
155
  overrides=overrides,
200
156
  )
201
157
 
202
158
 
203
- def generate_output(
204
- default: Mapping[str, Any], overrides: Sequence[Override], output: Output
159
+ def mapping_matches_override(mapping: dict[str, str], override: Override) -> bool:
160
+ """
161
+ Check if the values in the override match the given dimensions.
162
+ """
163
+ for dim, values in override.when.items():
164
+ if dim not in mapping:
165
+ return False
166
+
167
+ if mapping[dim] not in values:
168
+ return False
169
+
170
+ return True
171
+
172
+
173
+ def generate_for_mapping(
174
+ default: Mapping[str, Any],
175
+ overrides: Sequence[Override],
176
+ mapping: dict[str, str],
205
177
  ) -> dict[str, Any]:
206
178
  result = copy.deepcopy(default)
207
179
  # Apply each matching override
208
180
  for override in overrides:
209
181
  # Check if all dimension values in the override match
210
- if all(
211
- override.when.get(dim) == output.dimensions.get(dim)
212
- for dim in override.when.keys()
213
- ):
214
- result = merge_configs(result, override.config)
215
-
216
- return {"dimensions": output.dimensions, **result}
217
182
 
183
+ if mapping_matches_override(mapping=mapping, override=override):
184
+ result = merge_configs(result, override.config)
218
185
 
219
- def generate_outputs(config: Config, **filter: str | list[str]) -> dict[str, Any]:
220
- result = {}
221
- filter_with_lists: dict[str, list[str]] = {}
222
-
223
- for key, value in list(filter.items()):
224
- if key not in config.dimensions:
225
- raise exceptions.DimensionNotFound(type="arguments", id="", dimension=key)
226
-
227
- value = wrap_in_list(value)
228
-
229
- if set(value) - set(config.dimensions[key]):
230
- raise exceptions.DimensionValueNotFound(
231
- type="arguments",
232
- id="",
233
- dimension=key,
234
- value=", ".join(set(value) - set(config.dimensions[key])),
235
- )
236
- filter_with_lists[key] = value
237
-
238
- for output in config.outputs:
239
- if all(
240
- output.dimensions.get(key) in value
241
- for key, value in filter_with_lists.items()
242
- ):
243
- result[output.id] = generate_output(
244
- default=config.default,
245
- overrides=config.overrides,
246
- output=output,
247
- )
248
-
249
- return result
186
+ return {"dimensions": mapping, **result}
@@ -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 tomli
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 tomli.loads(raw_config)
13
- except tomli.TOMLDecodeError as e:
14
- raise exceptions.TomlDecodeError(
15
- message="Syntax error in TOML configuration file",
16
- context=str(e),
17
- ) from e
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.1.7
3
+ Version: 0.2.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: tomli>=2.2.1
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/output/default/override` system, what you actually define
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, on staging, `fruits` is `[{name="orange", color="orange"}]` and not `[{name="apple", color="red"}, {name="orange", color="orange"}]`.
125
- The only way to get multiple values to be merged is if they are tables: you'll need
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,22 +126,54 @@ 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
- ### A bigger example
129
+ ### Mapping
130
+
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. The mapping you pass is also returned in the output as a
135
+ dict under the `dimensions` key.
136
+
137
+ By default, the output is `toml` though you can switch to `json` with `--format=json`
138
+
139
+ ## CLI
140
+
141
+ Example with the config from the previous section:
142
+
143
+ ```console
144
+ $ toml-combine path/to/config.toml --environment=staging
145
+ [dimensions]
146
+ environment = "staging"
147
+
148
+ [fruits]
149
+ apple.color = "red"
150
+ orange.color = "orange"
151
+ ```
152
+
153
+ ## Lib
154
+
155
+ ```python
156
+ import toml_combine
157
+
158
+
159
+ result = toml_combine.combine(config_file=config_file, environment="staging")
160
+
161
+ print(result)
162
+ {
163
+ "dimensions": {"environment": "staging"},
164
+ "fruits": {"apple": {"color": "red"}, "orange": {"color": "orange"}}
165
+ }
166
+ ```
167
+
168
+ 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.
169
+
170
+ ## A bigger example
150
171
 
151
172
  ```toml
152
173
  [dimensions]
153
174
  environment = ["production", "staging", "dev"]
154
175
  service = ["frontend", "backend"]
155
176
 
156
- # All 4 combinations of those values will exist
157
- [[output]]
158
- environment = ["production", "staging"]
159
- service = ["frontend", "backend"]
160
-
161
- # On dev, the "service" is not defined. That's ok.
162
- [[output]]
163
- environment = "dev"
164
-
165
177
  [default]
166
178
  registry = "gcr.io/my-project/"
167
179
  service_account = "my-service-account"
@@ -181,39 +193,86 @@ container.port = 8080
181
193
  name = "service-dev"
182
194
  when.environment = "dev"
183
195
  container.env.DEBUG = true
196
+
197
+ [[override]]
198
+ when.environment = ["staging", "dev"]
199
+ when.service = "backend"
200
+ container.env.ENABLE_EXPENSIVE_MONITORING = false
184
201
  ```
185
202
 
186
- ### CLI
203
+ This produces the following configs:
187
204
 
188
205
  ```console
189
- $ toml-combine {path/to/config.toml}
206
+ $ uv run toml-combine example.toml --environment=production --service=frontend
207
+ registry = "gcr.io/my-project/"
208
+ service_account = "my-service-account"
209
+ name = "service-frontend"
210
+ [dimensions]
211
+ environment = "production"
212
+ service = "frontend"
213
+
214
+ [container]
215
+ image_name = "my-image-frontend"
190
216
  ```
191
217
 
192
- Generates all the outputs described by the given TOML config.
218
+ ```console
219
+ $ toml-combine example.toml --environment=production --service=backend
220
+ registry = "gcr.io/my-project/"
221
+ service_account = "my-service-account"
222
+ name = "service-backend"
223
+ [dimensions]
224
+ environment = "production"
225
+ service = "backend"
193
226
 
194
- Note that you can restrict generation to some dimension values by passing
195
- `--{dimension}={value}`
227
+ [container]
228
+ image_name = "my-image-backend"
229
+ port = 8080
230
+ ```
196
231
 
197
- ## Lib
232
+ ```console
233
+ $ toml-combine example.toml --environment=staging --service=frontend
234
+ registry = "gcr.io/my-project/"
235
+ service_account = "my-service-account"
236
+ name = "service-frontend"
237
+ [dimensions]
238
+ environment = "staging"
239
+ service = "frontend"
198
240
 
199
- ```python
200
- import toml_combine
241
+ [container]
242
+ image_name = "my-image-frontend"
243
+ ```
201
244
 
245
+ ```console
246
+ $ toml-combine example.toml --environment=staging --service=backend
247
+ registry = "gcr.io/my-project/"
248
+ service_account = "my-service-account"
249
+ name = "service-backend"
250
+ [dimensions]
251
+ environment = "staging"
252
+ service = "backend"
202
253
 
203
- result = toml_combine.combine(
204
- config_file=config_file,
205
- environment=["production", "staging"],
206
- type="job",
207
- job=["manage", "special-command"],
208
- )
254
+ [container]
255
+ image_name = "my-image-backend"
256
+ port = 8080
209
257
 
210
- print(result)
211
- {
212
- "production-job-manage": {...},
213
- "production-job-special-command": {...},
214
- "staging-job-manage": {...},
215
- "staging-job-special-command": {...},
216
- }
258
+ [container.env]
259
+ ENABLE_EXPENSIVE_MONITORING = false
217
260
  ```
218
261
 
219
- You can pass either `config` (TOML string or dict) or `config_file` (`pathlib.Path` or string path) to `combine()`. Additional `kwargs` restrict the output.
262
+ ```console
263
+ $ toml-combine example.toml --environment=dev --service=backend
264
+ registry = "gcr.io/my-project/"
265
+ service_account = "my-service-account"
266
+ name = "service-backend"
267
+ [dimensions]
268
+ environment = "dev"
269
+ service = "backend"
270
+
271
+ [container]
272
+ image_name = "my-image-backend"
273
+ port = 8080
274
+ [container.env]
275
+ DEBUG = true
276
+ ENABLE_EXPENSIVE_MONITORING = false
277
+
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=LLjBFNKz4sjwiyOM5hq9JaqvyNuogdzdyWJbGS5hpXs,5500
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.2.0.dist-info/METADATA,sha256=tuHxaP5V3DhE0unQt-XOnsL_TmCrNGP3mpxrSLuk30E,7777
9
+ toml_combine-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ toml_combine-0.2.0.dist-info/entry_points.txt,sha256=dXUQNom54uZt_7ylEG81iNYMamYpaFo9-ItcZJU6Uzc,58
11
+ toml_combine-0.2.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=98iWUnkbzDVGEkw-dXFZEt5G7io5SQzayceuTU2hRvo,7315
5
- toml_combine/exceptions.py,sha256=SepRFDxeWQEbD88jhF5g7laZSSULthho83BpW8u9RWs,897
6
- toml_combine/toml.py,sha256=_vCINvfJeS3gWid35Pmm3Yz4xyJ8LpKJRHL0axSU8nk,384
7
- toml_combine-0.1.7.dist-info/METADATA,sha256=vUsTB2Phm6t8GMpu28aPPsIxkSNOb0nBIFnQuKJSj_Y,6401
8
- toml_combine-0.1.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
- toml_combine-0.1.7.dist-info/entry_points.txt,sha256=dXUQNom54uZt_7ylEG81iNYMamYpaFo9-ItcZJU6Uzc,58
10
- toml_combine-0.1.7.dist-info/RECORD,,