conclude 1.0.1__tar.gz → 1.0.2__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.
- {conclude-1.0.1 → conclude-1.0.2}/.gitignore +6 -0
- {conclude-1.0.1 → conclude-1.0.2}/CHANGELOG.md +6 -0
- {conclude-1.0.1 → conclude-1.0.2}/PKG-INFO +16 -16
- {conclude-1.0.1 → conclude-1.0.2}/README.md +13 -13
- conclude-1.0.2/RELEASING.md +56 -0
- {conclude-1.0.1 → conclude-1.0.2}/docs/comparison.md +1 -1
- conclude-1.0.2/docs/concept.md +401 -0
- conclude-1.0.2/hatch_build.py +33 -0
- {conclude-1.0.1 → conclude-1.0.2}/pyproject.toml +21 -4
- conclude-1.0.2/spec/README.md +72 -0
- conclude-1.0.2/spec/casters.json +438 -0
- conclude-1.0.2/spec/config_layers.json +571 -0
- conclude-1.0.2/spec/config_tables.json +221 -0
- conclude-1.0.2/spec/dotenv.json +239 -0
- conclude-1.0.2/spec/gitignore.json +189 -0
- conclude-1.0.2/spec/guard.json +273 -0
- conclude-1.0.2/spec/invocation.json +1241 -0
- conclude-1.0.2/spec/merge.json +1076 -0
- conclude-1.0.2/spec/naming.json +94 -0
- conclude-1.0.2/spec/sources.json +315 -0
- conclude-1.0.2/spec/templates.json +603 -0
- {conclude-1.0.1 → conclude-1.0.2}/src/conclude/__init__.py +1 -1
- {conclude-1.0.1/tests → conclude-1.0.2/test}/test_comparison.py +15 -7
- {conclude-1.0.1/tests → conclude-1.0.2/test}/test_docs.py +22 -7
- conclude-1.0.2/test/test_repo_hygiene.py +413 -0
- conclude-1.0.2/test/test_spec.py +473 -0
- conclude-1.0.1/tests/test_repo_hygiene.py +0 -208
- {conclude-1.0.1 → conclude-1.0.2}/LICENSE +0 -0
- {conclude-1.0.1 → conclude-1.0.2}/docs/guide.md +0 -0
- {conclude-1.0.1 → conclude-1.0.2}/docs/reference.md +0 -0
- {conclude-1.0.1 → conclude-1.0.2}/src/conclude/app.py +0 -0
- {conclude-1.0.1 → conclude-1.0.2}/src/conclude/casters.py +0 -0
- {conclude-1.0.1 → conclude-1.0.2}/src/conclude/developer.py +0 -0
- {conclude-1.0.1 → conclude-1.0.2}/src/conclude/env.py +0 -0
- {conclude-1.0.1 → conclude-1.0.2}/src/conclude/files.py +0 -0
- {conclude-1.0.1 → conclude-1.0.2}/src/conclude/formatters.py +0 -0
- {conclude-1.0.1 → conclude-1.0.2}/src/conclude/guard.py +0 -0
- {conclude-1.0.1 → conclude-1.0.2}/src/conclude/infer.py +0 -0
- {conclude-1.0.1 → conclude-1.0.2}/src/conclude/merge.py +0 -0
- {conclude-1.0.1 → conclude-1.0.2}/src/conclude/naming.py +0 -0
- {conclude-1.0.1 → conclude-1.0.2}/src/conclude/paths.py +0 -0
- {conclude-1.0.1 → conclude-1.0.2}/src/conclude/py.typed +0 -0
- {conclude-1.0.1 → conclude-1.0.2}/src/conclude/templates.py +0 -0
- {conclude-1.0.1 → conclude-1.0.2}/src/conclude/tomlwrite.py +0 -0
- {conclude-1.0.1/tests → conclude-1.0.2/test}/test_app.py +0 -0
- {conclude-1.0.1/tests → conclude-1.0.2/test}/test_casters.py +0 -0
- {conclude-1.0.1/tests → conclude-1.0.2/test}/test_developer.py +0 -0
- {conclude-1.0.1/tests → conclude-1.0.2/test}/test_dotenv_guard.py +0 -0
- {conclude-1.0.1/tests → conclude-1.0.2/test}/test_env.py +0 -0
- {conclude-1.0.1/tests → conclude-1.0.2/test}/test_files.py +0 -0
- {conclude-1.0.1/tests → conclude-1.0.2/test}/test_formatters.py +0 -0
- {conclude-1.0.1/tests → conclude-1.0.2/test}/test_guard.py +0 -0
- {conclude-1.0.1/tests → conclude-1.0.2/test}/test_infer.py +0 -0
- {conclude-1.0.1/tests → conclude-1.0.2/test}/test_merge.py +0 -0
- {conclude-1.0.1/tests → conclude-1.0.2/test}/test_naming.py +0 -0
- {conclude-1.0.1/tests → conclude-1.0.2/test}/test_templates.py +0 -0
- {conclude-1.0.1/tests → conclude-1.0.2/test}/test_tomlwrite_and_paths.py +0 -0
|
@@ -4,6 +4,12 @@ All notable changes to this project are documented in this file. The
|
|
|
4
4
|
format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
5
|
and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [1.0.2] - 2026-09-26
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Python tree is now under `python/` to match `node/` tree.
|
|
12
|
+
|
|
7
13
|
## [1.0.1] - 2026-09-20
|
|
8
14
|
|
|
9
15
|
### Fixed
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: conclude
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Set up your defaults; conclude infers the env vars, config-file keys, and CLI flags, and resolves them all into one settings object.
|
|
5
5
|
Project-URL: Homepage, https://github.com/tanakapayam/conclude
|
|
6
|
-
Project-URL: Documentation, https://github.com/tanakapayam/conclude/blob/main/docs/guide.md
|
|
6
|
+
Project-URL: Documentation, https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md
|
|
7
7
|
Project-URL: Repository, https://github.com/tanakapayam/conclude
|
|
8
8
|
Project-URL: Issues, https://github.com/tanakapayam/conclude/issues
|
|
9
|
-
Project-URL: Changelog, https://github.com/tanakapayam/conclude/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Changelog, https://github.com/tanakapayam/conclude/blob/main/python/CHANGELOG.md
|
|
10
10
|
Author: Payam Tanaka
|
|
11
11
|
License-Expression: MIT
|
|
12
12
|
License-File: LICENSE
|
|
@@ -92,7 +92,7 @@ conclude deliberately does not do schema validation (values are cast,
|
|
|
92
92
|
not validated), secret-manager backends, YAML or JSON files, or named
|
|
93
93
|
environments; the table says where to look for those. For a dated,
|
|
94
94
|
feature-by-feature comparison, see
|
|
95
|
-
[How conclude compares](https://github.com/tanakapayam/conclude/blob/main/docs/comparison.md).
|
|
95
|
+
[How conclude compares](https://github.com/tanakapayam/conclude/blob/main/docs/python/comparison.md).
|
|
96
96
|
|
|
97
97
|
## Design principles
|
|
98
98
|
|
|
@@ -195,28 +195,28 @@ app = conclude.App(
|
|
|
195
195
|
The developer file is always guarded, and `.env` is when you ask: the
|
|
196
196
|
file is used only if it exists, sits in a git working tree, and is
|
|
197
197
|
gitignored. Otherwise it is skipped quietly, and `describe_sources()`
|
|
198
|
-
says why. Details: [the `.env` file](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#6-local-development-a-env-file), [the developer file](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#8-a-private-developer-config-file).
|
|
198
|
+
says why. Details: [the `.env` file](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#6-local-development-a-env-file), [the developer file](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#8-a-private-developer-config-file).
|
|
199
199
|
|
|
200
200
|
## What's in it
|
|
201
201
|
|
|
202
202
|
- **Inference** of casters, formatters, CLI flags, env var names and
|
|
203
203
|
config keys from the defaults alone; `opt(type)` for a setting that
|
|
204
|
-
starts out unset ([guide, section 1](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#1-the-bare-minimum), [section 3](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#3-when-inference-isnt-quite-enough-casters-and-formatters)).
|
|
204
|
+
starts out unset ([guide, section 1](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#1-the-bare-minimum), [section 3](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#3-when-inference-isnt-quite-enough-casters-and-formatters)).
|
|
205
205
|
- **Config-file tables**, including a positional shorthand that picks a
|
|
206
|
-
table per recipient/deck/profile ([section 4](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#4-a-positional-shorthand--per-recipient-config-tables)).
|
|
206
|
+
table per recipient/deck/profile ([section 4](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#4-a-positional-shorthand--per-recipient-config-tables)).
|
|
207
207
|
- **`--print-invocation`** and `App.format_invocation()`: print the
|
|
208
208
|
command line that reproduces a resolved configuration
|
|
209
|
-
([section 5](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#5-debugging-and-documentation---print-invocation)).
|
|
209
|
+
([section 5](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#5-debugging-and-documentation---print-invocation)).
|
|
210
210
|
- **A `.env` fallback**, optionally required to be gitignored, sitting
|
|
211
|
-
beneath real environment variables ([section 6](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#6-local-development-a-env-file)).
|
|
212
|
-
- **A system-wide config file**, the lowest-priority file ([section 7](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#7-machine-wide-defaults-a-system-config-file)).
|
|
211
|
+
beneath real environment variables ([section 6](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#6-local-development-a-env-file)).
|
|
212
|
+
- **A system-wide config file**, the lowest-priority file ([section 7](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#7-machine-wide-defaults-a-system-config-file)).
|
|
213
213
|
- **A private developer file**, TOML or dotenv, that beats ambient
|
|
214
|
-
environment variables ([section 8](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#8-a-private-developer-config-file)).
|
|
214
|
+
environment variables ([section 8](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#8-a-private-developer-config-file)).
|
|
215
215
|
- **`describe_sources()`** for `--help`: which sources are in play, and
|
|
216
|
-
why a given one isn't ([section 9](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#9-turning-off-a-source-and-telling-the-user)).
|
|
216
|
+
why a given one isn't ([section 9](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#9-turning-off-a-source-and-telling-the-user)).
|
|
217
217
|
- **Templates and docs generated from your defaults**:
|
|
218
218
|
`format_env()`, `format_toml()`, `format_cli()`
|
|
219
|
-
([section 10](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#10-generating-docs-and-templates)).
|
|
219
|
+
([section 10](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#10-generating-docs-and-templates)).
|
|
220
220
|
- Standard library only, fully typed (`py.typed`), Python 3.11+. The
|
|
221
221
|
gitignore check (developer file, strict `.env`) uses the optional
|
|
222
222
|
`pathspec` package.
|
|
@@ -237,11 +237,11 @@ pip install 'conclude[gitignore]'
|
|
|
237
237
|
|
|
238
238
|
## Documentation
|
|
239
239
|
|
|
240
|
-
- [Guide](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md) -- builds a small CLI, `remind`, one
|
|
240
|
+
- [Guide](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md) -- builds a small CLI, `remind`, one
|
|
241
241
|
idea at a time.
|
|
242
|
-
- [Reference](https://github.com/tanakapayam/conclude/blob/main/docs/reference.md) -- every class, method, and
|
|
242
|
+
- [Reference](https://github.com/tanakapayam/conclude/blob/main/docs/python/reference.md) -- every class, method, and
|
|
243
243
|
module.
|
|
244
|
-
- [Changelog](https://github.com/tanakapayam/conclude/blob/main/CHANGELOG.md).
|
|
244
|
+
- [Changelog](https://github.com/tanakapayam/conclude/blob/main/python/CHANGELOG.md).
|
|
245
245
|
|
|
246
246
|
## Development
|
|
247
247
|
|
|
@@ -61,7 +61,7 @@ conclude deliberately does not do schema validation (values are cast,
|
|
|
61
61
|
not validated), secret-manager backends, YAML or JSON files, or named
|
|
62
62
|
environments; the table says where to look for those. For a dated,
|
|
63
63
|
feature-by-feature comparison, see
|
|
64
|
-
[How conclude compares](https://github.com/tanakapayam/conclude/blob/main/docs/comparison.md).
|
|
64
|
+
[How conclude compares](https://github.com/tanakapayam/conclude/blob/main/docs/python/comparison.md).
|
|
65
65
|
|
|
66
66
|
## Design principles
|
|
67
67
|
|
|
@@ -164,28 +164,28 @@ app = conclude.App(
|
|
|
164
164
|
The developer file is always guarded, and `.env` is when you ask: the
|
|
165
165
|
file is used only if it exists, sits in a git working tree, and is
|
|
166
166
|
gitignored. Otherwise it is skipped quietly, and `describe_sources()`
|
|
167
|
-
says why. Details: [the `.env` file](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#6-local-development-a-env-file), [the developer file](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#8-a-private-developer-config-file).
|
|
167
|
+
says why. Details: [the `.env` file](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#6-local-development-a-env-file), [the developer file](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#8-a-private-developer-config-file).
|
|
168
168
|
|
|
169
169
|
## What's in it
|
|
170
170
|
|
|
171
171
|
- **Inference** of casters, formatters, CLI flags, env var names and
|
|
172
172
|
config keys from the defaults alone; `opt(type)` for a setting that
|
|
173
|
-
starts out unset ([guide, section 1](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#1-the-bare-minimum), [section 3](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#3-when-inference-isnt-quite-enough-casters-and-formatters)).
|
|
173
|
+
starts out unset ([guide, section 1](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#1-the-bare-minimum), [section 3](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#3-when-inference-isnt-quite-enough-casters-and-formatters)).
|
|
174
174
|
- **Config-file tables**, including a positional shorthand that picks a
|
|
175
|
-
table per recipient/deck/profile ([section 4](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#4-a-positional-shorthand--per-recipient-config-tables)).
|
|
175
|
+
table per recipient/deck/profile ([section 4](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#4-a-positional-shorthand--per-recipient-config-tables)).
|
|
176
176
|
- **`--print-invocation`** and `App.format_invocation()`: print the
|
|
177
177
|
command line that reproduces a resolved configuration
|
|
178
|
-
([section 5](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#5-debugging-and-documentation---print-invocation)).
|
|
178
|
+
([section 5](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#5-debugging-and-documentation---print-invocation)).
|
|
179
179
|
- **A `.env` fallback**, optionally required to be gitignored, sitting
|
|
180
|
-
beneath real environment variables ([section 6](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#6-local-development-a-env-file)).
|
|
181
|
-
- **A system-wide config file**, the lowest-priority file ([section 7](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#7-machine-wide-defaults-a-system-config-file)).
|
|
180
|
+
beneath real environment variables ([section 6](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#6-local-development-a-env-file)).
|
|
181
|
+
- **A system-wide config file**, the lowest-priority file ([section 7](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#7-machine-wide-defaults-a-system-config-file)).
|
|
182
182
|
- **A private developer file**, TOML or dotenv, that beats ambient
|
|
183
|
-
environment variables ([section 8](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#8-a-private-developer-config-file)).
|
|
183
|
+
environment variables ([section 8](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#8-a-private-developer-config-file)).
|
|
184
184
|
- **`describe_sources()`** for `--help`: which sources are in play, and
|
|
185
|
-
why a given one isn't ([section 9](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#9-turning-off-a-source-and-telling-the-user)).
|
|
185
|
+
why a given one isn't ([section 9](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#9-turning-off-a-source-and-telling-the-user)).
|
|
186
186
|
- **Templates and docs generated from your defaults**:
|
|
187
187
|
`format_env()`, `format_toml()`, `format_cli()`
|
|
188
|
-
([section 10](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md#10-generating-docs-and-templates)).
|
|
188
|
+
([section 10](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md#10-generating-docs-and-templates)).
|
|
189
189
|
- Standard library only, fully typed (`py.typed`), Python 3.11+. The
|
|
190
190
|
gitignore check (developer file, strict `.env`) uses the optional
|
|
191
191
|
`pathspec` package.
|
|
@@ -206,11 +206,11 @@ pip install 'conclude[gitignore]'
|
|
|
206
206
|
|
|
207
207
|
## Documentation
|
|
208
208
|
|
|
209
|
-
- [Guide](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md) -- builds a small CLI, `remind`, one
|
|
209
|
+
- [Guide](https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md) -- builds a small CLI, `remind`, one
|
|
210
210
|
idea at a time.
|
|
211
|
-
- [Reference](https://github.com/tanakapayam/conclude/blob/main/docs/reference.md) -- every class, method, and
|
|
211
|
+
- [Reference](https://github.com/tanakapayam/conclude/blob/main/docs/python/reference.md) -- every class, method, and
|
|
212
212
|
module.
|
|
213
|
-
- [Changelog](https://github.com/tanakapayam/conclude/blob/main/CHANGELOG.md).
|
|
213
|
+
- [Changelog](https://github.com/tanakapayam/conclude/blob/main/python/CHANGELOG.md).
|
|
214
214
|
|
|
215
215
|
## Development
|
|
216
216
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Releasing conclude (Python)
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
release published (tag python-v<version>)
|
|
5
|
+
|
|
|
6
|
+
v
|
|
7
|
+
ci ---> build ---> publish-testpypi (workflow_dispatch, target testpypi: a dry run)
|
|
8
|
+
| |
|
|
9
|
+
| +-> publish-pypi waits for a reviewer to approve the `pypi`
|
|
10
|
+
| environment, then publishes with trusted
|
|
11
|
+
| publishing (no stored token)
|
|
12
|
+
|
|
|
13
|
+
+-- one sdist + wheel, built once, uploaded as an artifact and promoted unchanged
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
TestPyPI is the dry run here (there is no separate staging registry, unlike the Node
|
|
17
|
+
package's GitHub Packages stage): `workflow_dispatch` with `target: testpypi` builds,
|
|
18
|
+
checks, and publishes to TestPyPI, and installs nothing back automatically -- try it
|
|
19
|
+
yourself with `pip install -i https://test.pypi.org/simple/ conclude`. A real release
|
|
20
|
+
(`target: pypi`, or a published GitHub Release) is checked, gated behind a required
|
|
21
|
+
reviewer on the `pypi` environment, then published to PyPI.
|
|
22
|
+
|
|
23
|
+
## One-time setup
|
|
24
|
+
|
|
25
|
+
1. **Environments** (repository Settings, Environments):
|
|
26
|
+
- `testpypi`: no protection needed.
|
|
27
|
+
- `pypi`: add yourself (or a team) under *Required reviewers*; that is the approval
|
|
28
|
+
gate.
|
|
29
|
+
2. **Trusted publisher**, on both <https://pypi.org> and <https://test.pypi.org>, for the
|
|
30
|
+
project `conclude`: this repository, workflow **`python-publish.yml`**, environment
|
|
31
|
+
`pypi` (`testpypi` on TestPyPI). For a project that does not exist yet, use
|
|
32
|
+
"Publishing" -> "Add a new pending publisher".
|
|
33
|
+
3. Nothing to store: publishing uses OIDC. No PyPI token exists.
|
|
34
|
+
|
|
35
|
+
> **If this workflow was ever registered under a different filename** (for example the
|
|
36
|
+
> project's original `publish.yml`, before this repository moved every language into its
|
|
37
|
+
> own top-level directory): trusted publishing is matched by *workflow filename*, so the
|
|
38
|
+
> existing entries on PyPI and TestPyPI must be updated to `python-publish.yml` -- edit
|
|
39
|
+
> them under "Publishing" on each site -- or the next release fails with a permission
|
|
40
|
+
> error, not a helpful one.
|
|
41
|
+
|
|
42
|
+
## Releasing
|
|
43
|
+
|
|
44
|
+
1. Bump `__version__` in `src/conclude/__init__.py` (the single source of truth; see
|
|
45
|
+
`[tool.hatch.version]` in `pyproject.toml`), and the version in
|
|
46
|
+
`docs/python/comparison.md`'s "Verified" line if it's stale. Put a dated entry in
|
|
47
|
+
`CHANGELOG.md` (the release is refused while it says *Unreleased*).
|
|
48
|
+
2. Merge to `main`.
|
|
49
|
+
3. Create a GitHub Release from a new tag `python-v<version>` on `main`.
|
|
50
|
+
4. `publish-pypi` waits for a reviewer to **approve the `pypi` deployment**; approve it
|
|
51
|
+
when you are happy.
|
|
52
|
+
|
|
53
|
+
## Rehearsing
|
|
54
|
+
|
|
55
|
+
Actions, Publish, Run workflow, target `testpypi` -- builds, checks, and publishes to
|
|
56
|
+
TestPyPI without touching the `pypi` environment or a real version on PyPI.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
A feature-by-feature comparison with five libraries people often reach
|
|
4
4
|
for instead. **Verified 2026-09-19** against each project's
|
|
5
5
|
documentation and its PyPI metadata; the versions checked are
|
|
6
|
-
conclude 1.0.
|
|
6
|
+
conclude 1.0.2, ConfigArgParse 1.7.7, jsonargparse 4.52.0,
|
|
7
7
|
pydantic-settings 2.15.0, Dynaconf 3.3.5 and python-decouple 3.8.
|
|
8
8
|
|
|
9
9
|
A dash (--) means no built-in support that the project's documentation
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# The conclude concept
|
|
2
|
+
|
|
3
|
+
conclude is one idea, implemented once per language: **write your settings
|
|
4
|
+
down once, as a set of defaults, and derive everything else.** This
|
|
5
|
+
document is the language-neutral statement of that idea -- what a
|
|
6
|
+
conforming implementation does, independent of syntax. The Python package
|
|
7
|
+
is the reference implementation ([guide](python/guide.md), [reference](python/reference.md));
|
|
8
|
+
other implementations follow this document and pass the conformance
|
|
9
|
+
fixtures in [`spec/`](../spec/README.md).
|
|
10
|
+
|
|
11
|
+
This is **concept version 1**, which is **spec version 1** of the fixtures
|
|
12
|
+
(the Python package 1.0.x implements it). The words "must" and "may" are
|
|
13
|
+
meant literally: a conforming implementation does what "must" says, and
|
|
14
|
+
anything a sentence does not require is up to the implementation.
|
|
15
|
+
|
|
16
|
+
## 1. The idea
|
|
17
|
+
|
|
18
|
+
A setting that can come from a flag, an environment variable, or a config
|
|
19
|
+
file usually gets written down three times, each with its own name, its own
|
|
20
|
+
type conversion, and its own copy of the default. conclude takes one
|
|
21
|
+
declaration -- a name, a type and a starting value -- and derives:
|
|
22
|
+
|
|
23
|
+
1. **the interfaces**: the environment variable, the config-file key, the
|
|
24
|
+
CLI flag, and the caster that turns a raw value from any of them into the
|
|
25
|
+
right type;
|
|
26
|
+
2. **the resolution**: a fixed, layered precedence that merges every source
|
|
27
|
+
into one settings object; and
|
|
28
|
+
3. **the documentation**: templates and reports generated from the same
|
|
29
|
+
declaration, so they cannot drift from it.
|
|
30
|
+
|
|
31
|
+
## 2. Vocabulary
|
|
32
|
+
|
|
33
|
+
- A **setting** is a *key*, a *declared type* and a *default*.
|
|
34
|
+
- A **key** is an identifier. Its canonical form is `snake_case`.
|
|
35
|
+
- The **declared types** are `bool`, `int`, `float`, `str` and `list`.
|
|
36
|
+
- A **default** is a value of the declared type, or **unset** (written
|
|
37
|
+
`null` in the fixtures): an *optional* setting declares its type but
|
|
38
|
+
starts out without a value.
|
|
39
|
+
- A **layer** is a source of raw values; a **raw value** is what the layer
|
|
40
|
+
gave (environment values are always text; config-file values are whatever
|
|
41
|
+
TOML gave; CLI values arrive already parsed).
|
|
42
|
+
- **No opinion** means a layer says nothing about a key. It is expressed by
|
|
43
|
+
the key being absent or `null`, and never overrides a lower layer.
|
|
44
|
+
|
|
45
|
+
Settings are ordered (declaration order): generated templates keep it.
|
|
46
|
+
|
|
47
|
+
## 3. Declaring settings
|
|
48
|
+
|
|
49
|
+
| Token | Meaning | Casts to |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| `bool` | a flag | true or false |
|
|
52
|
+
| `int` | a number that prints as a whole number when it is one | an integer, or a fraction if that is what was given |
|
|
53
|
+
| `float` | a number | a number |
|
|
54
|
+
| `str` | text | text, or unset |
|
|
55
|
+
| `list` | a list of text items | a list of text, or unset |
|
|
56
|
+
|
|
57
|
+
The declared type -- not the runtime shape of the default -- decides the
|
|
58
|
+
caster and how a default is rendered in a template. A language that can
|
|
59
|
+
infer the type from the default (Python) may do so; one that cannot tell `3`
|
|
60
|
+
from `3.0` (JavaScript) must let the author say it.
|
|
61
|
+
|
|
62
|
+
## 4. Names (`naming.json`)
|
|
63
|
+
|
|
64
|
+
- **Config key**: the key itself.
|
|
65
|
+
- **Environment variable**: the application name and the key, each with
|
|
66
|
+
every character outside `0-9`, `A-Z`, `a-z` and `_` replaced by `_`,
|
|
67
|
+
joined with `_` and upper-cased (ASCII); a name that would start with a
|
|
68
|
+
digit gets a leading `_`. `("my-app", "foo_bar")` is `MY_APP_FOO_BAR`. A
|
|
69
|
+
setting may override its name.
|
|
70
|
+
- **CLI flag**: `--` and the key with `_` replaced by `-`; case is kept.
|
|
71
|
+
`filter_col` is `--filter-col`.
|
|
72
|
+
|
|
73
|
+
Two application names that differ only in which non-identifier character
|
|
74
|
+
they use sanitize to the same prefix; that is a known, accepted limit.
|
|
75
|
+
|
|
76
|
+
## 5. Casting (`casters.json`)
|
|
77
|
+
|
|
78
|
+
Every layer's value passes through the caster for its setting's declared
|
|
79
|
+
type. A caster either returns a value, returns **unset**, or fails with an
|
|
80
|
+
error; it never guesses.
|
|
81
|
+
|
|
82
|
+
- **`bool`**: a true boolean passes through. Anything else is turned into
|
|
83
|
+
text, trimmed and lower-cased: `1 true yes on y` are true, `0 false no off
|
|
84
|
+
n` are false, and empty text is false. Any other text is an error (so the
|
|
85
|
+
typo `tru` is caught, not treated as false).
|
|
86
|
+
- **`int`**: `null` and empty text are unset. A number is itself (a whole
|
|
87
|
+
number stays whole). Text is trimmed, then read as an integer -- optionally
|
|
88
|
+
with a redundant all-zero decimal part (`5`, `5.0`, `5.00`) -- or else as a
|
|
89
|
+
decimal or exponent number (`1.5`, `1e3`), which is returned as an integer
|
|
90
|
+
when it is whole. Anything else, including text that is only whitespace and
|
|
91
|
+
hexadecimal text, is an error.
|
|
92
|
+
- **`float`**: `null` and empty text are unset; otherwise the trimmed value
|
|
93
|
+
must be a decimal or exponent number (`0.5`, `.5`, `5.`, `1e-2`), else an
|
|
94
|
+
error.
|
|
95
|
+
- **`str`**: `null` and empty text are unset; anything else becomes text,
|
|
96
|
+
untouched (no trimming).
|
|
97
|
+
- **`list`**: `null` and empty text are unset. A list has each item turned
|
|
98
|
+
into trimmed text, dropping empty ones. Text is split on commas the same
|
|
99
|
+
way, so `a,,b,` is `["a", "b"]` and `,` is an empty list.
|
|
100
|
+
|
|
101
|
+
## 6. Layers and precedence (`merge.json`)
|
|
102
|
+
|
|
103
|
+
From lowest to highest priority:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
defaults < system < user < project < env < developer < CLI
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`system`, `user` and `project` are config files (section 7); `env` is the
|
|
110
|
+
process environment, with an optional `.env` file beneath it (section 8);
|
|
111
|
+
`developer` is a private local file (section 10).
|
|
112
|
+
|
|
113
|
+
1. Layers are merged from lowest to highest, key by key, each value cast as
|
|
114
|
+
it lands; a higher layer replaces a lower one outright (lists are
|
|
115
|
+
replaced, not concatenated).
|
|
116
|
+
2. A `null` (or absent) value is *no opinion*. An **empty string is a
|
|
117
|
+
value**: it is cast like any other, so `""` overrides a lower layer and
|
|
118
|
+
resolves to unset for `int`, `str` and `list`, and to false for `bool`.
|
|
119
|
+
To have no opinion, leave the key out.
|
|
120
|
+
3. Keys that no setting declares are ignored, so a file or an environment can
|
|
121
|
+
carry unrelated keys.
|
|
122
|
+
4. Defaults are cast too, and every declared key is present in the result;
|
|
123
|
+
optional settings that nothing set are `null`.
|
|
124
|
+
5. A cast failure is an error wherever the bad value came from -- even in a
|
|
125
|
+
layer that a higher one would have overridden.
|
|
126
|
+
6. Whether a still-unset setting is acceptable is the application's concern,
|
|
127
|
+
not the merge's.
|
|
128
|
+
|
|
129
|
+
## 7. Config files (`config_layers.json`, `config_tables.json`)
|
|
130
|
+
|
|
131
|
+
| Layer | Location | On by default? |
|
|
132
|
+
| --- | --- | --- |
|
|
133
|
+
| system | `/etc/<name>/config.toml` | no (opt-in) |
|
|
134
|
+
| user | `~/.config/<name>/config.toml` | yes |
|
|
135
|
+
| project | `./.config.toml` | yes |
|
|
136
|
+
| project siblings | `./.config.*.toml`, sorted by name | yes (the pattern is configurable; it can be switched off alone) |
|
|
137
|
+
|
|
138
|
+
The files are merged key by key in that order, so a key set in a later file
|
|
139
|
+
wins; each source may be switched off, and a missing file contributes
|
|
140
|
+
nothing.
|
|
141
|
+
|
|
142
|
+
Files are TOML. A setting lives in a **table** named after the application
|
|
143
|
+
(or another chosen name). A table path has one or two levels, written
|
|
144
|
+
`PARENT` or `PARENT.CHILD`; with two, the parent table supplies shared
|
|
145
|
+
values and the child table overrides them. Only keys that a setting declares
|
|
146
|
+
are read. A missing table, or a name that is not a table, contributes
|
|
147
|
+
nothing; a file that is not valid TOML is a loud error that names the file.
|
|
148
|
+
|
|
149
|
+
**Choosing the table.** An explicit selection (a flag, or its environment
|
|
150
|
+
variable) wins outright. Otherwise a bare positional *shorthand* is looked
|
|
151
|
+
up in the files themselves -- across every file and sibling, as a union: a
|
|
152
|
+
top-level `[SHORTHAND]` table wins, then a nested `[<name>.SHORTHAND]`,
|
|
153
|
+
else the application's own table. Text with an empty part (`a..b`, `a.`,
|
|
154
|
+
`.a`) or more than two levels is an error, not a spelling of something else.
|
|
155
|
+
|
|
156
|
+
## 8. The `.env` file (`dotenv.json`)
|
|
157
|
+
|
|
158
|
+
An opt-in **fallback beneath the real environment**: a variable that is
|
|
159
|
+
actually set in the process always wins, and the file only supplies the rest.
|
|
160
|
+
Its keys are environment variable names.
|
|
161
|
+
|
|
162
|
+
The file is UTF-8 on every platform, and a leading byte-order mark is
|
|
163
|
+
ignored. One variable per line: `NAME=value`, with an optional leading
|
|
164
|
+
`export `; blank lines and lines starting with `#` are skipped, as are lines
|
|
165
|
+
without an `=`. The key and the value are trimmed, only the first `=`
|
|
166
|
+
separates them, and a later duplicate wins. A value wrapped in matching
|
|
167
|
+
quotes has them removed: single quotes are literal, double quotes also decode
|
|
168
|
+
`\n`, `\t`, `\r`, `\\` and `\"` (any other backslash sequence is left as
|
|
169
|
+
written). Not supported, on purpose: multi-line values, inline comments, and
|
|
170
|
+
`${VAR}` interpolation.
|
|
171
|
+
|
|
172
|
+
An application may require the file to pass the gitignore guard (section 9)
|
|
173
|
+
before it is read; if it fails, nothing is read from it.
|
|
174
|
+
|
|
175
|
+
## 9. The gitignore guard (`guard.json`, `gitignore.json`)
|
|
176
|
+
|
|
177
|
+
A file that beats -- or quietly backstops -- the environment must never come
|
|
178
|
+
from somewhere it shouldn't: a fresh clone, a CI checkout, a deployed image.
|
|
179
|
+
"Inside a git working tree, and ignored by it" is the practical proxy for
|
|
180
|
+
"this is somebody's private local file", and it can be checked without a
|
|
181
|
+
`git` executable.
|
|
182
|
+
|
|
183
|
+
The checks run in this order and the first failure is the **reason**:
|
|
184
|
+
|
|
185
|
+
1. **Kill switch**: if a kill-switch variable is configured and the real
|
|
186
|
+
environment sets it (trimmed, case-insensitive) to `0`, `off`, `false` or
|
|
187
|
+
`no`: `disabled by <VAR>=<value>` (the trimmed value, as written).
|
|
188
|
+
2. The file does not exist: `file not found`.
|
|
189
|
+
3. It is not inside a git working tree -- no ancestor directory holds a
|
|
190
|
+
`.git` (a directory, or a file, as in a worktree or submodule):
|
|
191
|
+
`not inside a git working tree`.
|
|
192
|
+
4. No ignore rule matches it: `not covered by .gitignore`.
|
|
193
|
+
5. Otherwise the file is **active**.
|
|
194
|
+
|
|
195
|
+
The kill switch is a control knob, not a setting: it is read only from the
|
|
196
|
+
real environment, and it is checked first so it also avoids needing the
|
|
197
|
+
matcher.
|
|
198
|
+
|
|
199
|
+
**Matching the rules.** The rules are the `.gitignore` files from the
|
|
200
|
+
repository root down to the file's directory, plus the repository's
|
|
201
|
+
`.git/info/exclude`. Nearer `.gitignore` files override farther ones, and
|
|
202
|
+
`info/exclude` has the lowest priority; within a file the last matching
|
|
203
|
+
pattern wins (so `!pattern` un-ignores); and an ignored directory ignores
|
|
204
|
+
everything beneath it, whatever the files inside say. The global
|
|
205
|
+
`core.excludesFile` is not consulted, and the check looks at patterns, not
|
|
206
|
+
at git's index -- a *tracked* file that matches a pattern still counts as
|
|
207
|
+
ignored. Both limits fail closed except that last one.
|
|
208
|
+
|
|
209
|
+
If the matching capability is unavailable (an optional dependency that is not
|
|
210
|
+
installed), that is a **setup error**, reported as such rather than as an
|
|
211
|
+
ordinary inactive file -- see the quiet-versus-loud rule below.
|
|
212
|
+
|
|
213
|
+
## 10. The developer layer
|
|
214
|
+
|
|
215
|
+
An opt-in, private, project-local file that sits **above the environment**
|
|
216
|
+
and below the CLI. Environment variables are process-wide and can leak
|
|
217
|
+
between projects; a file whose location the project decides is scoped to
|
|
218
|
+
exactly the project it belongs to, so it wins over ambient state.
|
|
219
|
+
|
|
220
|
+
- **Where it is declared** is a per-ecosystem *manifest binding* (section
|
|
221
|
+
14): the committed project manifest names the file, relative to the
|
|
222
|
+
manifest's directory.
|
|
223
|
+
- **Format follows the name.** A name ending in `.toml` is TOML, read with
|
|
224
|
+
the same tables as any config file (section 7). Any other name (say
|
|
225
|
+
`.env.local`) is dotenv (section 8), keyed by the application's
|
|
226
|
+
environment variable names, so the same private file can serve other tools;
|
|
227
|
+
dotenv has no tables, and an empty value is cast like an empty environment
|
|
228
|
+
variable (section 6), not skipped.
|
|
229
|
+
- **The guard is mandatory** (section 9), with a kill switch named after the
|
|
230
|
+
application: `<APP>_DEVELOPER_CONFIG`, using the environment naming of
|
|
231
|
+
section 4.
|
|
232
|
+
|
|
233
|
+
The layer is always in one of four states, which reports show verbatim:
|
|
234
|
+
|
|
235
|
+
| State | Meaning |
|
|
236
|
+
| --- | --- |
|
|
237
|
+
| `not opted in` | the application never asked for the layer |
|
|
238
|
+
| `not configured` | opted in, but the manifest names no file |
|
|
239
|
+
| `configured, inactive` | a file is named but a guard check failed; the reason says which |
|
|
240
|
+
| `configured, active` | the file is read |
|
|
241
|
+
|
|
242
|
+
An `.env` file held to the guard reports `disabled` (no path), `active` or
|
|
243
|
+
`inactive` in the same spirit (for example `.env -- active (gitignored)` or
|
|
244
|
+
`.env -- inactive (not covered by .gitignore)`); without the guard it is
|
|
245
|
+
simply `active` whenever it has a path.
|
|
246
|
+
|
|
247
|
+
**Quiet for ordinary situations, loud for broken setups.** Every state above
|
|
248
|
+
is ordinary -- a teammate who has not made their file yet, a CI run, a
|
|
249
|
+
deploy -- so an inactive layer contributes nothing and raises nothing;
|
|
250
|
+
reports say why. Two things are setup errors and raise: the missing matching
|
|
251
|
+
capability (section 9), and an *active* file that is not valid TOML. Status
|
|
252
|
+
and report calls never raise.
|
|
253
|
+
|
|
254
|
+
## 11. Generated templates and invocations (`templates.json`, `invocation.json`)
|
|
255
|
+
|
|
256
|
+
Every template renders the same declared settings, in order, with their
|
|
257
|
+
defaults; options can skip settings, replace a default with the *effective*
|
|
258
|
+
one, rename an environment variable, choose the TOML table (or drop its
|
|
259
|
+
header), and rename a CLI metavar.
|
|
260
|
+
|
|
261
|
+
A default's **plain text** is: `true` or `false` for a `bool`; the items
|
|
262
|
+
joined with `,` for a `list`; a `float` with a decimal point even when whole
|
|
263
|
+
(`3.0`); otherwise the value's text. A setting with no default has no plain
|
|
264
|
+
text and renders as a placeholder.
|
|
265
|
+
|
|
266
|
+
**Environment template**: one `NAME=value` line per setting, or `# NAME=` for
|
|
267
|
+
an unset one. The value is written bare if it consists only of letters,
|
|
268
|
+
digits and `_` (from any script) and the punctuation `. / : @ % + , -`
|
|
269
|
+
(that is, `[\w./:@%+,-]*`, so the empty string is bare); otherwise in single
|
|
270
|
+
quotes if it holds no `'` and no newline, tab or carriage return; otherwise in
|
|
271
|
+
double quotes with `\\`, `\"`, `\n`, `\t` and `\r` escaped.
|
|
272
|
+
|
|
273
|
+
**TOML template**: a `[table]` header (the application name by default; a
|
|
274
|
+
name that is not a bare key -- `A-Za-z0-9_-` -- is quoted), then one
|
|
275
|
+
`key = value` line per setting with native values: `true`/`false`, numbers, a
|
|
276
|
+
list as `["a", "b"]`, and text as a double-quoted string in which `\`, `"`,
|
|
277
|
+
newline, tab and carriage return are escaped as `\\`, `\"`, `\n`, `\t`, `\r`
|
|
278
|
+
and every other control character (U+0000-U+001F and U+007F) as `\uXXXX` with
|
|
279
|
+
upper-case hexadecimal. An unset setting is `# key =`. An empty table path
|
|
280
|
+
means no header.
|
|
281
|
+
|
|
282
|
+
**CLI reference**: one line per flag. A `bool` is the bare flag; any other
|
|
283
|
+
setting is the flag and `<METAVAR>` (the key upper-cased, or the override).
|
|
284
|
+
Each line ends with `(default: text)`, where an unset default reads `none`,
|
|
285
|
+
an empty text reads `""`, and the flag column is padded to the widest so the
|
|
286
|
+
`(default:` parts align, with one space after it.
|
|
287
|
+
|
|
288
|
+
**Reproducing a run.** The inverse of resolving: given a resolved configuration,
|
|
289
|
+
a standalone command line that gets back to it with no environment variables,
|
|
290
|
+
config files or shorthand -- what a `--print-invocation` flag prints. Settings
|
|
291
|
+
are visited in declaration order, and each is written as `--flag=value` (a bare
|
|
292
|
+
`--flag` for a `bool` that is on) unless it is left out. A setting is left out
|
|
293
|
+
when it equals its default -- omitting a flag already reproduces its default, and
|
|
294
|
+
this covers every setting automatically -- when it is a `bool` that is off (there
|
|
295
|
+
is no flag to turn one off), when its value is unset, or when the caller skips it
|
|
296
|
+
(`skip` beats `alwaysInclude`, which forces a setting that equals its default to
|
|
297
|
+
be written). `compareDefaults` replaces what counts as each setting's default for
|
|
298
|
+
one call, for a setting whose effective default is substituted after resolving.
|
|
299
|
+
The program name, if given, comes first. Values are POSIX shell-quoted: bare if
|
|
300
|
+
every character is a letter or digit from ASCII, or one of `_ @ % + = : , . / -`,
|
|
301
|
+
otherwise in single quotes with each `'` written as `'"'"'` (so non-ASCII letters
|
|
302
|
+
are quoted, and an empty text is `''`); a list is its items joined with `,`, then
|
|
303
|
+
quoted; a `float` keeps its decimal point (`3.0`).
|
|
304
|
+
|
|
305
|
+
## 12. Reports (`sources.json`)
|
|
306
|
+
|
|
307
|
+
A report of the sources in play, for a `--help` epilog, is a header line
|
|
308
|
+
`config sources:` and then one row per source, lowest priority first:
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
config sources:
|
|
312
|
+
system config disabled
|
|
313
|
+
user config /home/me/.config/myapp/config.toml
|
|
314
|
+
project config .config.toml, .config.*.toml
|
|
315
|
+
.env file disabled
|
|
316
|
+
developer config not opted in
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Each row is two spaces, the label padded to the longest label
|
|
320
|
+
(`developer config`), two more spaces, and the value. The value of the
|
|
321
|
+
config-file rows is the path, or `disabled` when that source is switched off;
|
|
322
|
+
the project row also lists the sibling pattern (in the same directory as the
|
|
323
|
+
project file) unless siblings are off. The `.env` row is `disabled`, its path
|
|
324
|
+
(when no guard is required), or -- for a guarded file --
|
|
325
|
+
`<path> -- active (gitignored)` or `<path> -- inactive (<reason>)`. The
|
|
326
|
+
developer row is one of the four states of section 10, with the guard's reason
|
|
327
|
+
for an inactive file.
|
|
328
|
+
|
|
329
|
+
## 13. Conformance
|
|
330
|
+
|
|
331
|
+
An implementation conforms to spec version 1 when it passes every fixture in
|
|
332
|
+
[`spec/`](../spec/README.md). The fixtures are data, not code; each
|
|
333
|
+
implementation writes a small adapter from a fixture file to its own API.
|
|
334
|
+
|
|
335
|
+
| Fixture | Sections | What it covers |
|
|
336
|
+
| --- | --- | --- |
|
|
337
|
+
| `naming.json` | 4 | environment variable, flag and key names |
|
|
338
|
+
| `casters.json` | 3, 5 | casting for every declared type |
|
|
339
|
+
| `dotenv.json` | 8 | the `.env` dialect |
|
|
340
|
+
| `merge.json` | 6 | precedence, no-opinion, empty values, errors |
|
|
341
|
+
| `config_layers.json` | 7 | merging system, user, project and sibling files; tables |
|
|
342
|
+
| `config_tables.json` | 7 | table selection and the positional shorthand |
|
|
343
|
+
| `templates.json` | 11 | the environment, TOML and CLI templates |
|
|
344
|
+
| `invocation.json` | 11 | reproducing a resolved configuration as a command line |
|
|
345
|
+
| `gitignore.json` | 9 | rule matching, judged by real `git` |
|
|
346
|
+
| `guard.json` | 9 | the guard's checks and reasons |
|
|
347
|
+
| `sources.json` | 10, 12 | the report of which sources are in play |
|
|
348
|
+
|
|
349
|
+
Not yet covered by fixtures: how each binding reads its manifest (section 10;
|
|
350
|
+
the wording of a `not configured` reason is binding-specific), and how a
|
|
351
|
+
language exposes flags to its CLI parser.
|
|
352
|
+
|
|
353
|
+
## 14. What varies by language
|
|
354
|
+
|
|
355
|
+
| Concern | Python (reference) | TypeScript ([`node/`](../node/README.md)) |
|
|
356
|
+
| --- | --- | --- |
|
|
357
|
+
| Declaring settings | a dict of defaults; `opt(type)` for an unset one | an object of defaults, with `int()`, `float()`, `str()`, ... for numbers and unset settings |
|
|
358
|
+
| Type inference | the default's runtime type | the default's runtime type, plus explicit `int` and `float` (D2) |
|
|
359
|
+
| Key style in the API | `snake_case` | `camelCase`, converted (D1) |
|
|
360
|
+
| Manifest binding | `pyproject.toml`, `[tool.conclude.developer] config = "..."` | `package.json`, `"conclude": { "developer": { "config": "..." } }` (D3) |
|
|
361
|
+
| CLI | builds `argparse` flags | flag definitions in `node:util.parseArgs`' shape, plus a thin `parseArgs` wrapper (D4) |
|
|
362
|
+
| TOML | standard library | a small dependency (D5) |
|
|
363
|
+
| Gitignore matching | the optional `pathspec` extra | an optional dependency (D5) |
|
|
364
|
+
|
|
365
|
+
**Decisions for other bindings.** The TypeScript package follows all five (its
|
|
366
|
+
[guide](node/guide.md) and [reference](node/reference.md) show how):
|
|
367
|
+
|
|
368
|
+
- **D1: canonical names in files and the environment.** Config-file keys and
|
|
369
|
+
environment variable names use the canonical `snake_case` of section 4, so
|
|
370
|
+
one config file or `.env` serves every implementation; a binding's API may
|
|
371
|
+
use its own casing and converts (`filterCol` is `filter_col`).
|
|
372
|
+
- **D2: numeric kinds are explicit** where the language cannot distinguish
|
|
373
|
+
them, and templates render per declared kind (section 11).
|
|
374
|
+
- **D3: each ecosystem uses its own manifest** to name the developer file;
|
|
375
|
+
in a repository with several, they may name the same file.
|
|
376
|
+
- **D4: no bundled CLI parser.** The binding exposes derived flag
|
|
377
|
+
definitions rather than owning the command line.
|
|
378
|
+
- **D5: dependencies are minimal.** A TOML parser is required; gitignore
|
|
379
|
+
matching is optional, and its absence is a setup error (section 9).
|
|
380
|
+
|
|
381
|
+
## 15. Implementation-defined
|
|
382
|
+
|
|
383
|
+
The fixtures deliberately say nothing about these; implementations may differ
|
|
384
|
+
and should document what they do:
|
|
385
|
+
|
|
386
|
+
- integers beyond 2^53, `inf` and `nan`, underscores or non-ASCII digits in
|
|
387
|
+
numerals, and how a native boolean is cast by `int` or `str`;
|
|
388
|
+
- line breaks other than `\n`, `\r\n` and `\r` in a `.env` file;
|
|
389
|
+
- how a very small or very large `float` is written (exponent form) in a
|
|
390
|
+
template;
|
|
391
|
+
- reproducing a run for a `bool` whose default is true (there is no flag to turn
|
|
392
|
+
it off, so it cannot be reproduced), and for a non-`bool` setting whose
|
|
393
|
+
resolved value is unset while its default is not (Python writes `None`; the
|
|
394
|
+
TypeScript binding leaves it out);
|
|
395
|
+
- a default of empty text on a `str` setting when it is *resolved* (the
|
|
396
|
+
template shows it; the merge casts it to unset);
|
|
397
|
+
- what a boolean, a list or another non-text value does when cast to `str`,
|
|
398
|
+
and what a boolean does when cast to `int` or `float` (Python converts;
|
|
399
|
+
the TypeScript binding rejects them);
|
|
400
|
+
- the wording of the manifest-related reasons (`not configured`), and of
|
|
401
|
+
errors.
|