lib-cli-exit-tools 2.3.4__tar.gz → 2.4.1__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.
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.github/workflows/default_cicd_public.yml +42 -6
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.gitignore +8 -5
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/CHANGELOG.md +25 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/Makefile +38 -4
- lib_cli_exit_tools-2.3.4/README.md → lib_cli_exit_tools-2.4.1/PKG-INFO +102 -25
- lib_cli_exit_tools-2.3.4/PKG-INFO → lib_cli_exit_tools-2.4.1/README.md +59 -67
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/pyproject.toml +16 -13
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/src/lib_cli_exit_tools/__init__conf__.py +1 -1
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/src/lib_cli_exit_tools/application/runner.py +52 -18
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/src/lib_cli_exit_tools/cli/styling.py +5 -4
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/tests/test_application_runner.py +84 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/tests/test_cli.py +7 -13
- lib_cli_exit_tools-2.4.1/uv.lock +1968 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.codacy/cli-config.yaml +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.codacy/cli.sh +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.codacy/codacy.yaml +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.codacy/tools-configs/lizard.yaml +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.codacy/tools-configs/trivy.yaml +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.data_arch_violations.json +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.devcontainer/devcontainer.json +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.devcontainer/settings.json +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.env.example +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.github/actions/extract-metadata/action.yml +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.github/dependabot.yml +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.github/workflows/codeql.yml +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.github/workflows/default_release_public.yml +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.qlty/qlty.toml +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.snyk +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/CONTRIBUTING.md +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/DEVELOPMENT.md +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/INSTALL.md +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/LICENSE +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/codecov.yml +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/docs/system-design/module_reference.md +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/notebooks/Quickstart.ipynb +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/src/lib_cli_exit_tools/__init__.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/src/lib_cli_exit_tools/__main__.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/src/lib_cli_exit_tools/adapters/__init__.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/src/lib_cli_exit_tools/adapters/signals.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/src/lib_cli_exit_tools/application/__init__.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/src/lib_cli_exit_tools/cli/__init__.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/src/lib_cli_exit_tools/cli/commands.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/src/lib_cli_exit_tools/cli/group.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/src/lib_cli_exit_tools/cli/typed_click.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/src/lib_cli_exit_tools/core/__init__.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/src/lib_cli_exit_tools/core/configuration.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/src/lib_cli_exit_tools/core/exit_codes.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/src/lib_cli_exit_tools/lib_cli_exit_tools.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/src/lib_cli_exit_tools/py.typed +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/tests/conftest.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/tests/test_adapters_signals.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/tests/test_behaviors.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/tests/test_core_configuration.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/tests/test_core_exit_codes.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/tests/test_metadata.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/tests/test_module_entry.py +0 -0
- {lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/tests/test_signals_integration.py +0 -0
{lib_cli_exit_tools-2.3.4 → lib_cli_exit_tools-2.4.1}/.github/workflows/default_cicd_public.yml
RENAMED
|
@@ -124,9 +124,36 @@ jobs:
|
|
|
124
124
|
print("No CI system dependencies configured for macOS.")
|
|
125
125
|
|
|
126
126
|
- name: Install dev deps
|
|
127
|
-
shell:
|
|
127
|
+
shell: python
|
|
128
128
|
run: |
|
|
129
|
-
|
|
129
|
+
import os, subprocess, tempfile
|
|
130
|
+
from pathlib import Path
|
|
131
|
+
try:
|
|
132
|
+
import tomllib
|
|
133
|
+
except ModuleNotFoundError:
|
|
134
|
+
import pip._vendor.tomli as tomllib
|
|
135
|
+
|
|
136
|
+
# Push and pull_request runs install exactly the tree uv.lock pins - versions
|
|
137
|
+
# plus file hashes, which uv verifies - so CI tests what the lock ships and no
|
|
138
|
+
# release published upstream since the last lock refresh can enter the build.
|
|
139
|
+
# The nightly schedule run resolves afresh instead (still inside the project's
|
|
140
|
+
# [tool.uv] exclude-newer cooldown, which uv honours here): it is the canary
|
|
141
|
+
# that reports upstream breakage before a lock refresh adopts it. A project
|
|
142
|
+
# without a uv.lock keeps the unlocked install.
|
|
143
|
+
with open("pyproject.toml", "rb") as f:
|
|
144
|
+
data = tomllib.load(f)
|
|
145
|
+
# Asking uv for an extra the project does not declare is a hard error.
|
|
146
|
+
extra = ["--extra", "dev"] if "dev" in data.get("project", {}).get("optional-dependencies", {}) else []
|
|
147
|
+
target = ["--system"] # setup-python's interpreter; the job runs no venv
|
|
148
|
+
locked = Path("uv.lock").is_file() and os.environ["GITHUB_EVENT_NAME"] != "schedule"
|
|
149
|
+
if locked:
|
|
150
|
+
req = Path(tempfile.mkdtemp()) / "locked-requirements.txt"
|
|
151
|
+
# --locked fails when pyproject.toml changed without a lock refresh.
|
|
152
|
+
subprocess.check_call(["uv", "export", "--locked", "--no-emit-project", *extra, "-o", str(req), "--quiet"])
|
|
153
|
+
subprocess.check_call(["uv", "pip", "install", *target, "-r", str(req)])
|
|
154
|
+
subprocess.check_call(["uv", "pip", "install", *target, "--no-deps", "-e", "."])
|
|
155
|
+
else:
|
|
156
|
+
subprocess.check_call(["uv", "pip", "install", *target, "-e", ".[dev]" if extra else "."])
|
|
130
157
|
|
|
131
158
|
- name: Install CI system dependencies (Windows/choco)
|
|
132
159
|
if: runner.os == 'Windows'
|
|
@@ -236,7 +263,7 @@ jobs:
|
|
|
236
263
|
continue-on-error: true
|
|
237
264
|
shell: python
|
|
238
265
|
run: |
|
|
239
|
-
import subprocess, sys, tempfile
|
|
266
|
+
import os, subprocess, sys, tempfile
|
|
240
267
|
import rtoml
|
|
241
268
|
from pathlib import Path
|
|
242
269
|
|
|
@@ -263,9 +290,18 @@ jobs:
|
|
|
263
290
|
|
|
264
291
|
tmp = Path(tempfile.mkdtemp())
|
|
265
292
|
req = tmp / "compiled-requirements.txt"
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
293
|
+
# With a uv.lock the install step put exactly the locked tree on the
|
|
294
|
+
# runner, so audit that tree; the schedule canary and lock-less
|
|
295
|
+
# projects resolve pyproject.toml afresh, as their install did.
|
|
296
|
+
if Path("uv.lock").is_file() and os.environ["GITHUB_EVENT_NAME"] != "schedule":
|
|
297
|
+
subprocess.check_call(
|
|
298
|
+
["uv", "export", "--locked", "--no-emit-project", "--no-hashes", *selector,
|
|
299
|
+
"-o", str(req), "--quiet"]
|
|
300
|
+
)
|
|
301
|
+
else:
|
|
302
|
+
subprocess.check_call(
|
|
303
|
+
["uv", "pip", "compile", *selector, "pyproject.toml", "-o", str(req), "--quiet"]
|
|
304
|
+
)
|
|
269
305
|
|
|
270
306
|
# Drop direct URL references ("pkg @ git+https://...") before auditing.
|
|
271
307
|
# pip-audit cannot audit those (they are not on PyPI) but, given a
|
|
@@ -46,7 +46,8 @@ tmp_*
|
|
|
46
46
|
# *.py[cod]: catches .pyc, .pyo, .pyd
|
|
47
47
|
__pycache__/
|
|
48
48
|
*.py[cod]
|
|
49
|
-
|
|
49
|
+
# other compiled artifacts
|
|
50
|
+
*$py.class
|
|
50
51
|
|
|
51
52
|
# pytest, coverage, MyPy, Hypothesis
|
|
52
53
|
.pytest_cache/
|
|
@@ -72,12 +73,15 @@ coverage.xml
|
|
|
72
73
|
# source/package builds
|
|
73
74
|
build/
|
|
74
75
|
dist/
|
|
75
|
-
|
|
76
|
+
# metadata for pip installs
|
|
77
|
+
*.egg-info/
|
|
76
78
|
*.egg
|
|
77
79
|
*.whl
|
|
78
80
|
pip-wheel-metadata/
|
|
79
|
-
|
|
80
|
-
*.
|
|
81
|
+
# PyInstaller metadata
|
|
82
|
+
*.manifest
|
|
83
|
+
# PyInstaller spec files
|
|
84
|
+
*.spec
|
|
81
85
|
|
|
82
86
|
# Eggs, parts, wheels, sdist, etc.
|
|
83
87
|
develop-eggs/
|
|
@@ -177,7 +181,6 @@ result
|
|
|
177
181
|
# Optional lockfiles (for alternative managers)
|
|
178
182
|
poetry.lock
|
|
179
183
|
pdm.lock
|
|
180
|
-
uv.lock
|
|
181
184
|
/.github_bak/
|
|
182
185
|
prompts.md
|
|
183
186
|
CLAUDE.md
|
|
@@ -4,6 +4,31 @@ All notable changes to this project are documented here. The format follows [Kee
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.4.1] 2026-10-12 22:22:57
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Python 3.15 is a supported version: declared in the classifiers, so CI and `make test-all` run it.
|
|
11
|
+
- `uv.lock` is committed, and `[tool.uv] exclude-newer` sets a cooldown that keeps releases younger
|
|
12
|
+
than it out of the lock. CI installs exactly the locked tree on push and pull request; the nightly
|
|
13
|
+
scheduled run still resolves afresh to catch upstream breakage early.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- The install section explains why installing with uv protects against supply-chain attacks and why
|
|
17
|
+
a plain `pip install` from PyPI does not.
|
|
18
|
+
- `rtoml` floor raised to 0.14.0 (dev extra), the first release with Python 3.15 wheels.
|
|
19
|
+
|
|
20
|
+
## [2.4.0] 2026-10-05 20:48:53
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- `cli_session()` now resolves exit codes the way `run_cli()`'s default handler does. Its handler went straight to a traceback summary plus `get_system_exit_code()`, skipping the resolver chain, so under a session a Click usage error (bad flag, unknown command) exited `1` and printed `NoSuchOption: ...` instead of exiting `2` with Click's usage message, a `ClickException` with its own `exit_code` lost it, and the `signal_specs` passed to the session runner were ignored. The session's `summary_limit`/`verbose_limit` still apply to an exception nothing else claims. CLIs built on `cli_session` now report a usage error as `2`, like Click and like `run_cli`.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- `SessionRunner`, a `Protocol` typing the callable `cli_session()` yields. The previous annotation was a union whose first member took only the command, so a type checker rejected `run(cmd, argv=[...])` and callers had to `cast` the session.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- `README.md` is ASCII-only, and its `cli_session` section says how the session's handler resolves exit codes.
|
|
30
|
+
- Raised the dev tool floors.
|
|
31
|
+
|
|
7
32
|
## [2.3.4] 2026-07-29 15:20:59
|
|
8
33
|
|
|
9
34
|
### Fixed
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# BMK MAKEFILE 3.
|
|
1
|
+
# BMK MAKEFILE 3.21.0
|
|
2
2
|
# do not alter this file - it might be overwritten on new versions of BMK
|
|
3
3
|
# if You want to alter it, remove the first line # BMK MAKEFILE 1.0 - then it is a custom makefile and will not be overwritten
|
|
4
4
|
# bmk Makefile - thin wrapper using `uv tool upgrade` for persistent bmk
|
|
@@ -186,16 +186,50 @@ endif
|
|
|
186
186
|
# BMK_MIN floors the FALLBACK install. The upgrade path does not consult it: uv re-resolves
|
|
187
187
|
# the requirement recorded at install time, which is itself a `bmk>=...`, so it always lands
|
|
188
188
|
# on the newest release and clears the floor anyway.
|
|
189
|
-
BMK_MIN := 3.
|
|
189
|
+
BMK_MIN := 3.21.0
|
|
190
190
|
|
|
191
191
|
# Absent env, missing check, or damaged env - all three mean "do not upgrade, rebuild".
|
|
192
192
|
# `test -x` keeps a fresh machine quiet: there is no interpreter to run yet.
|
|
193
193
|
BMK_INTACT := $(if $(BMK_PY),test -x "$(BMK_PY)" && "$(BMK_PY)" -m bmk_selfcheck,false)
|
|
194
194
|
|
|
195
|
+
# The env this recipe mutates is shared by EVERY repo on the machine, so the mutation has to
|
|
196
|
+
# wait for the bmk processes running out of it. Every bmk started from that env holds a
|
|
197
|
+
# SHARED lock on <uv tool dir>/.bmk-tool.lock for its whole lifetime; these wrappers take
|
|
198
|
+
# the same lock EXCLUSIVE for the length of the upgrade. Without it, a make here deleted the
|
|
199
|
+
# site-packages out from under a bmk minutes into a test suite in an UNRELATED repo, and it
|
|
200
|
+
# surfaced as an ImportError inside bmk's OWN dependencies that cleared on a re-run - so it
|
|
201
|
+
# read as a flake rather than as the environment vanishing mid-gate.
|
|
202
|
+
#
|
|
203
|
+
# `bmk_toollock` is a second stdlib-only TOP-LEVEL module bmk ships, run on the tool env's
|
|
204
|
+
# own interpreter. That is not circular: a uv tool env's bin/python is a SYMLINK to a base
|
|
205
|
+
# interpreter OUTSIDE the tool dir, so the stdlib the guard runs on is not part of the tree
|
|
206
|
+
# uv is about to replace, and the guard is read and compiled before uv is ever spawned.
|
|
207
|
+
#
|
|
208
|
+
# NOT uv's own <uv tool dir>/.lock, which uv does honour: `uv tool upgrade` and `uv tool
|
|
209
|
+
# list` block on it too (measured), and the upgrade runs before every target - so a
|
|
210
|
+
# gate-lifetime shared lock there would make every make on the machine, and every unrelated
|
|
211
|
+
# `uv tool install`, wait for the longest-running gate.
|
|
212
|
+
#
|
|
213
|
+
# $(wildcard) rather than a shell test: on a machine with no tool env yet these expand to
|
|
214
|
+
# NOTHING and the install runs bare, which is exactly what a first install needs.
|
|
215
|
+
#
|
|
216
|
+
# The bounded wait is load-bearing in both directions. An unbounded one would serialise
|
|
217
|
+
# every repo's make behind the longest gate on the machine. A SKIPPED upgrade costs nothing
|
|
218
|
+
# (the next make picks it up), so the upgrade skips. The REPAIR may not skip: proceeding
|
|
219
|
+
# would leave a damaged env in place, so it fails and drops through to the retry.
|
|
220
|
+
#
|
|
221
|
+
# The LAST attempt is deliberately NEVER guarded, and a test pins that. The guard lives
|
|
222
|
+
# inside the env being repaired, so on the path where that env is absent or broken the guard
|
|
223
|
+
# may be broken too; an unguarded last resort is what stops a defective guard from making
|
|
224
|
+
# `make` unrunnable in every repo at once. If bmk_toollock ever falls out of the wheel, every
|
|
225
|
+
# make everywhere drops to a full reinstall - there is a packaging test for exactly that.
|
|
226
|
+
BMK_LOCK := $(if $(wildcard $(BMK_PY)),"$(BMK_PY)" -m bmk_toollock --exclusive --timeout 10 --on-timeout skip --,)
|
|
227
|
+
BMK_LOCK_REPAIR := $(if $(wildcard $(BMK_PY)),"$(BMK_PY)" -m bmk_toollock --exclusive --timeout 60 --on-timeout fail --,)
|
|
228
|
+
|
|
195
229
|
.PHONY: _ensure_bmk
|
|
196
230
|
_ensure_bmk:
|
|
197
|
-
@$(BMK_INTACT) && uv tool upgrade bmk \
|
|
198
|
-
|| uv tool install --reinstall --force "bmk>=$(BMK_MIN)" \
|
|
231
|
+
@$(BMK_INTACT) && $(BMK_LOCK) uv tool upgrade bmk \
|
|
232
|
+
|| $(BMK_LOCK_REPAIR) uv tool install --reinstall --force "bmk>=$(BMK_MIN)" \
|
|
199
233
|
|| uv tool install --reinstall --force "bmk>=$(BMK_MIN)"
|
|
200
234
|
|
|
201
235
|
# ──────────────────────────────────────────────────────────────
|
|
@@ -1,3 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: lib_cli_exit_tools
|
|
3
|
+
Version: 2.4.1
|
|
4
|
+
Summary: CLI exit handling helpers: clean signals, exit codes, and error printing
|
|
5
|
+
Project-URL: Homepage, https://github.com/bitranox/lib_cli_exit_tools
|
|
6
|
+
Project-URL: Repository, https://github.com/bitranox/lib_cli_exit_tools.git
|
|
7
|
+
Project-URL: Issues, https://github.com/bitranox/lib_cli_exit_tools/issues
|
|
8
|
+
Author-email: bitranox <bitranox@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: cli,click,errors,exit,signals
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: rich-click>=1.9.9
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: bandit>=1.9.4; extra == 'dev'
|
|
28
|
+
Requires-Dist: build>=1.6.1; extra == 'dev'
|
|
29
|
+
Requires-Dist: click>=8.5.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: coverage[toml]>=7.16.2; extra == 'dev'
|
|
31
|
+
Requires-Dist: hypothesis>=6.168.5; extra == 'dev'
|
|
32
|
+
Requires-Dist: import-linter>=2.15; extra == 'dev'
|
|
33
|
+
Requires-Dist: pip-audit>=2.10.1; extra == 'dev'
|
|
34
|
+
Requires-Dist: pyright[nodejs]>=1.1.414; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-asyncio>=1.4.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest-cov>=7.1.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest>=9.1.1; extra == 'dev'
|
|
38
|
+
Requires-Dist: rtoml>=0.14.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: ruff>=0.17.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: textual>=8.2.8; extra == 'dev'
|
|
41
|
+
Requires-Dist: twine>=7.0.0; extra == 'dev'
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
1
44
|
# lib_cli_exit_tools
|
|
2
45
|
|
|
3
46
|
<!-- Badges -->
|
|
@@ -21,15 +64,15 @@ Small helpers for robust CLI exit handling:
|
|
|
21
64
|
|
|
22
65
|
Building CLI applications that behave correctly in shell pipelines, CI/CD systems, and across platforms is surprisingly tricky. This library solves five common pain points:
|
|
23
66
|
|
|
24
|
-
1. **Correct Exit Codes**
|
|
67
|
+
1. **Correct Exit Codes** - Exceptions are automatically mapped to platform-appropriate exit codes (POSIX errno or BSD sysexits). `FileNotFoundError` returns `2`, `PermissionError` returns `13` on POSIX, and `KeyboardInterrupt` returns `130` - matching shell conventions without manual bookkeeping.
|
|
25
68
|
|
|
26
|
-
2. **Portable Signal Handling**
|
|
69
|
+
2. **Portable Signal Handling** - SIGINT (Ctrl+C), SIGTERM, and Windows SIGBREAK are translated into structured Python exceptions with deterministic exit codes. Your CLI behaves consistently whether terminated by a user, a process manager, or a CI runner.
|
|
27
70
|
|
|
28
|
-
3. **Clean Error Output**
|
|
71
|
+
3. **Clean Error Output** - Uncaught exceptions produce concise, coloured error messages by default. Toggle `--traceback` for full Rich-formatted stack traces during debugging - no code changes required.
|
|
29
72
|
|
|
30
|
-
4. **Pipeline-Friendly**
|
|
73
|
+
4. **Pipeline-Friendly** - `BrokenPipeError` is handled gracefully (exit 141 by default, matching `128 + SIGPIPE`), so piping output to `head` or other truncating tools doesn't produce noisy tracebacks.
|
|
31
74
|
|
|
32
|
-
5. **Zero Boilerplate**
|
|
75
|
+
5. **Zero Boilerplate** - Wrap any Click command with `run_cli()` and get signal handling, exit-code translation, and stream flushing in one call. Configuration is centralised and test-friendly via context managers.
|
|
33
76
|
|
|
34
77
|
## Install
|
|
35
78
|
|
|
@@ -41,6 +84,36 @@ pip install lib_cli_exit_tools
|
|
|
41
84
|
|
|
42
85
|
See [INSTALL.md](INSTALL.md) for editable installs, pipx/uv usage, and troubleshooting tips.
|
|
43
86
|
|
|
87
|
+
### Why we recommend uv: supply-chain protection
|
|
88
|
+
|
|
89
|
+
This repository commits a `uv.lock`. It pins every package the project needs, transitive
|
|
90
|
+
dependencies included, to an exact version and to the hashes of that version's files. The
|
|
91
|
+
`exclude-newer` setting under `[tool.uv]` in `pyproject.toml` adds a cooldown: uv leaves any
|
|
92
|
+
release younger than that out of the lock. CI installs from the lock, so a hijacked release that
|
|
93
|
+
gets pulled again within the cooldown never reaches our builds, and a file whose hash does not
|
|
94
|
+
match is refused.
|
|
95
|
+
|
|
96
|
+
That protection stays in this repository. The installable package declares only lower bounds
|
|
97
|
+
(`>=`) for its dependencies, so a plain `pip install` takes the newest release of every transitive
|
|
98
|
+
dependency at the moment you run it. If one of them was hijacked an hour earlier, you get the
|
|
99
|
+
hijacked version. `pipx install` and `uv tool install` without options do the same.
|
|
100
|
+
|
|
101
|
+
To install the command line tool with a cooldown of your own (uv then skips any release younger
|
|
102
|
+
than 7 days, transitive ones included), or to run the exact tree our CI tests:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
uv tool install --exclude-newer "7 days" lib_cli_exit_tools
|
|
106
|
+
git clone https://github.com/bitranox/lib_cli_exit_tools.git && cd lib_cli_exit_tools && uv sync --locked && uv run lib_cli_exit_tools --help
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
If you depend on lib_cli_exit_tools in your own project, give that project the same cooldown (7 days here, as
|
|
110
|
+
an example) and commit the `uv.lock` that `uv add lib_cli_exit_tools` writes:
|
|
111
|
+
|
|
112
|
+
```toml
|
|
113
|
+
[tool.uv]
|
|
114
|
+
exclude-newer = "7 days"
|
|
115
|
+
```
|
|
116
|
+
|
|
44
117
|
## Usage
|
|
45
118
|
|
|
46
119
|
Console script (all commands map to ``lib_cli_exit_tools.cli:main``):
|
|
@@ -59,11 +132,11 @@ The library installs three equivalent console scripts: `lib-cli-exit-tools` (pri
|
|
|
59
132
|
|
|
60
133
|
### Global Options
|
|
61
134
|
|
|
62
|
-
| Option
|
|
63
|
-
|
|
135
|
+
| Option | Default | Description |
|
|
136
|
+
|----------------------------------|---------|--------------------------------------|
|
|
64
137
|
| `--traceback` / `--no-traceback` | `False` | Show full Python traceback on errors |
|
|
65
|
-
| `--version`
|
|
66
|
-
| `-h`, `--help`
|
|
138
|
+
| `--version` | - | Show program version and exit |
|
|
139
|
+
| `-h`, `--help` | - | Show help message and exit |
|
|
67
140
|
|
|
68
141
|
### Commands
|
|
69
142
|
|
|
@@ -84,7 +157,7 @@ lib-cli-exit-tools --traceback fail # show full traceback
|
|
|
84
157
|
|
|
85
158
|
### Examples
|
|
86
159
|
|
|
87
|
-
The snippets below move from a minimal
|
|
160
|
+
The snippets below move from a minimal "hello world" through a production-ready
|
|
88
161
|
CLI that demonstrates configuration hooks and structured error handling.
|
|
89
162
|
|
|
90
163
|
#### 1. Minimal command (copy-paste ready)
|
|
@@ -181,7 +254,7 @@ def info(settings: Settings) -> None:
|
|
|
181
254
|
@cli.command()
|
|
182
255
|
@click.pass_obj
|
|
183
256
|
def fail(settings: Settings) -> None:
|
|
184
|
-
click.echo("About to fail
|
|
257
|
+
click.echo("About to fail...")
|
|
185
258
|
raise RuntimeError("intentional failure for diagnostics")
|
|
186
259
|
|
|
187
260
|
|
|
@@ -202,7 +275,7 @@ if __name__ == "__main__":
|
|
|
202
275
|
|
|
203
276
|
This version parses `--traceback` early (before Click sees it), then wires
|
|
204
277
|
configuration overrides, custom signal specs, and multiple commands into a
|
|
205
|
-
single composition point
|
|
278
|
+
single composition point - mirroring how a production CLI can layer policy logic
|
|
206
279
|
around Click while still delegating exit-code translation to `lib_cli_exit_tools`.
|
|
207
280
|
|
|
208
281
|
#### 4. Custom signal handlers
|
|
@@ -315,20 +388,20 @@ All configuration lives on the module-level `lib_cli_exit_tools.config` object.
|
|
|
315
388
|
from lib_cli_exit_tools import config
|
|
316
389
|
|
|
317
390
|
config.traceback = True # emit full tracebacks instead of short messages
|
|
318
|
-
config.exit_code_style = "sysexits" # emit BSD-style exit codes (EX_USAGE, EX_NOINPUT,
|
|
391
|
+
config.exit_code_style = "sysexits" # emit BSD-style exit codes (EX_USAGE, EX_NOINPUT, ...)
|
|
319
392
|
config.broken_pipe_exit_code = 0 # treat BrokenPipeError as a benign truncation
|
|
320
393
|
```
|
|
321
394
|
|
|
322
395
|
Field reference:
|
|
323
396
|
|
|
324
|
-
| Field
|
|
325
|
-
|
|
326
|
-
| `traceback`
|
|
327
|
-
| `exit_code_style`
|
|
328
|
-
| `broken_pipe_exit_code` | `int`
|
|
329
|
-
| `traceback_force_color` | `bool`
|
|
397
|
+
| Field | Type | Default | Description |
|
|
398
|
+
|-------------------------|---------------------------|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
399
|
+
| `traceback` | `bool` | `False` | When `True`, `handle_cli_exception` renders a full Rich traceback to stderr. The bundled CLI toggles this via `--traceback/--no-traceback`. |
|
|
400
|
+
| `exit_code_style` | `"errno"` \| `"sysexits"` | `"errno"` | Controls exit code mapping. `errno` returns POSIX/Windows-style codes; `sysexits` returns BSD-style semantic codes (EX_USAGE, EX_NOINPUT, etc.). |
|
|
401
|
+
| `broken_pipe_exit_code` | `int` | `141` | Exit status for `BrokenPipeError` (default mirrors `128 + SIGPIPE`). Set to `0` to treat truncation as success. |
|
|
402
|
+
| `traceback_force_color` | `bool` | `False` | Force Rich to emit ANSI-coloured tracebacks even when stderr is not a TTY. Useful for CI logs. |
|
|
330
403
|
|
|
331
|
-
Remember that `config` is module-level
|
|
404
|
+
Remember that `config` is module-level - if you call the library from multiple threads or embed it in another CLI, configure it once during bootstrap before handing control to user code. When you need temporary overrides (for tests or nested CLIs), wrap the change with the built-in context manager so state is restored automatically:
|
|
332
405
|
|
|
333
406
|
```python
|
|
334
407
|
from lib_cli_exit_tools import config_overrides, config
|
|
@@ -385,13 +458,13 @@ python -m scripts.test --coverage=off --verbose
|
|
|
385
458
|
The suite includes OS-aware cases (POSIX, Windows-specific signal handling), so
|
|
386
459
|
run it on each target platform you support to keep coverage consistent.
|
|
387
460
|
|
|
388
|
-
If your environment reports
|
|
461
|
+
If your environment reports "cannot execute" when running `pytest`, the auto-generated entry-point script likely points at a removed interpreter. Reinstall the dev extras or invoke tests with `python -m pytest` (for example, `python -m pytest tests/`).
|
|
389
462
|
|
|
390
463
|
When coverage uploads are skipped (no Codecov token), `make test` still writes `coverage.xml` and `codecov.xml` to the project root so you can inspect results locally or feed them into other tooling.
|
|
391
464
|
|
|
392
465
|
## Public API Reference
|
|
393
466
|
|
|
394
|
-
The package re-exports the helpers below via `lib_cli_exit_tools.__all__`. Import them directly with `from lib_cli_exit_tools import
|
|
467
|
+
The package re-exports the helpers below via `lib_cli_exit_tools.__all__`. Import them directly with `from lib_cli_exit_tools import ...`.
|
|
395
468
|
|
|
396
469
|
### `config`
|
|
397
470
|
Mutable dataclass-like singleton holding process-wide settings. Configure it during CLI startup.
|
|
@@ -415,8 +488,12 @@ Parameters:
|
|
|
415
488
|
|
|
416
489
|
### `cli_session(*, summary_limit=500, verbose_limit=10_000, overrides=None, restore=True)`
|
|
417
490
|
Context manager that snapshots `lib_cli_exit_tools.config`, optionally
|
|
418
|
-
applies temporary overrides, and yields a callable
|
|
419
|
-
`run_cli
|
|
491
|
+
applies temporary overrides, and yields a `SessionRunner`: a callable with
|
|
492
|
+
`run_cli`'s signature. Its exception handler resolves exit codes exactly like
|
|
493
|
+
`handle_cli_exception` - signals, broken pipes, Click exceptions and `SystemExit`
|
|
494
|
+
keep their own code and message, so a usage error exits `2` with Click's usage
|
|
495
|
+
text - and applies `summary_limit`/`verbose_limit` only to an exception none of
|
|
496
|
+
those claims.
|
|
420
497
|
|
|
421
498
|
Parameters:
|
|
422
499
|
- `summary_limit` (`int`, default `500`): Character budget when tracebacks are disabled.
|
|
@@ -424,7 +501,7 @@ Parameters:
|
|
|
424
501
|
- `overrides` (`Mapping[str, object] | None`, default `None`): Mapping of configuration field/value pairs applied during the session. When `traceback` is supplied and `traceback_force_color` is omitted, colour output is automatically forced.
|
|
425
502
|
- `restore` (`bool`, default `True`): When `True`, configuration state is restored after the session. Set to `False` to leave overrides in place once the context exits.
|
|
426
503
|
|
|
427
|
-
Use it to restore configuration automatically
|
|
504
|
+
Use it to restore configuration automatically - even when the wrapped command
|
|
428
505
|
raises:
|
|
429
506
|
|
|
430
507
|
```python
|
|
@@ -1,45 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: lib_cli_exit_tools
|
|
3
|
-
Version: 2.3.4
|
|
4
|
-
Summary: CLI exit handling helpers: clean signals, exit codes, and error printing
|
|
5
|
-
Project-URL: Homepage, https://github.com/bitranox/lib_cli_exit_tools
|
|
6
|
-
Project-URL: Repository, https://github.com/bitranox/lib_cli_exit_tools.git
|
|
7
|
-
Project-URL: Issues, https://github.com/bitranox/lib_cli_exit_tools/issues
|
|
8
|
-
Author-email: bitranox <bitranox@gmail.com>
|
|
9
|
-
License: MIT
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
Keywords: cli,click,errors,exit,signals
|
|
12
|
-
Classifier: Environment :: Console
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
-
Classifier: Operating System :: OS Independent
|
|
15
|
-
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
-
Classifier: Typing :: Typed
|
|
23
|
-
Requires-Python: >=3.10
|
|
24
|
-
Requires-Dist: rich-click>=1.9.8
|
|
25
|
-
Provides-Extra: dev
|
|
26
|
-
Requires-Dist: bandit>=1.9.4; extra == 'dev'
|
|
27
|
-
Requires-Dist: build>=1.5.0; extra == 'dev'
|
|
28
|
-
Requires-Dist: click>=8.4.2; extra == 'dev'
|
|
29
|
-
Requires-Dist: coverage[toml]>=7.15.2; extra == 'dev'
|
|
30
|
-
Requires-Dist: hypothesis>=6.163.0; extra == 'dev'
|
|
31
|
-
Requires-Dist: import-linter>=2.13; extra == 'dev'
|
|
32
|
-
Requires-Dist: pip-audit>=2.10.1; extra == 'dev'
|
|
33
|
-
Requires-Dist: pyright[nodejs]>=1.1.411; extra == 'dev'
|
|
34
|
-
Requires-Dist: pytest-asyncio>=1.4.0; extra == 'dev'
|
|
35
|
-
Requires-Dist: pytest-cov>=7.1.0; extra == 'dev'
|
|
36
|
-
Requires-Dist: pytest>=9.1.1; extra == 'dev'
|
|
37
|
-
Requires-Dist: rtoml>=0.13.0; extra == 'dev'
|
|
38
|
-
Requires-Dist: ruff>=0.16.0; extra == 'dev'
|
|
39
|
-
Requires-Dist: textual>=8.2.8; extra == 'dev'
|
|
40
|
-
Requires-Dist: twine>=7.0.0; extra == 'dev'
|
|
41
|
-
Description-Content-Type: text/markdown
|
|
42
|
-
|
|
43
1
|
# lib_cli_exit_tools
|
|
44
2
|
|
|
45
3
|
<!-- Badges -->
|
|
@@ -63,15 +21,15 @@ Small helpers for robust CLI exit handling:
|
|
|
63
21
|
|
|
64
22
|
Building CLI applications that behave correctly in shell pipelines, CI/CD systems, and across platforms is surprisingly tricky. This library solves five common pain points:
|
|
65
23
|
|
|
66
|
-
1. **Correct Exit Codes**
|
|
24
|
+
1. **Correct Exit Codes** - Exceptions are automatically mapped to platform-appropriate exit codes (POSIX errno or BSD sysexits). `FileNotFoundError` returns `2`, `PermissionError` returns `13` on POSIX, and `KeyboardInterrupt` returns `130` - matching shell conventions without manual bookkeeping.
|
|
67
25
|
|
|
68
|
-
2. **Portable Signal Handling**
|
|
26
|
+
2. **Portable Signal Handling** - SIGINT (Ctrl+C), SIGTERM, and Windows SIGBREAK are translated into structured Python exceptions with deterministic exit codes. Your CLI behaves consistently whether terminated by a user, a process manager, or a CI runner.
|
|
69
27
|
|
|
70
|
-
3. **Clean Error Output**
|
|
28
|
+
3. **Clean Error Output** - Uncaught exceptions produce concise, coloured error messages by default. Toggle `--traceback` for full Rich-formatted stack traces during debugging - no code changes required.
|
|
71
29
|
|
|
72
|
-
4. **Pipeline-Friendly**
|
|
30
|
+
4. **Pipeline-Friendly** - `BrokenPipeError` is handled gracefully (exit 141 by default, matching `128 + SIGPIPE`), so piping output to `head` or other truncating tools doesn't produce noisy tracebacks.
|
|
73
31
|
|
|
74
|
-
5. **Zero Boilerplate**
|
|
32
|
+
5. **Zero Boilerplate** - Wrap any Click command with `run_cli()` and get signal handling, exit-code translation, and stream flushing in one call. Configuration is centralised and test-friendly via context managers.
|
|
75
33
|
|
|
76
34
|
## Install
|
|
77
35
|
|
|
@@ -83,6 +41,36 @@ pip install lib_cli_exit_tools
|
|
|
83
41
|
|
|
84
42
|
See [INSTALL.md](INSTALL.md) for editable installs, pipx/uv usage, and troubleshooting tips.
|
|
85
43
|
|
|
44
|
+
### Why we recommend uv: supply-chain protection
|
|
45
|
+
|
|
46
|
+
This repository commits a `uv.lock`. It pins every package the project needs, transitive
|
|
47
|
+
dependencies included, to an exact version and to the hashes of that version's files. The
|
|
48
|
+
`exclude-newer` setting under `[tool.uv]` in `pyproject.toml` adds a cooldown: uv leaves any
|
|
49
|
+
release younger than that out of the lock. CI installs from the lock, so a hijacked release that
|
|
50
|
+
gets pulled again within the cooldown never reaches our builds, and a file whose hash does not
|
|
51
|
+
match is refused.
|
|
52
|
+
|
|
53
|
+
That protection stays in this repository. The installable package declares only lower bounds
|
|
54
|
+
(`>=`) for its dependencies, so a plain `pip install` takes the newest release of every transitive
|
|
55
|
+
dependency at the moment you run it. If one of them was hijacked an hour earlier, you get the
|
|
56
|
+
hijacked version. `pipx install` and `uv tool install` without options do the same.
|
|
57
|
+
|
|
58
|
+
To install the command line tool with a cooldown of your own (uv then skips any release younger
|
|
59
|
+
than 7 days, transitive ones included), or to run the exact tree our CI tests:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uv tool install --exclude-newer "7 days" lib_cli_exit_tools
|
|
63
|
+
git clone https://github.com/bitranox/lib_cli_exit_tools.git && cd lib_cli_exit_tools && uv sync --locked && uv run lib_cli_exit_tools --help
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
If you depend on lib_cli_exit_tools in your own project, give that project the same cooldown (7 days here, as
|
|
67
|
+
an example) and commit the `uv.lock` that `uv add lib_cli_exit_tools` writes:
|
|
68
|
+
|
|
69
|
+
```toml
|
|
70
|
+
[tool.uv]
|
|
71
|
+
exclude-newer = "7 days"
|
|
72
|
+
```
|
|
73
|
+
|
|
86
74
|
## Usage
|
|
87
75
|
|
|
88
76
|
Console script (all commands map to ``lib_cli_exit_tools.cli:main``):
|
|
@@ -101,11 +89,11 @@ The library installs three equivalent console scripts: `lib-cli-exit-tools` (pri
|
|
|
101
89
|
|
|
102
90
|
### Global Options
|
|
103
91
|
|
|
104
|
-
| Option
|
|
105
|
-
|
|
92
|
+
| Option | Default | Description |
|
|
93
|
+
|----------------------------------|---------|--------------------------------------|
|
|
106
94
|
| `--traceback` / `--no-traceback` | `False` | Show full Python traceback on errors |
|
|
107
|
-
| `--version`
|
|
108
|
-
| `-h`, `--help`
|
|
95
|
+
| `--version` | - | Show program version and exit |
|
|
96
|
+
| `-h`, `--help` | - | Show help message and exit |
|
|
109
97
|
|
|
110
98
|
### Commands
|
|
111
99
|
|
|
@@ -126,7 +114,7 @@ lib-cli-exit-tools --traceback fail # show full traceback
|
|
|
126
114
|
|
|
127
115
|
### Examples
|
|
128
116
|
|
|
129
|
-
The snippets below move from a minimal
|
|
117
|
+
The snippets below move from a minimal "hello world" through a production-ready
|
|
130
118
|
CLI that demonstrates configuration hooks and structured error handling.
|
|
131
119
|
|
|
132
120
|
#### 1. Minimal command (copy-paste ready)
|
|
@@ -223,7 +211,7 @@ def info(settings: Settings) -> None:
|
|
|
223
211
|
@cli.command()
|
|
224
212
|
@click.pass_obj
|
|
225
213
|
def fail(settings: Settings) -> None:
|
|
226
|
-
click.echo("About to fail
|
|
214
|
+
click.echo("About to fail...")
|
|
227
215
|
raise RuntimeError("intentional failure for diagnostics")
|
|
228
216
|
|
|
229
217
|
|
|
@@ -244,7 +232,7 @@ if __name__ == "__main__":
|
|
|
244
232
|
|
|
245
233
|
This version parses `--traceback` early (before Click sees it), then wires
|
|
246
234
|
configuration overrides, custom signal specs, and multiple commands into a
|
|
247
|
-
single composition point
|
|
235
|
+
single composition point - mirroring how a production CLI can layer policy logic
|
|
248
236
|
around Click while still delegating exit-code translation to `lib_cli_exit_tools`.
|
|
249
237
|
|
|
250
238
|
#### 4. Custom signal handlers
|
|
@@ -357,20 +345,20 @@ All configuration lives on the module-level `lib_cli_exit_tools.config` object.
|
|
|
357
345
|
from lib_cli_exit_tools import config
|
|
358
346
|
|
|
359
347
|
config.traceback = True # emit full tracebacks instead of short messages
|
|
360
|
-
config.exit_code_style = "sysexits" # emit BSD-style exit codes (EX_USAGE, EX_NOINPUT,
|
|
348
|
+
config.exit_code_style = "sysexits" # emit BSD-style exit codes (EX_USAGE, EX_NOINPUT, ...)
|
|
361
349
|
config.broken_pipe_exit_code = 0 # treat BrokenPipeError as a benign truncation
|
|
362
350
|
```
|
|
363
351
|
|
|
364
352
|
Field reference:
|
|
365
353
|
|
|
366
|
-
| Field
|
|
367
|
-
|
|
368
|
-
| `traceback`
|
|
369
|
-
| `exit_code_style`
|
|
370
|
-
| `broken_pipe_exit_code` | `int`
|
|
371
|
-
| `traceback_force_color` | `bool`
|
|
354
|
+
| Field | Type | Default | Description |
|
|
355
|
+
|-------------------------|---------------------------|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
356
|
+
| `traceback` | `bool` | `False` | When `True`, `handle_cli_exception` renders a full Rich traceback to stderr. The bundled CLI toggles this via `--traceback/--no-traceback`. |
|
|
357
|
+
| `exit_code_style` | `"errno"` \| `"sysexits"` | `"errno"` | Controls exit code mapping. `errno` returns POSIX/Windows-style codes; `sysexits` returns BSD-style semantic codes (EX_USAGE, EX_NOINPUT, etc.). |
|
|
358
|
+
| `broken_pipe_exit_code` | `int` | `141` | Exit status for `BrokenPipeError` (default mirrors `128 + SIGPIPE`). Set to `0` to treat truncation as success. |
|
|
359
|
+
| `traceback_force_color` | `bool` | `False` | Force Rich to emit ANSI-coloured tracebacks even when stderr is not a TTY. Useful for CI logs. |
|
|
372
360
|
|
|
373
|
-
Remember that `config` is module-level
|
|
361
|
+
Remember that `config` is module-level - if you call the library from multiple threads or embed it in another CLI, configure it once during bootstrap before handing control to user code. When you need temporary overrides (for tests or nested CLIs), wrap the change with the built-in context manager so state is restored automatically:
|
|
374
362
|
|
|
375
363
|
```python
|
|
376
364
|
from lib_cli_exit_tools import config_overrides, config
|
|
@@ -427,13 +415,13 @@ python -m scripts.test --coverage=off --verbose
|
|
|
427
415
|
The suite includes OS-aware cases (POSIX, Windows-specific signal handling), so
|
|
428
416
|
run it on each target platform you support to keep coverage consistent.
|
|
429
417
|
|
|
430
|
-
If your environment reports
|
|
418
|
+
If your environment reports "cannot execute" when running `pytest`, the auto-generated entry-point script likely points at a removed interpreter. Reinstall the dev extras or invoke tests with `python -m pytest` (for example, `python -m pytest tests/`).
|
|
431
419
|
|
|
432
420
|
When coverage uploads are skipped (no Codecov token), `make test` still writes `coverage.xml` and `codecov.xml` to the project root so you can inspect results locally or feed them into other tooling.
|
|
433
421
|
|
|
434
422
|
## Public API Reference
|
|
435
423
|
|
|
436
|
-
The package re-exports the helpers below via `lib_cli_exit_tools.__all__`. Import them directly with `from lib_cli_exit_tools import
|
|
424
|
+
The package re-exports the helpers below via `lib_cli_exit_tools.__all__`. Import them directly with `from lib_cli_exit_tools import ...`.
|
|
437
425
|
|
|
438
426
|
### `config`
|
|
439
427
|
Mutable dataclass-like singleton holding process-wide settings. Configure it during CLI startup.
|
|
@@ -457,8 +445,12 @@ Parameters:
|
|
|
457
445
|
|
|
458
446
|
### `cli_session(*, summary_limit=500, verbose_limit=10_000, overrides=None, restore=True)`
|
|
459
447
|
Context manager that snapshots `lib_cli_exit_tools.config`, optionally
|
|
460
|
-
applies temporary overrides, and yields a callable
|
|
461
|
-
`run_cli
|
|
448
|
+
applies temporary overrides, and yields a `SessionRunner`: a callable with
|
|
449
|
+
`run_cli`'s signature. Its exception handler resolves exit codes exactly like
|
|
450
|
+
`handle_cli_exception` - signals, broken pipes, Click exceptions and `SystemExit`
|
|
451
|
+
keep their own code and message, so a usage error exits `2` with Click's usage
|
|
452
|
+
text - and applies `summary_limit`/`verbose_limit` only to an exception none of
|
|
453
|
+
those claims.
|
|
462
454
|
|
|
463
455
|
Parameters:
|
|
464
456
|
- `summary_limit` (`int`, default `500`): Character budget when tracebacks are disabled.
|
|
@@ -466,7 +458,7 @@ Parameters:
|
|
|
466
458
|
- `overrides` (`Mapping[str, object] | None`, default `None`): Mapping of configuration field/value pairs applied during the session. When `traceback` is supplied and `traceback_force_color` is omitted, colour output is automatically forced.
|
|
467
459
|
- `restore` (`bool`, default `True`): When `True`, configuration state is restored after the session. Set to `False` to leave overrides in place once the context exits.
|
|
468
460
|
|
|
469
|
-
Use it to restore configuration automatically
|
|
461
|
+
Use it to restore configuration automatically - even when the wrapped command
|
|
470
462
|
raises:
|
|
471
463
|
|
|
472
464
|
```python
|