toml-combine 0.1.4__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.
- toml_combine-0.1.4/.github/PULL_REQUEST_TEMPLATE.md +8 -0
- toml_combine-0.1.4/.github/renovate.json5 +16 -0
- toml_combine-0.1.4/.github/workflows/ci.yml +62 -0
- toml_combine-0.1.4/.github/workflows/coverage-comment.yml +21 -0
- toml_combine-0.1.4/.github/workflows/pr-labels.yml +42 -0
- toml_combine-0.1.4/.pre-commit-config.yaml +34 -0
- toml_combine-0.1.4/PKG-INFO +210 -0
- toml_combine-0.1.4/README.md +191 -0
- toml_combine-0.1.4/pyproject.toml +79 -0
- toml_combine-0.1.4/tests/result.json +192 -0
- toml_combine-0.1.4/tests/test.toml +134 -0
- toml_combine-0.1.4/tests/test_cli.py +15 -0
- toml_combine-0.1.4/tests/test_combiner.py +338 -0
- toml_combine-0.1.4/tests/test_lib.py +65 -0
- toml_combine-0.1.4/toml_combine/__init__.py +55 -0
- toml_combine-0.1.4/toml_combine/__main__.py +6 -0
- toml_combine-0.1.4/toml_combine/cli.py +86 -0
- toml_combine-0.1.4/toml_combine/combiner.py +249 -0
- toml_combine-0.1.4/toml_combine/exceptions.py +31 -0
- toml_combine-0.1.4/toml_combine/toml.py +17 -0
- toml_combine-0.1.4/uv.lock +122 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
{
|
2
|
+
extends: ["config:recommended", ":enablePreCommit"],
|
3
|
+
schedule: ["before 4am on Saturday"],
|
4
|
+
lockFileMaintenance: {
|
5
|
+
enabled: true,
|
6
|
+
automerge: true,
|
7
|
+
},
|
8
|
+
packageRules: [
|
9
|
+
{
|
10
|
+
groupName: "all dependencies",
|
11
|
+
groupSlug: "all",
|
12
|
+
matchPackageNames: ["*"],
|
13
|
+
},
|
14
|
+
],
|
15
|
+
automerge: true,
|
16
|
+
}
|
@@ -0,0 +1,62 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on:
|
4
|
+
pull_request:
|
5
|
+
push:
|
6
|
+
branches:
|
7
|
+
- "main"
|
8
|
+
tags:
|
9
|
+
- "*"
|
10
|
+
|
11
|
+
env:
|
12
|
+
UV_FROZEN: "true"
|
13
|
+
|
14
|
+
jobs:
|
15
|
+
tests:
|
16
|
+
strategy:
|
17
|
+
matrix:
|
18
|
+
python-version:
|
19
|
+
- "3.9"
|
20
|
+
- "3.10"
|
21
|
+
- "3.11"
|
22
|
+
- "3.12"
|
23
|
+
- "3.13"
|
24
|
+
|
25
|
+
name: "py${{ matrix.python-version }}"
|
26
|
+
runs-on: ubuntu-latest
|
27
|
+
|
28
|
+
steps:
|
29
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
30
|
+
|
31
|
+
- name: Install the latest version of uv
|
32
|
+
uses: astral-sh/setup-uv@f94ec6bedd8674c4426838e6b50417d36b6ab231 # v5
|
33
|
+
with:
|
34
|
+
python-version: ${{ matrix.python-version }}
|
35
|
+
|
36
|
+
- name: Run tests
|
37
|
+
run: uv run pytest
|
38
|
+
|
39
|
+
publish:
|
40
|
+
name: Publish package to PyPI
|
41
|
+
if: github.event_name == 'push' && github.ref_type == 'tag'
|
42
|
+
runs-on: ubuntu-latest
|
43
|
+
environment: release
|
44
|
+
permissions:
|
45
|
+
id-token: write
|
46
|
+
needs:
|
47
|
+
- tests
|
48
|
+
steps:
|
49
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
50
|
+
with:
|
51
|
+
fetch-depth: 1
|
52
|
+
fetch-tags: true
|
53
|
+
ref: ${{ github.ref }}
|
54
|
+
|
55
|
+
- name: Install the latest version of uv
|
56
|
+
uses: astral-sh/setup-uv@f94ec6bedd8674c4426838e6b50417d36b6ab231 # v5
|
57
|
+
|
58
|
+
- name: Build wheel & sdist
|
59
|
+
run: uv build
|
60
|
+
|
61
|
+
- name: Publish package distributions to PyPI
|
62
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
@@ -0,0 +1,21 @@
|
|
1
|
+
name: Post coverage comment
|
2
|
+
|
3
|
+
on:
|
4
|
+
workflow_run:
|
5
|
+
workflows: ["CI"]
|
6
|
+
types:
|
7
|
+
- completed
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
test:
|
11
|
+
name: Run tests & display coverage
|
12
|
+
runs-on: ubuntu-latest
|
13
|
+
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
|
14
|
+
steps:
|
15
|
+
# DO NOT run actions/checkout@v2 here, for securitity reasons
|
16
|
+
# For details, refer to https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
|
17
|
+
- name: Post comment
|
18
|
+
uses: ewjoachim/python-coverage-comment-action@v3
|
19
|
+
with:
|
20
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
21
|
+
GITHUB_PR_RUN_ID: ${{ github.event.workflow_run.id }}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
name: Label PR
|
2
|
+
|
3
|
+
on:
|
4
|
+
pull_request_target:
|
5
|
+
types:
|
6
|
+
- opened
|
7
|
+
- reopened
|
8
|
+
- edited
|
9
|
+
|
10
|
+
jobs:
|
11
|
+
label:
|
12
|
+
name: Label PR
|
13
|
+
runs-on: ubuntu-latest
|
14
|
+
permissions:
|
15
|
+
pull-requests: write
|
16
|
+
steps:
|
17
|
+
- run: |
|
18
|
+
export LABELS=$(gh repo view $REPO --json="labels" --jq='.labels[].name | select(startswith(env.PREFIX))')
|
19
|
+
python <(cat <<EOF
|
20
|
+
import sys, os
|
21
|
+
from urllib.parse import quote
|
22
|
+
url = f"https://github.com/{os.environ['REPO']}/labels/"
|
23
|
+
labels = os.environ['LABELS'].strip().splitlines()
|
24
|
+
prefix = os.environ['PREFIX']
|
25
|
+
checked = "- [x] "
|
26
|
+
unchecked = "- [ ] "
|
27
|
+
for line in os.environ["BODY"].splitlines():
|
28
|
+
for label in labels:
|
29
|
+
if label.startswith(prefix) and line.strip().endswith(url + quote(label)):
|
30
|
+
if line.strip().startswith(checked):
|
31
|
+
print(f"--add-label={label}")
|
32
|
+
elif line.strip().startswith(unchecked):
|
33
|
+
print(f"--remove-label={label}")
|
34
|
+
EOF
|
35
|
+
) | xargs --no-run-if-empty --delimiter '\n' gh pr --repo $REPO edit $NUMBER
|
36
|
+
|
37
|
+
env:
|
38
|
+
PREFIX: "PR type: "
|
39
|
+
REPO: procrastinate-org/procrastinate
|
40
|
+
BODY: ${{github.event.pull_request.body}}
|
41
|
+
NUMBER: ${{ github.event.pull_request.number }}
|
42
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
ci:
|
2
|
+
autoupdate_schedule: quarterly
|
3
|
+
repos:
|
4
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
5
|
+
rev: v5.0.0
|
6
|
+
hooks:
|
7
|
+
# File names
|
8
|
+
- id: check-case-conflict
|
9
|
+
# File formats
|
10
|
+
- id: pretty-format-json
|
11
|
+
- id: check-json
|
12
|
+
- id: check-toml
|
13
|
+
- id: check-yaml
|
14
|
+
# Git shenanigans
|
15
|
+
- id: check-merge-conflict
|
16
|
+
- id: check-added-large-files
|
17
|
+
# Python issues
|
18
|
+
- id: check-ast
|
19
|
+
- id: debug-statements
|
20
|
+
# Whitespace
|
21
|
+
- id: end-of-file-fixer
|
22
|
+
- id: trailing-whitespace
|
23
|
+
- id: mixed-line-ending
|
24
|
+
- repo: https://github.com/astral-sh/uv-pre-commit
|
25
|
+
# uv version.
|
26
|
+
rev: 0.6.12
|
27
|
+
hooks:
|
28
|
+
- id: uv-lock
|
29
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
30
|
+
rev: v0.11.0
|
31
|
+
hooks:
|
32
|
+
- id: ruff
|
33
|
+
args: [--fix, --unsafe-fixes, --show-fixes]
|
34
|
+
- id: ruff-format
|
@@ -0,0 +1,210 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: toml-combine
|
3
|
+
Version: 0.1.4
|
4
|
+
Summary: A tool for combining complex configurations in TOML format.
|
5
|
+
Author-email: Joachim Jablon <ewjoachim@gmail.com>
|
6
|
+
License-Expression: MIT
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
8
|
+
Classifier: Intended Audience :: Developers
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
16
|
+
Requires-Python: >=3.9
|
17
|
+
Requires-Dist: tomli>=2.2.1
|
18
|
+
Description-Content-Type: text/markdown
|
19
|
+
|
20
|
+
# Toml-combine
|
21
|
+
|
22
|
+
`toml-combine` is a Python lib and CLI-tool that reads a toml configuration defining
|
23
|
+
a default configuration and overrides with, and applies those overrides to get
|
24
|
+
final configurations. Say: you have multiple services, and environments, and you
|
25
|
+
want to describe them all without repeating the parts that are common to everyone.
|
26
|
+
|
27
|
+
## Concepts
|
28
|
+
|
29
|
+
### The config file
|
30
|
+
|
31
|
+
The configuration file is usually a TOML file. Here's a small example:
|
32
|
+
|
33
|
+
```toml
|
34
|
+
[dimensions]
|
35
|
+
environment = ["production", "staging"]
|
36
|
+
|
37
|
+
[[output]]
|
38
|
+
environment = "production"
|
39
|
+
|
40
|
+
[[output]]
|
41
|
+
environment = "staging"
|
42
|
+
|
43
|
+
[default]
|
44
|
+
name = "my-service"
|
45
|
+
registry = "gcr.io/my-project/"
|
46
|
+
container.image_name = "my-image"
|
47
|
+
container.port = 8080
|
48
|
+
service_account = "my-service-account"
|
49
|
+
|
50
|
+
[[override]]
|
51
|
+
when.environment = "staging"
|
52
|
+
service_account = "my-staging-service-account"
|
53
|
+
```
|
54
|
+
|
55
|
+
### Dimensions
|
56
|
+
|
57
|
+
Consider all the configurations you want to generate. Each one differs from the others.
|
58
|
+
Dimensions lets you describe the main "thing" that makes manifests differents, e.g.:
|
59
|
+
`environment` might be `staging` or `production`, region might be `eu` or `us`, and
|
60
|
+
service might be `frontend` or `backend`. Some combinations of dimensions might not
|
61
|
+
exists, for example, maybe there's no `staging` in `eu`.
|
62
|
+
|
63
|
+
### Outputs
|
64
|
+
|
65
|
+
Create a `output` for each configuration you want to generate, and specify the
|
66
|
+
dimensions relevant for this output. It's ok to omit some dimensions when they're not
|
67
|
+
used for a given output.
|
68
|
+
|
69
|
+
> [!Note]
|
70
|
+
> Defining a list as the value of one or more dimensions in a output
|
71
|
+
> is a shorthand for defining all combinations of dimensions
|
72
|
+
|
73
|
+
### Default
|
74
|
+
|
75
|
+
The common configuration to start from, before we start overlaying overrides on top.
|
76
|
+
|
77
|
+
### Overrides
|
78
|
+
|
79
|
+
Overrides define a set of condition where they apply (`when.<dimension> =
|
80
|
+
"<value>"`) and the values that are overriden. Overrides are applied in order from less
|
81
|
+
specific to more specific, each one overriding the values of the previous ones:
|
82
|
+
|
83
|
+
- If an override contains conditions on more dimensions than another one, it's applied
|
84
|
+
later
|
85
|
+
- In case 2 overrides contain the same number of dimensions and they're a disjoint set,
|
86
|
+
then it depends on how the dimensions are defined at the top of the file: dimensions
|
87
|
+
defined last have a greater priority
|
88
|
+
|
89
|
+
> [!Note]
|
90
|
+
> Defining a list as the value of one or more conditions in an override
|
91
|
+
> means that the override will apply to any of the dimension values of the list
|
92
|
+
|
93
|
+
### The configuration itself
|
94
|
+
|
95
|
+
Under the layer of `dimensions/output/default/override` system, what you actually define
|
96
|
+
in the configuration is completely up to you. That said, only nested
|
97
|
+
"dictionnaries"/"objects"/"tables"/"mapping" (those are all the same things in
|
98
|
+
Python/JS/Toml lingo) will be merged between the default and the overrides, while
|
99
|
+
arrays will just replace one another. See `Arrays` below.
|
100
|
+
|
101
|
+
In the generated configuration, the dimensions of the output will appear in the generated
|
102
|
+
object as an object under the `dimensions` key.
|
103
|
+
|
104
|
+
### Arrays
|
105
|
+
|
106
|
+
Let's look at an example:
|
107
|
+
|
108
|
+
```toml
|
109
|
+
[dimensions]
|
110
|
+
environment = ["production", "staging"]
|
111
|
+
|
112
|
+
[[output]]
|
113
|
+
environment = ["production", "staging"]
|
114
|
+
|
115
|
+
[default]
|
116
|
+
fruits = [{name="apple", color="red"}]
|
117
|
+
|
118
|
+
[[override]]
|
119
|
+
when.environment = "staging"
|
120
|
+
fruits = [{name="orange", color="orange"}]
|
121
|
+
```
|
122
|
+
|
123
|
+
In this example, on staging, `fruits` is `[{name="orange", color="orange"}]` and not `[{name="apple", color="red"}, {name="orange", color="orange"}]`.
|
124
|
+
The only way to get multiple values to be merged is if they are tables: you'll need
|
125
|
+
to chose an element to become the key:
|
126
|
+
|
127
|
+
```toml
|
128
|
+
[dimensions]
|
129
|
+
environment = ["production", "staging"]
|
130
|
+
|
131
|
+
[[output]]
|
132
|
+
environment = ["production", "staging"]
|
133
|
+
|
134
|
+
[default]
|
135
|
+
fruits.apple.color = "red"
|
136
|
+
|
137
|
+
[[override]]
|
138
|
+
when.environment = "staging"
|
139
|
+
fruits.orange.color = "orange"
|
140
|
+
```
|
141
|
+
|
142
|
+
In this example, on staging, `fruits` is `{apple={color="red"}, orange={color="orange"}}`.
|
143
|
+
|
144
|
+
This example is simple because `name` is a natural choice for the key. In some cases,
|
145
|
+
the choice is less natural, but you can always decide to name the elements of your
|
146
|
+
list and use that name as a key. Also, yes, you'll loose ordering.
|
147
|
+
|
148
|
+
### A bigger example
|
149
|
+
|
150
|
+
```toml
|
151
|
+
[dimensions]
|
152
|
+
environment = ["production", "staging", "dev"]
|
153
|
+
service = ["frontend", "backend"]
|
154
|
+
|
155
|
+
# All 4 combinations of those values will exist
|
156
|
+
[[output]]
|
157
|
+
environment = ["production", "staging"]
|
158
|
+
service = ["frontend", "backend"]
|
159
|
+
|
160
|
+
# On dev, the "service" is not defined. That's ok.
|
161
|
+
[[output]]
|
162
|
+
environment = "dev"
|
163
|
+
|
164
|
+
[default]
|
165
|
+
registry = "gcr.io/my-project/"
|
166
|
+
service_account = "my-service-account"
|
167
|
+
|
168
|
+
[[override]]
|
169
|
+
when.service = "frontend"
|
170
|
+
name = "service-frontend"
|
171
|
+
container.image_name = "my-image-frontend"
|
172
|
+
|
173
|
+
[[override]]
|
174
|
+
when.service = "backend"
|
175
|
+
name = "service-backend"
|
176
|
+
container.image_name = "my-image-backend"
|
177
|
+
container.port = 8080
|
178
|
+
|
179
|
+
[[override]]
|
180
|
+
name = "service-dev"
|
181
|
+
when.environment = "dev"
|
182
|
+
container.env.DEBUG = true
|
183
|
+
```
|
184
|
+
|
185
|
+
### CLI
|
186
|
+
|
187
|
+
```console
|
188
|
+
$ toml-combine {path/to/config.toml}
|
189
|
+
```
|
190
|
+
|
191
|
+
Generates all the outputs described by the given TOML config.
|
192
|
+
|
193
|
+
Note that you can restrict generation to some dimension values by passing
|
194
|
+
`--{dimension}={value}`
|
195
|
+
|
196
|
+
## Lib
|
197
|
+
|
198
|
+
```python
|
199
|
+
import toml_combine
|
200
|
+
|
201
|
+
|
202
|
+
result = toml_combine.combine(
|
203
|
+
config_file=config_file,
|
204
|
+
environment=["production", "staging"],
|
205
|
+
type="job",
|
206
|
+
job=["manage", "special-command"],
|
207
|
+
)
|
208
|
+
|
209
|
+
print(result)
|
210
|
+
```
|
@@ -0,0 +1,191 @@
|
|
1
|
+
# Toml-combine
|
2
|
+
|
3
|
+
`toml-combine` is a Python lib and CLI-tool that reads a toml configuration defining
|
4
|
+
a default configuration and overrides with, and applies those overrides to get
|
5
|
+
final configurations. Say: you have multiple services, and environments, and you
|
6
|
+
want to describe them all without repeating the parts that are common to everyone.
|
7
|
+
|
8
|
+
## Concepts
|
9
|
+
|
10
|
+
### The config file
|
11
|
+
|
12
|
+
The configuration file is usually a TOML file. Here's a small example:
|
13
|
+
|
14
|
+
```toml
|
15
|
+
[dimensions]
|
16
|
+
environment = ["production", "staging"]
|
17
|
+
|
18
|
+
[[output]]
|
19
|
+
environment = "production"
|
20
|
+
|
21
|
+
[[output]]
|
22
|
+
environment = "staging"
|
23
|
+
|
24
|
+
[default]
|
25
|
+
name = "my-service"
|
26
|
+
registry = "gcr.io/my-project/"
|
27
|
+
container.image_name = "my-image"
|
28
|
+
container.port = 8080
|
29
|
+
service_account = "my-service-account"
|
30
|
+
|
31
|
+
[[override]]
|
32
|
+
when.environment = "staging"
|
33
|
+
service_account = "my-staging-service-account"
|
34
|
+
```
|
35
|
+
|
36
|
+
### Dimensions
|
37
|
+
|
38
|
+
Consider all the configurations you want to generate. Each one differs from the others.
|
39
|
+
Dimensions lets you describe the main "thing" that makes manifests differents, e.g.:
|
40
|
+
`environment` might be `staging` or `production`, region might be `eu` or `us`, and
|
41
|
+
service might be `frontend` or `backend`. Some combinations of dimensions might not
|
42
|
+
exists, for example, maybe there's no `staging` in `eu`.
|
43
|
+
|
44
|
+
### Outputs
|
45
|
+
|
46
|
+
Create a `output` for each configuration you want to generate, and specify the
|
47
|
+
dimensions relevant for this output. It's ok to omit some dimensions when they're not
|
48
|
+
used for a given output.
|
49
|
+
|
50
|
+
> [!Note]
|
51
|
+
> Defining a list as the value of one or more dimensions in a output
|
52
|
+
> is a shorthand for defining all combinations of dimensions
|
53
|
+
|
54
|
+
### Default
|
55
|
+
|
56
|
+
The common configuration to start from, before we start overlaying overrides on top.
|
57
|
+
|
58
|
+
### Overrides
|
59
|
+
|
60
|
+
Overrides define a set of condition where they apply (`when.<dimension> =
|
61
|
+
"<value>"`) and the values that are overriden. Overrides are applied in order from less
|
62
|
+
specific to more specific, each one overriding the values of the previous ones:
|
63
|
+
|
64
|
+
- If an override contains conditions on more dimensions than another one, it's applied
|
65
|
+
later
|
66
|
+
- In case 2 overrides contain the same number of dimensions and they're a disjoint set,
|
67
|
+
then it depends on how the dimensions are defined at the top of the file: dimensions
|
68
|
+
defined last have a greater priority
|
69
|
+
|
70
|
+
> [!Note]
|
71
|
+
> Defining a list as the value of one or more conditions in an override
|
72
|
+
> means that the override will apply to any of the dimension values of the list
|
73
|
+
|
74
|
+
### The configuration itself
|
75
|
+
|
76
|
+
Under the layer of `dimensions/output/default/override` system, what you actually define
|
77
|
+
in the configuration is completely up to you. That said, only nested
|
78
|
+
"dictionnaries"/"objects"/"tables"/"mapping" (those are all the same things in
|
79
|
+
Python/JS/Toml lingo) will be merged between the default and the overrides, while
|
80
|
+
arrays will just replace one another. See `Arrays` below.
|
81
|
+
|
82
|
+
In the generated configuration, the dimensions of the output will appear in the generated
|
83
|
+
object as an object under the `dimensions` key.
|
84
|
+
|
85
|
+
### Arrays
|
86
|
+
|
87
|
+
Let's look at an example:
|
88
|
+
|
89
|
+
```toml
|
90
|
+
[dimensions]
|
91
|
+
environment = ["production", "staging"]
|
92
|
+
|
93
|
+
[[output]]
|
94
|
+
environment = ["production", "staging"]
|
95
|
+
|
96
|
+
[default]
|
97
|
+
fruits = [{name="apple", color="red"}]
|
98
|
+
|
99
|
+
[[override]]
|
100
|
+
when.environment = "staging"
|
101
|
+
fruits = [{name="orange", color="orange"}]
|
102
|
+
```
|
103
|
+
|
104
|
+
In this example, on staging, `fruits` is `[{name="orange", color="orange"}]` and not `[{name="apple", color="red"}, {name="orange", color="orange"}]`.
|
105
|
+
The only way to get multiple values to be merged is if they are tables: you'll need
|
106
|
+
to chose an element to become the key:
|
107
|
+
|
108
|
+
```toml
|
109
|
+
[dimensions]
|
110
|
+
environment = ["production", "staging"]
|
111
|
+
|
112
|
+
[[output]]
|
113
|
+
environment = ["production", "staging"]
|
114
|
+
|
115
|
+
[default]
|
116
|
+
fruits.apple.color = "red"
|
117
|
+
|
118
|
+
[[override]]
|
119
|
+
when.environment = "staging"
|
120
|
+
fruits.orange.color = "orange"
|
121
|
+
```
|
122
|
+
|
123
|
+
In this example, on staging, `fruits` is `{apple={color="red"}, orange={color="orange"}}`.
|
124
|
+
|
125
|
+
This example is simple because `name` is a natural choice for the key. In some cases,
|
126
|
+
the choice is less natural, but you can always decide to name the elements of your
|
127
|
+
list and use that name as a key. Also, yes, you'll loose ordering.
|
128
|
+
|
129
|
+
### A bigger example
|
130
|
+
|
131
|
+
```toml
|
132
|
+
[dimensions]
|
133
|
+
environment = ["production", "staging", "dev"]
|
134
|
+
service = ["frontend", "backend"]
|
135
|
+
|
136
|
+
# All 4 combinations of those values will exist
|
137
|
+
[[output]]
|
138
|
+
environment = ["production", "staging"]
|
139
|
+
service = ["frontend", "backend"]
|
140
|
+
|
141
|
+
# On dev, the "service" is not defined. That's ok.
|
142
|
+
[[output]]
|
143
|
+
environment = "dev"
|
144
|
+
|
145
|
+
[default]
|
146
|
+
registry = "gcr.io/my-project/"
|
147
|
+
service_account = "my-service-account"
|
148
|
+
|
149
|
+
[[override]]
|
150
|
+
when.service = "frontend"
|
151
|
+
name = "service-frontend"
|
152
|
+
container.image_name = "my-image-frontend"
|
153
|
+
|
154
|
+
[[override]]
|
155
|
+
when.service = "backend"
|
156
|
+
name = "service-backend"
|
157
|
+
container.image_name = "my-image-backend"
|
158
|
+
container.port = 8080
|
159
|
+
|
160
|
+
[[override]]
|
161
|
+
name = "service-dev"
|
162
|
+
when.environment = "dev"
|
163
|
+
container.env.DEBUG = true
|
164
|
+
```
|
165
|
+
|
166
|
+
### CLI
|
167
|
+
|
168
|
+
```console
|
169
|
+
$ toml-combine {path/to/config.toml}
|
170
|
+
```
|
171
|
+
|
172
|
+
Generates all the outputs described by the given TOML config.
|
173
|
+
|
174
|
+
Note that you can restrict generation to some dimension values by passing
|
175
|
+
`--{dimension}={value}`
|
176
|
+
|
177
|
+
## Lib
|
178
|
+
|
179
|
+
```python
|
180
|
+
import toml_combine
|
181
|
+
|
182
|
+
|
183
|
+
result = toml_combine.combine(
|
184
|
+
config_file=config_file,
|
185
|
+
environment=["production", "staging"],
|
186
|
+
type="job",
|
187
|
+
job=["manage", "special-command"],
|
188
|
+
)
|
189
|
+
|
190
|
+
print(result)
|
191
|
+
```
|
@@ -0,0 +1,79 @@
|
|
1
|
+
[build-system]
|
2
|
+
requires = ["hatchling", "uv-dynamic-versioning"]
|
3
|
+
build-backend = "hatchling.build"
|
4
|
+
|
5
|
+
|
6
|
+
[tool.uv-dynamic-versioning]
|
7
|
+
pattern = "default-unprefixed"
|
8
|
+
|
9
|
+
[tool.hatch.version]
|
10
|
+
source = "uv-dynamic-versioning"
|
11
|
+
|
12
|
+
[tool.hatch.build.targets.sdist]
|
13
|
+
|
14
|
+
[tool.hatch.build.targets.wheel]
|
15
|
+
|
16
|
+
[project]
|
17
|
+
name = "toml-combine"
|
18
|
+
dynamic = ["version"]
|
19
|
+
description = "A tool for combining complex configurations in TOML format."
|
20
|
+
readme = "README.md"
|
21
|
+
requires-python = ">=3.9"
|
22
|
+
license = "MIT"
|
23
|
+
authors = [{ name = "Joachim Jablon", email = "ewjoachim@gmail.com" }]
|
24
|
+
|
25
|
+
classifiers = [
|
26
|
+
"Development Status :: 4 - Beta",
|
27
|
+
"Intended Audience :: Developers",
|
28
|
+
"License :: OSI Approved :: MIT License",
|
29
|
+
"Programming Language :: Python :: 3",
|
30
|
+
"Programming Language :: Python :: 3.9",
|
31
|
+
"Programming Language :: Python :: 3.10",
|
32
|
+
"Programming Language :: Python :: 3.11",
|
33
|
+
"Programming Language :: Python :: 3.12",
|
34
|
+
"Programming Language :: Python :: 3.13",
|
35
|
+
]
|
36
|
+
|
37
|
+
dependencies = ["tomli>=2.2.1"]
|
38
|
+
|
39
|
+
[tool.uv]
|
40
|
+
default-groups = ["test"]
|
41
|
+
|
42
|
+
[dependency-groups]
|
43
|
+
test = ["pytest>=8.3.5"]
|
44
|
+
|
45
|
+
[project.scripts]
|
46
|
+
toml-combine = "toml_combine.cli:run_cli"
|
47
|
+
|
48
|
+
[tool.pytest.ini_options]
|
49
|
+
addopts = ["-vv", "--strict-markers", "-rfE"]
|
50
|
+
|
51
|
+
[tool.ruff]
|
52
|
+
unsafe-fixes = true
|
53
|
+
|
54
|
+
[tool.ruff.lint]
|
55
|
+
# Enable ruff features:
|
56
|
+
# E => pycodestyle (errors), enabled in ruff default configuration
|
57
|
+
# W => pycodestyle (warnings)
|
58
|
+
# F => pyflakes, enabled in ruff default configuration
|
59
|
+
# I => isort
|
60
|
+
# UP => pyupgrade
|
61
|
+
# PL => pylint
|
62
|
+
select = ["UP", "E", "F", "W", "PL", "I", "TID"]
|
63
|
+
ignore = [
|
64
|
+
"E501", # "Line too long"
|
65
|
+
"E402", # "Module level import not at top of file"
|
66
|
+
"PLR2004", # "Magic value used in comparison"
|
67
|
+
"PLR0913", # "Too many arguments to function call"
|
68
|
+
"PLR0911", # "Too many return statements"
|
69
|
+
"PLR0912", # "Too many branches"
|
70
|
+
"PLR0915", # "Too many statements"
|
71
|
+
"PLW2901", # "Redefined loop name"
|
72
|
+
"PLR5501", # "Use elif instead of if"
|
73
|
+
"PLW0603", # "Global statement"
|
74
|
+
"PLW1508", # "Invalid-envvar-default"
|
75
|
+
]
|
76
|
+
|
77
|
+
# Ruff isort specific options
|
78
|
+
[tool.ruff.lint.isort]
|
79
|
+
required-imports = ["from __future__ import annotations"]
|