conclude 1.0.0__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.0/.github/dependabot.yml +10 -0
- conclude-1.0.0/.github/workflows/ci.yml +109 -0
- conclude-1.0.0/.github/workflows/publish.yml +125 -0
- conclude-1.0.0/.gitignore +29 -0
- conclude-1.0.0/CHANGELOG.md +7 -0
- conclude-1.0.0/LICENSE +7 -0
- conclude-1.0.0/PKG-INFO +260 -0
- conclude-1.0.0/README.md +229 -0
- conclude-1.0.0/docs/comparison.md +71 -0
- conclude-1.0.0/docs/guide.md +739 -0
- conclude-1.0.0/docs/reference.md +211 -0
- conclude-1.0.0/pyproject.toml +93 -0
- conclude-1.0.0/src/conclude/__init__.py +189 -0
- conclude-1.0.0/src/conclude/app.py +819 -0
- conclude-1.0.0/src/conclude/casters.py +262 -0
- conclude-1.0.0/src/conclude/developer.py +215 -0
- conclude-1.0.0/src/conclude/env.py +176 -0
- conclude-1.0.0/src/conclude/files.py +247 -0
- conclude-1.0.0/src/conclude/formatters.py +100 -0
- conclude-1.0.0/src/conclude/guard.py +172 -0
- conclude-1.0.0/src/conclude/infer.py +116 -0
- conclude-1.0.0/src/conclude/merge.py +58 -0
- conclude-1.0.0/src/conclude/naming.py +58 -0
- conclude-1.0.0/src/conclude/paths.py +26 -0
- conclude-1.0.0/src/conclude/py.typed +0 -0
- conclude-1.0.0/src/conclude/templates.py +83 -0
- conclude-1.0.0/src/conclude/tomlwrite.py +29 -0
- conclude-1.0.0/tests/test_app.py +650 -0
- conclude-1.0.0/tests/test_casters.py +260 -0
- conclude-1.0.0/tests/test_comparison.py +145 -0
- conclude-1.0.0/tests/test_developer.py +742 -0
- conclude-1.0.0/tests/test_docs.py +246 -0
- conclude-1.0.0/tests/test_dotenv_guard.py +213 -0
- conclude-1.0.0/tests/test_env.py +100 -0
- conclude-1.0.0/tests/test_files.py +354 -0
- conclude-1.0.0/tests/test_formatters.py +60 -0
- conclude-1.0.0/tests/test_guard.py +109 -0
- conclude-1.0.0/tests/test_infer.py +59 -0
- conclude-1.0.0/tests/test_merge.py +65 -0
- conclude-1.0.0/tests/test_naming.py +57 -0
- conclude-1.0.0/tests/test_repo_hygiene.py +190 -0
- conclude-1.0.0/tests/test_templates.py +425 -0
- conclude-1.0.0/tests/test_tomlwrite_and_paths.py +38 -0
- conclude-1.0.0/uv.lock +948 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_call: # publish.yml runs this before it publishes anything
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
# Every third-party action is pinned to a full commit SHA (the version is
|
|
13
|
+
# in the trailing comment); .github/dependabot.yml keeps them current.
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
test:
|
|
17
|
+
name: pytest (Python ${{ matrix.python-version }})
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
timeout-minutes: 10
|
|
20
|
+
strategy:
|
|
21
|
+
fail-fast: false
|
|
22
|
+
matrix:
|
|
23
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
24
|
+
steps:
|
|
25
|
+
- name: Check out
|
|
26
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
27
|
+
with:
|
|
28
|
+
persist-credentials: false
|
|
29
|
+
- name: Set up uv and Python ${{ matrix.python-version }}
|
|
30
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
31
|
+
with:
|
|
32
|
+
python-version: ${{ matrix.python-version }}
|
|
33
|
+
- name: Install the project and its dev tools (pathspec included)
|
|
34
|
+
run: uv sync --locked
|
|
35
|
+
- name: pytest
|
|
36
|
+
run: uv run pytest -q
|
|
37
|
+
|
|
38
|
+
lint:
|
|
39
|
+
name: ruff and mypy
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
timeout-minutes: 10
|
|
42
|
+
steps:
|
|
43
|
+
- name: Check out
|
|
44
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
45
|
+
with:
|
|
46
|
+
persist-credentials: false
|
|
47
|
+
- name: Set up uv and Python 3.11 (the oldest supported version)
|
|
48
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
49
|
+
with:
|
|
50
|
+
python-version: "3.11"
|
|
51
|
+
- name: Install the project and its dev tools
|
|
52
|
+
run: uv sync --locked
|
|
53
|
+
- name: ruff check
|
|
54
|
+
run: uv run ruff check .
|
|
55
|
+
- name: ruff format --check
|
|
56
|
+
run: uv run ruff format --check .
|
|
57
|
+
- name: mypy
|
|
58
|
+
run: uv run mypy
|
|
59
|
+
|
|
60
|
+
no-extras:
|
|
61
|
+
name: pytest with no optional dependencies
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
timeout-minutes: 10
|
|
64
|
+
steps:
|
|
65
|
+
- name: Check out
|
|
66
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
67
|
+
with:
|
|
68
|
+
persist-credentials: false
|
|
69
|
+
- name: Set up uv and Python 3.13
|
|
70
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
71
|
+
with:
|
|
72
|
+
python-version: "3.13"
|
|
73
|
+
- name: Install the package on its own, plus pytest (no pathspec)
|
|
74
|
+
run: |
|
|
75
|
+
uv venv "$RUNNER_TEMP/venv-plain"
|
|
76
|
+
uv pip install --python "$RUNNER_TEMP/venv-plain/bin/python" . pytest
|
|
77
|
+
- name: pytest (the pathspec-dependent tests skip themselves)
|
|
78
|
+
run: |
|
|
79
|
+
"$RUNNER_TEMP/venv-plain/bin/python" -m pytest -q
|
|
80
|
+
|
|
81
|
+
build:
|
|
82
|
+
name: build and twine check
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
timeout-minutes: 10
|
|
85
|
+
steps:
|
|
86
|
+
- name: Check out
|
|
87
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
88
|
+
with:
|
|
89
|
+
persist-credentials: false
|
|
90
|
+
- name: Set up uv and Python 3.13
|
|
91
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
92
|
+
with:
|
|
93
|
+
python-version: "3.13"
|
|
94
|
+
- name: Build the sdist and wheel
|
|
95
|
+
run: uv build
|
|
96
|
+
- name: twine check
|
|
97
|
+
run: uvx twine check --strict dist/*
|
|
98
|
+
- name: Smoke-test the wheel in a clean environment
|
|
99
|
+
run: |
|
|
100
|
+
uv venv "$RUNNER_TEMP/venv-wheel"
|
|
101
|
+
uv pip install --python "$RUNNER_TEMP/venv-wheel/bin/python" dist/*.whl
|
|
102
|
+
"$RUNNER_TEMP/venv-wheel/bin/python" - <<'PY'
|
|
103
|
+
import pathlib
|
|
104
|
+
|
|
105
|
+
import conclude
|
|
106
|
+
|
|
107
|
+
print("conclude", conclude.__version__)
|
|
108
|
+
assert (pathlib.Path(conclude.__file__).parent / "py.typed").exists()
|
|
109
|
+
PY
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
# One-time setup, before the first run:
|
|
4
|
+
# 1. On https://pypi.org (and https://test.pypi.org), add a trusted publisher
|
|
5
|
+
# for the project `conclude`: this repository, workflow `publish.yml`,
|
|
6
|
+
# environment `pypi` (`testpypi` on TestPyPI). For a project that doesn't
|
|
7
|
+
# exist yet, use "Publishing" -> "Add a new pending publisher".
|
|
8
|
+
# 2. In this repository's Settings -> Environments, create `pypi` and
|
|
9
|
+
# `testpypi`. Put required reviewers on `pypi` to make a real release
|
|
10
|
+
# wait for an explicit approval.
|
|
11
|
+
#
|
|
12
|
+
# Then:
|
|
13
|
+
# - A dry run: Actions -> Publish -> Run workflow -> target `testpypi`.
|
|
14
|
+
# - A release: publish a GitHub Release whose tag is `v<version>`.
|
|
15
|
+
|
|
16
|
+
on:
|
|
17
|
+
release:
|
|
18
|
+
types: [published]
|
|
19
|
+
workflow_dispatch:
|
|
20
|
+
inputs:
|
|
21
|
+
target:
|
|
22
|
+
description: Where to publish
|
|
23
|
+
type: choice
|
|
24
|
+
options: [testpypi, pypi]
|
|
25
|
+
default: testpypi
|
|
26
|
+
|
|
27
|
+
permissions:
|
|
28
|
+
contents: read
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
ci:
|
|
32
|
+
name: CI
|
|
33
|
+
uses: ./.github/workflows/ci.yml
|
|
34
|
+
|
|
35
|
+
build:
|
|
36
|
+
name: Build the distributions
|
|
37
|
+
needs: ci
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
timeout-minutes: 10
|
|
40
|
+
env:
|
|
41
|
+
TARGET: ${{ github.event_name == 'release' && 'pypi' || inputs.target }}
|
|
42
|
+
steps:
|
|
43
|
+
- name: Check out
|
|
44
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
45
|
+
with:
|
|
46
|
+
persist-credentials: false
|
|
47
|
+
- name: Set up uv and Python 3.13
|
|
48
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
49
|
+
with:
|
|
50
|
+
python-version: "3.13"
|
|
51
|
+
- name: Read the version
|
|
52
|
+
id: version
|
|
53
|
+
run: echo "version=$(grep -oP '^__version__ = "\K[^"]+' src/conclude/__init__.py)" >> "$GITHUB_OUTPUT"
|
|
54
|
+
- name: Check the release is ready (real PyPI only)
|
|
55
|
+
if: env.TARGET == 'pypi'
|
|
56
|
+
env:
|
|
57
|
+
TAG: ${{ github.event.release.tag_name }}
|
|
58
|
+
VERSION: ${{ steps.version.outputs.version }}
|
|
59
|
+
run: |
|
|
60
|
+
if [ -n "$TAG" ] && [ "$TAG" != "v$VERSION" ]; then
|
|
61
|
+
echo "::error::The release tag $TAG does not match the package version v$VERSION."
|
|
62
|
+
exit 1
|
|
63
|
+
fi
|
|
64
|
+
if ! grep -q "^## \[$VERSION\]" CHANGELOG.md; then
|
|
65
|
+
echo "::error::CHANGELOG.md has no entry for $VERSION."
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
if grep -q "^## \[$VERSION\] - Unreleased" CHANGELOG.md; then
|
|
69
|
+
echo "::error::CHANGELOG.md still marks $VERSION as Unreleased; put the date on it."
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
- name: Build the sdist and wheel
|
|
73
|
+
run: uv build
|
|
74
|
+
- name: twine check
|
|
75
|
+
run: uvx twine check --strict dist/*
|
|
76
|
+
- name: Upload the distributions
|
|
77
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
78
|
+
with:
|
|
79
|
+
name: dist
|
|
80
|
+
path: dist/
|
|
81
|
+
if-no-files-found: error
|
|
82
|
+
retention-days: 7
|
|
83
|
+
|
|
84
|
+
publish-testpypi:
|
|
85
|
+
name: Publish to TestPyPI
|
|
86
|
+
needs: build
|
|
87
|
+
if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi'
|
|
88
|
+
runs-on: ubuntu-latest
|
|
89
|
+
timeout-minutes: 10
|
|
90
|
+
environment:
|
|
91
|
+
name: testpypi
|
|
92
|
+
url: https://test.pypi.org/p/conclude
|
|
93
|
+
permissions:
|
|
94
|
+
id-token: write # trusted publishing (OIDC); no API token is stored
|
|
95
|
+
steps:
|
|
96
|
+
- name: Download the distributions
|
|
97
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
98
|
+
with:
|
|
99
|
+
name: dist
|
|
100
|
+
path: dist/
|
|
101
|
+
- name: Publish to TestPyPI
|
|
102
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
103
|
+
with:
|
|
104
|
+
repository-url: https://test.pypi.org/legacy/
|
|
105
|
+
skip-existing: true
|
|
106
|
+
|
|
107
|
+
publish-pypi:
|
|
108
|
+
name: Publish to PyPI
|
|
109
|
+
needs: build
|
|
110
|
+
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.target == 'pypi')
|
|
111
|
+
runs-on: ubuntu-latest
|
|
112
|
+
timeout-minutes: 10
|
|
113
|
+
environment:
|
|
114
|
+
name: pypi
|
|
115
|
+
url: https://pypi.org/p/conclude
|
|
116
|
+
permissions:
|
|
117
|
+
id-token: write # trusted publishing (OIDC); no API token is stored
|
|
118
|
+
steps:
|
|
119
|
+
- name: Download the distributions
|
|
120
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
121
|
+
with:
|
|
122
|
+
name: dist
|
|
123
|
+
path: dist/
|
|
124
|
+
- name: Publish to PyPI
|
|
125
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Local configuration / secrets (the names the docs use; never commit these)
|
|
2
|
+
.env
|
|
3
|
+
.env.local
|
|
4
|
+
.developer.toml
|
|
5
|
+
*.local.toml
|
|
6
|
+
|
|
7
|
+
# Python
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.py[cod]
|
|
11
|
+
|
|
12
|
+
# Test / type-check / lint caches
|
|
13
|
+
.coverage
|
|
14
|
+
.mypy_cache/
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
htmlcov/
|
|
18
|
+
|
|
19
|
+
# Build artifacts
|
|
20
|
+
build/
|
|
21
|
+
dist/
|
|
22
|
+
|
|
23
|
+
# Virtual environments
|
|
24
|
+
.venv/
|
|
25
|
+
.venv-*/
|
|
26
|
+
venv/
|
|
27
|
+
|
|
28
|
+
# OS
|
|
29
|
+
.DS_Store
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file. The
|
|
4
|
+
format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
|
+
and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [1.0.0] - 2026-09-20
|
conclude-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 Payam Tanaka
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
conclude-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: conclude
|
|
3
|
+
Version: 1.0.0
|
|
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
|
+
Project-URL: Homepage, https://github.com/tanakapayam/conclude
|
|
6
|
+
Project-URL: Documentation, https://github.com/tanakapayam/conclude/blob/main/docs/guide.md
|
|
7
|
+
Project-URL: Repository, https://github.com/tanakapayam/conclude
|
|
8
|
+
Project-URL: Issues, https://github.com/tanakapayam/conclude/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/tanakapayam/conclude/blob/main/CHANGELOG.md
|
|
10
|
+
Author: Payam Tanaka
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: cli,config,configuration,dotenv,environment-variables,settings,toml
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.11
|
|
28
|
+
Provides-Extra: gitignore
|
|
29
|
+
Requires-Dist: pathspec>=0.12; extra == 'gitignore'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# conclude
|
|
33
|
+
|
|
34
|
+
A setting that can come from a flag, an environment variable, or a
|
|
35
|
+
config file usually gets written down three times: an argparse flag, an
|
|
36
|
+
environment variable, and a config-file key -- each with its own name,
|
|
37
|
+
its own type conversion, and its own copy of the default. Add a setting
|
|
38
|
+
and you touch three places; rename one and the other two quietly drift.
|
|
39
|
+
|
|
40
|
+
conclude has you write it once. Set up your defaults; conclude works
|
|
41
|
+
out the rest. You write down a setting's name, its type, and its
|
|
42
|
+
starting value -- as an entry in a plain dict -- and conclude infers
|
|
43
|
+
the environment variable name, the config-file key, the CLI flag, and
|
|
44
|
+
how to cast a raw value from any of those into the right type.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import conclude
|
|
48
|
+
|
|
49
|
+
DEFAULTS = {
|
|
50
|
+
"host": "localhost",
|
|
51
|
+
"port": 8080,
|
|
52
|
+
"debug": False,
|
|
53
|
+
"tags": conclude.opt(list), # unset by default, but still a list when set
|
|
54
|
+
}
|
|
55
|
+
app = conclude.App("myapp", DEFAULTS)
|
|
56
|
+
|
|
57
|
+
parser = app.build_arg_parser(prog="myapp")
|
|
58
|
+
namespace = parser.parse_args()
|
|
59
|
+
cli = {k: v for k, v in vars(namespace).items() if v is not None}
|
|
60
|
+
print(app.resolve(cli))
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
$ MYAPP_PORT=9000 myapp --debug --tags a,b
|
|
65
|
+
{'host': 'localhost', 'port': 9000, 'debug': True, 'tags': ['a', 'b']}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
That one dict gave you the CLI flags (`--host`, `--port`, `--debug`,
|
|
69
|
+
`--tags`), the environment variables (`MYAPP_HOST`, `MYAPP_PORT`, ...),
|
|
70
|
+
the config-file keys (a `[myapp]` table in `~/.config/myapp/config.toml`
|
|
71
|
+
or `./.config.toml`), and a caster for each -- with no second list of
|
|
72
|
+
names to keep in sync.
|
|
73
|
+
|
|
74
|
+
## Where it fits
|
|
75
|
+
|
|
76
|
+
Configuration libraries tend to start from different things. conclude
|
|
77
|
+
starts from a dict of defaults and derives the rest:
|
|
78
|
+
|
|
79
|
+
| Package | Primary abstraction | Reach for it when... |
|
|
80
|
+
| ----------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------- |
|
|
81
|
+
| `argparse` | CLI parser | you only need flags |
|
|
82
|
+
| ConfigArgParse | argparse plus config files and environment variables | you have an argparse CLI and want files and env vars added |
|
|
83
|
+
| jsonargparse | CLI and config built from type hints | your program is functions, classes or dataclasses with type hints |
|
|
84
|
+
| python-dotenv | `.env` loader | you just need a `.env` in `os.environ` |
|
|
85
|
+
| python-decouple | individual value reader (env, then file, then default) | you read a handful of values, Django-style |
|
|
86
|
+
| pydantic-settings | typed, validated settings model | you want validation, nested models or secret-manager sources |
|
|
87
|
+
| Dynaconf | general-purpose layered configuration system | you want named environments, many file formats, or Vault |
|
|
88
|
+
| Hydra / OmegaConf | hierarchical, composable configuration | you compose config groups, run sweeps or manage experiments |
|
|
89
|
+
| **conclude** | **defaults → inferred env / TOML / CLI interfaces → layered resolution** | **you want all three interfaces from one dict, and safe private local overrides** |
|
|
90
|
+
|
|
91
|
+
conclude deliberately does not do schema validation (values are cast,
|
|
92
|
+
not validated), secret-manager backends, YAML or JSON files, or named
|
|
93
|
+
environments; the table says where to look for those. For a dated,
|
|
94
|
+
feature-by-feature comparison, see
|
|
95
|
+
[How conclude compares](https://github.com/tanakapayam/conclude/blob/main/docs/comparison.md).
|
|
96
|
+
|
|
97
|
+
## Design principles
|
|
98
|
+
|
|
99
|
+
- **One source of truth.** The defaults dict. Flags, environment
|
|
100
|
+
variable names, config keys, casters and the generated templates are
|
|
101
|
+
all derived from it, so they can't drift apart.
|
|
102
|
+
- **Opt in to anything unusual.** The system file, `.env` and the
|
|
103
|
+
developer file are off until you ask; the user and project files are
|
|
104
|
+
one `None` from off.
|
|
105
|
+
- **Private files have to prove they're private.** The developer file
|
|
106
|
+
(and `.env`, if you ask) is read only when git is ignoring it.
|
|
107
|
+
- **Quiet for ordinary situations, loud for broken setups.** A missing
|
|
108
|
+
local file is normal and silent, and `describe_sources()` explains
|
|
109
|
+
it; a malformed developer file or a missing `pathspec` raises.
|
|
110
|
+
- **Standard library only.** `pathspec`, for the gitignore check, is an
|
|
111
|
+
optional extra.
|
|
112
|
+
|
|
113
|
+
## The layers
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
1. CLI flags
|
|
117
|
+
2. Developer config file (opt-in; private, gitignored, project-local,
|
|
118
|
+
TOML or dotenv -- beats the environment)
|
|
119
|
+
3. Environment variables (with an opt-in `.env` file as a fallback)
|
|
120
|
+
4. Config file(s), in this order (each overrides the previous):
|
|
121
|
+
a. /etc/<app>/config.toml (system-wide; opt-in, off by default)
|
|
122
|
+
b. ~/.config/<app>/config.toml (user-global, overrides system)
|
|
123
|
+
c. ./.config.toml (project-local, overrides user)
|
|
124
|
+
d. ./.config.*.toml (sibling files, sorted by name)
|
|
125
|
+
5. Hardcoded defaults
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Or, lowest priority to highest:
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
defaults < system < user < project < env < developer < CLI
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Everything hangs off that one dict: the layers it feeds, and what
|
|
135
|
+
conclude infers from it so you never write any of it twice:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
┌── CLI
|
|
139
|
+
defaults ───────┼── developer config (explicit opt-in, must be gitignored)
|
|
140
|
+
│ ├── environment
|
|
141
|
+
│ ├── .env (explicit opt-in)
|
|
142
|
+
│ └── config files
|
|
143
|
+
│
|
|
144
|
+
└── inference
|
|
145
|
+
├── caster
|
|
146
|
+
├── formatter
|
|
147
|
+
├── CLI name
|
|
148
|
+
└── environment name
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
(The layers are listed highest priority first; `defaults` sits beneath
|
|
152
|
+
all of them.) The user and project config files are on by default, and
|
|
153
|
+
each is one `None` away from off. The system file, `.env`, and the
|
|
154
|
+
developer file are off until you ask for them.
|
|
155
|
+
|
|
156
|
+
## Local files: which one?
|
|
157
|
+
|
|
158
|
+
Two of the layers are about your own machine, and each comes in two
|
|
159
|
+
formats:
|
|
160
|
+
|
|
161
|
+
| Format | Below the environment | Above the environment |
|
|
162
|
+
| ------ | ----------------------------------------- | ------------------------------------------ |
|
|
163
|
+
| TOML | the system, user and project config files | the developer file, e.g. `.developer.toml` |
|
|
164
|
+
| dotenv | `.env` | the developer file, e.g. `.env.local` |
|
|
165
|
+
|
|
166
|
+
- **Non-secret local defaults you're happy to commit** -- use `.env`.
|
|
167
|
+
A real `export` in the shell still beats it, so it can't override a
|
|
168
|
+
deployment.
|
|
169
|
+
- **Private values that must win over stray shell exports** (a local
|
|
170
|
+
`DATABASE_URL`, say) -- use the developer file. Make it TOML if only
|
|
171
|
+
your app reads it (it gets `[table]`s, including per-recipient ones);
|
|
172
|
+
make it dotenv, like `.env.local`, if docker compose, direnv or your
|
|
173
|
+
IDE need the same values.
|
|
174
|
+
- **A private `.env` that stays below the environment** -- use `.env`
|
|
175
|
+
with `dotenv_require_gitignored=True`.
|
|
176
|
+
|
|
177
|
+
The developer file's location lives in your committed `pyproject.toml`
|
|
178
|
+
(any name works; the name decides the format, and only `.toml` means
|
|
179
|
+
TOML):
|
|
180
|
+
|
|
181
|
+
```toml
|
|
182
|
+
[tool.conclude.developer]
|
|
183
|
+
config = ".developer.toml" # or ".env.local"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
app = conclude.App(
|
|
188
|
+
"myapp",
|
|
189
|
+
DEFAULTS,
|
|
190
|
+
pyproject_path=conclude.AUTO, # the developer file
|
|
191
|
+
dotenv_path=conclude.AUTO, # .env
|
|
192
|
+
)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The developer file is always guarded, and `.env` is when you ask: the
|
|
196
|
+
file is used only if it exists, sits in a git working tree, and is
|
|
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).
|
|
199
|
+
|
|
200
|
+
## What's in it
|
|
201
|
+
|
|
202
|
+
- **Inference** of casters, formatters, CLI flags, env var names and
|
|
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)).
|
|
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)).
|
|
207
|
+
- **`--print-invocation`** and `App.format_invocation()`: print the
|
|
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)).
|
|
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)).
|
|
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)).
|
|
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)).
|
|
217
|
+
- **Templates and docs generated from your defaults**:
|
|
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)).
|
|
220
|
+
- Standard library only, fully typed (`py.typed`), Python 3.11+. The
|
|
221
|
+
gitignore check (developer file, strict `.env`) uses the optional
|
|
222
|
+
`pathspec` package.
|
|
223
|
+
|
|
224
|
+
## Install
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
pip install conclude
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The developer config layer (and a `.env` you ask to be held to the same
|
|
231
|
+
standard) needs one small pure-Python dependency, only to check that a
|
|
232
|
+
private file is gitignored:
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
pip install 'conclude[gitignore]'
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Documentation
|
|
239
|
+
|
|
240
|
+
- [Guide](https://github.com/tanakapayam/conclude/blob/main/docs/guide.md) -- builds a small CLI, `remind`, one
|
|
241
|
+
idea at a time.
|
|
242
|
+
- [Reference](https://github.com/tanakapayam/conclude/blob/main/docs/reference.md) -- every class, method, and
|
|
243
|
+
module.
|
|
244
|
+
- [Changelog](https://github.com/tanakapayam/conclude/blob/main/CHANGELOG.md).
|
|
245
|
+
|
|
246
|
+
## Development
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
uv sync
|
|
250
|
+
uv run pytest
|
|
251
|
+
uv run ruff check . && uv run ruff format --check .
|
|
252
|
+
uv run mypy
|
|
253
|
+
uv build && uvx twine check --strict dist/*
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
CI runs these same commands on Python 3.11 through 3.14.
|
|
257
|
+
|
|
258
|
+
## License
|
|
259
|
+
|
|
260
|
+
MIT -- see [LICENSE](https://github.com/tanakapayam/conclude/blob/main/LICENSE).
|