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.
@@ -0,0 +1,8 @@
1
+ Closes #<ticket number>
2
+
3
+ ### Successful PR Checklist:
4
+
5
+ - [ ] Tests
6
+ - [ ] (not applicable?)
7
+ - [ ] Documentation
8
+ - [ ] (not applicable?)
@@ -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"]