lib-cli-exit-tools 2.1.1__tar.gz → 2.2.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.
Files changed (76) hide show
  1. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/.github/workflows/ci.yml +11 -3
  2. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/CHANGELOG.md +23 -0
  3. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/INSTALL.md +1 -1
  4. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/PKG-INFO +76 -26
  5. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/README.md +72 -23
  6. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/pyproject.toml +15 -5
  7. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/src/lib_cli_exit_tools/__init__conf__.py +1 -1
  8. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/tests/test_metadata.py +6 -1
  9. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/.codacy/cli-config.yaml +0 -0
  10. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/.codacy/cli.sh +0 -0
  11. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/.codacy/codacy.yaml +0 -0
  12. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/.codacy/tools-configs/lizard.yaml +0 -0
  13. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/.codacy/tools-configs/trivy.yaml +0 -0
  14. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/.devcontainer/devcontainer.json +0 -0
  15. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/.devcontainer/settings.json +0 -0
  16. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/.env.example +0 -0
  17. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/.github/dependabot.yml +0 -0
  18. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/.github/workflows/codeql.yml +0 -0
  19. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/.github/workflows/release.yml +0 -0
  20. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/.gitignore +0 -0
  21. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/.qlty/qlty.toml +0 -0
  22. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/.snyk +0 -0
  23. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/AGENTS.md +0 -0
  24. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/AGENTS_README.md +0 -0
  25. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/CONTRIBUTING.md +0 -0
  26. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/DEVELOPMENT.md +0 -0
  27. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/LICENSE +0 -0
  28. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/Makefile +0 -0
  29. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/codecov.yml +0 -0
  30. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/docs/system-design/module_reference.md +0 -0
  31. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/notebooks/Quickstart.ipynb +0 -0
  32. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/__init__.py +0 -0
  33. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/__main__.py +0 -0
  34. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/_utils.py +0 -0
  35. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/build.py +0 -0
  36. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/bump.py +0 -0
  37. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/bump_major.py +0 -0
  38. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/bump_minor.py +0 -0
  39. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/bump_patch.py +0 -0
  40. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/bump_version.py +0 -0
  41. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/clean.py +0 -0
  42. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/cli.py +0 -0
  43. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/dependencies.py +0 -0
  44. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/dev.py +0 -0
  45. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/help.py +0 -0
  46. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/install.py +0 -0
  47. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/menu.py +0 -0
  48. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/push.py +0 -0
  49. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/release.py +0 -0
  50. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/run_cli.py +0 -0
  51. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/target_metadata.py +0 -0
  52. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/test.py +0 -0
  53. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/toml_config.py +0 -0
  54. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/scripts/version_current.py +0 -0
  55. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/src/lib_cli_exit_tools/__init__.py +0 -0
  56. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/src/lib_cli_exit_tools/__main__.py +0 -0
  57. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/src/lib_cli_exit_tools/adapters/__init__.py +0 -0
  58. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/src/lib_cli_exit_tools/adapters/signals.py +0 -0
  59. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/src/lib_cli_exit_tools/application/__init__.py +0 -0
  60. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/src/lib_cli_exit_tools/application/runner.py +0 -0
  61. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/src/lib_cli_exit_tools/cli.py +0 -0
  62. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/src/lib_cli_exit_tools/core/__init__.py +0 -0
  63. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/src/lib_cli_exit_tools/core/configuration.py +0 -0
  64. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/src/lib_cli_exit_tools/core/exit_codes.py +0 -0
  65. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/src/lib_cli_exit_tools/lib_cli_exit_tools.py +0 -0
  66. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/src/lib_cli_exit_tools/py.typed +0 -0
  67. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/tests/conftest.py +0 -0
  68. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/tests/test_adapters_signals.py +0 -0
  69. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/tests/test_application_runner.py +0 -0
  70. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/tests/test_behaviors.py +0 -0
  71. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/tests/test_cli.py +0 -0
  72. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/tests/test_core_configuration.py +0 -0
  73. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/tests/test_core_exit_codes.py +0 -0
  74. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/tests/test_module_entry.py +0 -0
  75. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/tests/test_scripts.py +0 -0
  76. {lib_cli_exit_tools-2.1.1 → lib_cli_exit_tools-2.2.1}/tests/test_signals_integration.py +0 -0
@@ -18,7 +18,7 @@ jobs:
18
18
  matrix:
19
19
  os: [ubuntu-latest, macos-latest, windows-latest]
20
20
  # Test against Python 3.13 and the latest 3.x available on Actions runners
21
- python: ["3.13", "3.x"]
21
+ python: ["3.10","3.11","3.12","3.13", "3.x"]
22
22
  env:
23
23
  PYO3_USE_ABI3_FORWARD_COMPATIBILITY: "1"
24
24
  PYTHONIOENCODING: "utf-8"
@@ -37,9 +37,13 @@ jobs:
37
37
  shell: python
38
38
  run: |
39
39
  import os
40
- import tomllib
41
40
  from pathlib import Path
42
41
 
42
+ try:
43
+ import tomllib
44
+ except ModuleNotFoundError:
45
+ import pip._vendor.tomli as tomllib
46
+
43
47
  data = tomllib.loads(Path('pyproject.toml').read_text('utf-8'))
44
48
  project = data['project']['name']
45
49
  module = project.replace('-', '_')
@@ -123,9 +127,13 @@ jobs:
123
127
  shell: python
124
128
  run: |
125
129
  import os
126
- import tomllib
127
130
  from pathlib import Path
128
131
 
132
+ try:
133
+ import tomllib
134
+ except ModuleNotFoundError:
135
+ import pip._vendor.tomli as tomllib
136
+
129
137
  data = tomllib.loads(Path('pyproject.toml').read_text('utf-8'))
130
138
  project = data['project']['name']
131
139
  module = project.replace('-', '_')
@@ -3,6 +3,29 @@
3
3
  All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
4
 
5
5
 
6
+ ## [2.2.1] - 2025-12-11
7
+
8
+ ### Added
9
+ - "Why lib_cli_exit_tools?" section in README highlighting five key benefits: correct exit codes, portable signal handling, clean error output, pipeline-friendly behaviour, and zero boilerplate.
10
+ - CLI Reference section in README with global options table and command descriptions.
11
+
12
+ ### Changed
13
+ - Replaced all `--verbose` references in README examples with `--traceback` to match actual CLI option.
14
+ - Updated `cli_session` documentation to include missing `restore` parameter.
15
+ - Converted config field reference to table format with types and defaults.
16
+
17
+ ### Fixed
18
+ - Added missing `traceback_force_color` configuration field to documentation.
19
+
20
+ ## [2.2.0] - 2025-12-11
21
+
22
+ ### Changed
23
+ - Lowered minimum Python version from 3.13 to 3.10 for broader compatibility.
24
+ - Updated ruff target-version to py310 to match the new baseline.
25
+
26
+ ### Security
27
+ - Added setuptools vulnerability IDs (PYSEC-2022-43012, CVE-2024-6345, PYSEC-2025-49) to pip-audit ignore list. These are build-time dependencies managed by the CI environment, not runtime dependencies.
28
+
6
29
  ## [2.1.1] - 2025-12-08
7
30
 
8
31
  ### Changed
@@ -27,7 +27,7 @@ The sections below cover more specialised workflows, from editable installs to a
27
27
 
28
28
  ## Supported Python Versions
29
29
 
30
- `lib_cli_exit_tools` targets Python 3.13 and newer. Wheels are published for CPython on Linux, macOS, and Windows. For alternative interpreters (PyPy) install from source using `pip install lib_cli_exit_tools`.
30
+ `lib_cli_exit_tools` targets Python 3.10 and newer. Wheels are published for CPython on Linux, macOS, and Windows. For alternative interpreters (PyPy) install from source using `pip install lib_cli_exit_tools`.
31
31
 
32
32
  ## Installation Flows
33
33
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lib_cli_exit_tools
3
- Version: 2.1.1
3
+ Version: 2.2.1
4
4
  Summary: CLI exit handling helpers: clean signals, exit codes, and error printing
5
5
  Project-URL: Homepage, https://github.com/bitranox/lib_cli_exit_tools
6
6
  Project-URL: Repository, https://github.com/bitranox/lib_cli_exit_tools.git
@@ -15,7 +15,7 @@ Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python :: 3
16
16
  Classifier: Programming Language :: Python :: 3 :: Only
17
17
  Classifier: Typing :: Typed
18
- Requires-Python: >=3.13
18
+ Requires-Python: >=3.10
19
19
  Requires-Dist: rich-click>=1.9.4
20
20
  Provides-Extra: dev
21
21
  Requires-Dist: bandit>=1.9.2; extra == 'dev'
@@ -23,7 +23,7 @@ Requires-Dist: build>=1.3.0; extra == 'dev'
23
23
  Requires-Dist: codecov-cli>=11.2.6; extra == 'dev'
24
24
  Requires-Dist: coverage[toml]>=7.13.0; extra == 'dev'
25
25
  Requires-Dist: hypothesis>=6.148.7; extra == 'dev'
26
- Requires-Dist: import-linter>=2.7; extra == 'dev'
26
+ Requires-Dist: import-linter>=2.9; extra == 'dev'
27
27
  Requires-Dist: pip-audit>=2.10.0; extra == 'dev'
28
28
  Requires-Dist: pyright>=1.1.407; extra == 'dev'
29
29
  Requires-Dist: pytest-asyncio>=1.3.0; extra == 'dev'
@@ -31,6 +31,7 @@ Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
31
31
  Requires-Dist: pytest>=9.0.2; extra == 'dev'
32
32
  Requires-Dist: ruff>=0.14.8; extra == 'dev'
33
33
  Requires-Dist: textual>=6.8.0; extra == 'dev'
34
+ Requires-Dist: tomli>=2.3.0; (python_version < '3.11') and extra == 'dev'
34
35
  Requires-Dist: twine>=6.2.0; extra == 'dev'
35
36
  Description-Content-Type: text/markdown
36
37
 
@@ -54,9 +55,23 @@ Small helpers for robust CLI exit handling:
54
55
  - Consistent exception → exit code mapping
55
56
  - Concise error printing with optional traceback and subprocess stdout/stderr capture
56
57
 
58
+ ## Why lib_cli_exit_tools?
59
+
60
+ 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:
61
+
62
+ 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.
63
+
64
+ 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.
65
+
66
+ 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.
67
+
68
+ 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.
69
+
70
+ 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.
71
+
57
72
  ## Install
58
73
 
59
- Requires Python 3.13 or newer.
74
+ Requires Python 3.10 or newer.
60
75
 
61
76
  ```bash
62
77
  pip install lib_cli_exit_tools
@@ -76,6 +91,35 @@ lib-cli-exit-tools fail # intentionally trigger RuntimeError to test error path
76
91
  # Aliases are also generated: `cli-exit-tools`, `lib_cli_exit_tools`
77
92
  ```
78
93
 
94
+ ## CLI Reference
95
+
96
+ The library installs three equivalent console scripts: `lib-cli-exit-tools` (primary), `cli-exit-tools`, and `lib_cli_exit_tools`. All invoke `lib_cli_exit_tools.cli:main`.
97
+
98
+ ### Global Options
99
+
100
+ | Option | Default | Description |
101
+ |--------|---------|-------------|
102
+ | `--traceback` / `--no-traceback` | `False` | Show full Python traceback on errors |
103
+ | `--version` | — | Show program version and exit |
104
+ | `-h`, `--help` | — | Show help message and exit |
105
+
106
+ ### Commands
107
+
108
+ #### `info`
109
+ Display package metadata (name, version, homepage, author).
110
+
111
+ ```bash
112
+ lib-cli-exit-tools info
113
+ ```
114
+
115
+ #### `fail`
116
+ Intentionally raise `RuntimeError('i should fail')` to validate error handling paths.
117
+
118
+ ```bash
119
+ lib-cli-exit-tools fail
120
+ lib-cli-exit-tools --traceback fail # show full traceback
121
+ ```
122
+
79
123
  ### Examples
80
124
 
81
125
  The snippets below move from a minimal “hello world” through a production-ready
@@ -108,7 +152,7 @@ Run it with:
108
152
  python hello.py
109
153
  ```
110
154
 
111
- #### 2. Verbose-mode helper using `cli_session`
155
+ #### 2. Traceback-mode helper using `cli_session`
112
156
 
113
157
  ```python
114
158
  from __future__ import annotations
@@ -119,22 +163,22 @@ from lib_cli_exit_tools import cli_session, run_cli
119
163
 
120
164
 
121
165
  @click.command()
122
- @click.option("--verbose", is_flag=True, help="Show full tracebacks on error")
123
- def cli(verbose: bool) -> int:
124
- with cli_session(overrides={"traceback": verbose}) as execute:
166
+ @click.option("--traceback", is_flag=True, help="Show full tracebacks on error")
167
+ def cli(traceback: bool) -> int:
168
+ with cli_session(overrides={"traceback": traceback}) as execute:
125
169
  return execute(failable, argv=[])
126
170
 
127
171
 
128
172
  @click.command()
129
173
  def failable() -> None:
130
- raise RuntimeError("toggle --verbose to see the full traceback")
174
+ raise RuntimeError("toggle --traceback to see the full traceback")
131
175
 
132
176
 
133
177
  if __name__ == "__main__":
134
178
  raise SystemExit(run_cli(cli))
135
179
  ```
136
180
 
137
- Invoking `python cli.py --verbose` enables coloured tracebacks just for that
181
+ Invoking `python cli.py --traceback` enables coloured tracebacks just for that
138
182
  run and resets the configuration afterwards.
139
183
 
140
184
  #### 3. Full-featured multi-command CLI
@@ -153,21 +197,21 @@ from lib_cli_exit_tools import SignalSpec, cli_session, default_signal_specs
153
197
  @dataclass(slots=True)
154
198
  class Settings:
155
199
  pretty: bool
156
- verbose: bool
200
+ traceback: bool
157
201
 
158
202
 
159
203
  @click.group()
160
204
  @click.option("--pretty/--no-pretty", default=True)
161
- @click.option("--verbose", is_flag=True)
205
+ @click.option("--traceback", is_flag=True, help="Show full tracebacks on error")
162
206
  @click.pass_context
163
- def cli(ctx: click.Context, pretty: bool, verbose: bool) -> None:
164
- ctx.obj = Settings(pretty=pretty, verbose=verbose)
207
+ def cli(ctx: click.Context, pretty: bool, traceback: bool) -> None:
208
+ ctx.obj = Settings(pretty=pretty, traceback=traceback)
165
209
 
166
210
 
167
211
  @cli.command()
168
212
  @click.pass_obj
169
213
  def info(settings: Settings) -> None:
170
- payload = {"verbose": settings.verbose, "pretty": settings.pretty}
214
+ payload = {"traceback": settings.traceback, "pretty": settings.pretty}
171
215
  click.echo(json.dumps(payload, indent=2 if settings.pretty else None))
172
216
 
173
217
 
@@ -307,9 +351,12 @@ config.broken_pipe_exit_code = 0 # treat BrokenPipeError as a benign truncat
307
351
 
308
352
  Field reference:
309
353
 
310
- - `traceback` (`bool`, default `False`): when `True`, `handle_cli_exception` renders a full Rich traceback to stderr and then returns a non-zero exit code. The original exception is not re-raised; callers should rely on the rendered output and returned status. The bundled CLI toggles this via `--traceback/--no-traceback`.
311
- - `exit_code_style` (`"errno"` or `"sysexits"`, default `"errno"`): controls the numeric mapping produced by `get_system_exit_code`. `errno` returns POSIX/Windows-style codes (e.g., `FileNotFoundError → 2`, `SIGINT → 130`); `sysexits` returns BSD-style semantic codes (`EX_NOINPUT`, `EX_USAGE`, etc.).
312
- - `broken_pipe_exit_code` (`int`, default `141`): overrides the exit status when a `BrokenPipeError` is raised (the default mirrors `128 + SIGPIPE`). Set this to `0` if you want truncation to be treated as success.
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. |
313
360
 
314
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:
315
362
 
@@ -396,22 +443,25 @@ Parameters:
396
443
  - `exception_handler`: Callable receiving the raised exception and returning an exit code; defaults to ``handle_cli_exception``.
397
444
  - `signal_installer`: Callable mirroring ``install_signal_handlers`` for embedding scenarios.
398
445
 
399
- ### `cli_session(*, summary_limit=500, verbose_limit=10_000, overrides=None)`
400
- Context manager that snapshots :mod:`lib_cli_exit_tools.config`, optionally
446
+ ### `cli_session(*, summary_limit=500, verbose_limit=10_000, overrides=None, restore=True)`
447
+ Context manager that snapshots `lib_cli_exit_tools.config`, optionally
401
448
  applies temporary overrides, and yields a callable compatible with
402
- ``run_cli``.
449
+ `run_cli`.
403
450
 
404
451
  Parameters:
405
- - `summary_limit`: Character budget when tracebacks are disabled.
406
- - `verbose_limit`: Character budget when tracebacks are enabled.
407
- - `overrides`: Mapping of configuration field/value pairs applied during the session.
452
+ - `summary_limit` (`int`, default `500`): Character budget when tracebacks are disabled.
453
+ - `verbose_limit` (`int`, default `10_000`): Character budget when tracebacks are enabled.
454
+ - `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.
455
+ - `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.
408
456
 
409
457
  Use it to restore configuration automatically—even when the wrapped command
410
458
  raises:
411
459
 
412
460
  ```python
461
+ from lib_cli_exit_tools import cli_session
462
+
413
463
  with cli_session(overrides={"traceback": True}) as run:
414
- exit_code = run(click_command, argv=args)
464
+ exit_code = run(my_click_command, argv=["--help"])
415
465
  ```
416
466
 
417
467
  ### `handle_cli_exception(exc, *, signal_specs=None, echo=None) -> int`
@@ -582,7 +632,7 @@ When installed, the generated console scripts (`lib-cli-exit-tools`, `cli-exit-t
582
632
 
583
633
  ## Modern Python Toolchain
584
634
 
585
- - Targets Python 3.13+ exclusively—no runtime shims or compatibility branches remain.
635
+ - Targets Python 3.10+ for modern language features and broad compatibility.
586
636
  - Development extras track the latest stable releases published on PyPI so quality gates match local and CI environments.
587
637
  - GitHub Actions workflows rely on the current major releases of `actions/checkout`, `actions/setup-python`, and `astral-sh/setup-uv`, aligning the automation stack with the 2025 runner images.
588
638
 
@@ -18,9 +18,23 @@ Small helpers for robust CLI exit handling:
18
18
  - Consistent exception → exit code mapping
19
19
  - Concise error printing with optional traceback and subprocess stdout/stderr capture
20
20
 
21
+ ## Why lib_cli_exit_tools?
22
+
23
+ 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:
24
+
25
+ 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.
26
+
27
+ 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.
28
+
29
+ 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.
30
+
31
+ 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.
32
+
33
+ 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.
34
+
21
35
  ## Install
22
36
 
23
- Requires Python 3.13 or newer.
37
+ Requires Python 3.10 or newer.
24
38
 
25
39
  ```bash
26
40
  pip install lib_cli_exit_tools
@@ -40,6 +54,35 @@ lib-cli-exit-tools fail # intentionally trigger RuntimeError to test error path
40
54
  # Aliases are also generated: `cli-exit-tools`, `lib_cli_exit_tools`
41
55
  ```
42
56
 
57
+ ## CLI Reference
58
+
59
+ The library installs three equivalent console scripts: `lib-cli-exit-tools` (primary), `cli-exit-tools`, and `lib_cli_exit_tools`. All invoke `lib_cli_exit_tools.cli:main`.
60
+
61
+ ### Global Options
62
+
63
+ | Option | Default | Description |
64
+ |--------|---------|-------------|
65
+ | `--traceback` / `--no-traceback` | `False` | Show full Python traceback on errors |
66
+ | `--version` | — | Show program version and exit |
67
+ | `-h`, `--help` | — | Show help message and exit |
68
+
69
+ ### Commands
70
+
71
+ #### `info`
72
+ Display package metadata (name, version, homepage, author).
73
+
74
+ ```bash
75
+ lib-cli-exit-tools info
76
+ ```
77
+
78
+ #### `fail`
79
+ Intentionally raise `RuntimeError('i should fail')` to validate error handling paths.
80
+
81
+ ```bash
82
+ lib-cli-exit-tools fail
83
+ lib-cli-exit-tools --traceback fail # show full traceback
84
+ ```
85
+
43
86
  ### Examples
44
87
 
45
88
  The snippets below move from a minimal “hello world” through a production-ready
@@ -72,7 +115,7 @@ Run it with:
72
115
  python hello.py
73
116
  ```
74
117
 
75
- #### 2. Verbose-mode helper using `cli_session`
118
+ #### 2. Traceback-mode helper using `cli_session`
76
119
 
77
120
  ```python
78
121
  from __future__ import annotations
@@ -83,22 +126,22 @@ from lib_cli_exit_tools import cli_session, run_cli
83
126
 
84
127
 
85
128
  @click.command()
86
- @click.option("--verbose", is_flag=True, help="Show full tracebacks on error")
87
- def cli(verbose: bool) -> int:
88
- with cli_session(overrides={"traceback": verbose}) as execute:
129
+ @click.option("--traceback", is_flag=True, help="Show full tracebacks on error")
130
+ def cli(traceback: bool) -> int:
131
+ with cli_session(overrides={"traceback": traceback}) as execute:
89
132
  return execute(failable, argv=[])
90
133
 
91
134
 
92
135
  @click.command()
93
136
  def failable() -> None:
94
- raise RuntimeError("toggle --verbose to see the full traceback")
137
+ raise RuntimeError("toggle --traceback to see the full traceback")
95
138
 
96
139
 
97
140
  if __name__ == "__main__":
98
141
  raise SystemExit(run_cli(cli))
99
142
  ```
100
143
 
101
- Invoking `python cli.py --verbose` enables coloured tracebacks just for that
144
+ Invoking `python cli.py --traceback` enables coloured tracebacks just for that
102
145
  run and resets the configuration afterwards.
103
146
 
104
147
  #### 3. Full-featured multi-command CLI
@@ -117,21 +160,21 @@ from lib_cli_exit_tools import SignalSpec, cli_session, default_signal_specs
117
160
  @dataclass(slots=True)
118
161
  class Settings:
119
162
  pretty: bool
120
- verbose: bool
163
+ traceback: bool
121
164
 
122
165
 
123
166
  @click.group()
124
167
  @click.option("--pretty/--no-pretty", default=True)
125
- @click.option("--verbose", is_flag=True)
168
+ @click.option("--traceback", is_flag=True, help="Show full tracebacks on error")
126
169
  @click.pass_context
127
- def cli(ctx: click.Context, pretty: bool, verbose: bool) -> None:
128
- ctx.obj = Settings(pretty=pretty, verbose=verbose)
170
+ def cli(ctx: click.Context, pretty: bool, traceback: bool) -> None:
171
+ ctx.obj = Settings(pretty=pretty, traceback=traceback)
129
172
 
130
173
 
131
174
  @cli.command()
132
175
  @click.pass_obj
133
176
  def info(settings: Settings) -> None:
134
- payload = {"verbose": settings.verbose, "pretty": settings.pretty}
177
+ payload = {"traceback": settings.traceback, "pretty": settings.pretty}
135
178
  click.echo(json.dumps(payload, indent=2 if settings.pretty else None))
136
179
 
137
180
 
@@ -271,9 +314,12 @@ config.broken_pipe_exit_code = 0 # treat BrokenPipeError as a benign truncat
271
314
 
272
315
  Field reference:
273
316
 
274
- - `traceback` (`bool`, default `False`): when `True`, `handle_cli_exception` renders a full Rich traceback to stderr and then returns a non-zero exit code. The original exception is not re-raised; callers should rely on the rendered output and returned status. The bundled CLI toggles this via `--traceback/--no-traceback`.
275
- - `exit_code_style` (`"errno"` or `"sysexits"`, default `"errno"`): controls the numeric mapping produced by `get_system_exit_code`. `errno` returns POSIX/Windows-style codes (e.g., `FileNotFoundError → 2`, `SIGINT → 130`); `sysexits` returns BSD-style semantic codes (`EX_NOINPUT`, `EX_USAGE`, etc.).
276
- - `broken_pipe_exit_code` (`int`, default `141`): overrides the exit status when a `BrokenPipeError` is raised (the default mirrors `128 + SIGPIPE`). Set this to `0` if you want truncation to be treated as success.
317
+ | Field | Type | Default | Description |
318
+ |-------|------|---------|-------------|
319
+ | `traceback` | `bool` | `False` | When `True`, `handle_cli_exception` renders a full Rich traceback to stderr. The bundled CLI toggles this via `--traceback/--no-traceback`. |
320
+ | `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.). |
321
+ | `broken_pipe_exit_code` | `int` | `141` | Exit status for `BrokenPipeError` (default mirrors `128 + SIGPIPE`). Set to `0` to treat truncation as success. |
322
+ | `traceback_force_color` | `bool` | `False` | Force Rich to emit ANSI-coloured tracebacks even when stderr is not a TTY. Useful for CI logs. |
277
323
 
278
324
  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:
279
325
 
@@ -360,22 +406,25 @@ Parameters:
360
406
  - `exception_handler`: Callable receiving the raised exception and returning an exit code; defaults to ``handle_cli_exception``.
361
407
  - `signal_installer`: Callable mirroring ``install_signal_handlers`` for embedding scenarios.
362
408
 
363
- ### `cli_session(*, summary_limit=500, verbose_limit=10_000, overrides=None)`
364
- Context manager that snapshots :mod:`lib_cli_exit_tools.config`, optionally
409
+ ### `cli_session(*, summary_limit=500, verbose_limit=10_000, overrides=None, restore=True)`
410
+ Context manager that snapshots `lib_cli_exit_tools.config`, optionally
365
411
  applies temporary overrides, and yields a callable compatible with
366
- ``run_cli``.
412
+ `run_cli`.
367
413
 
368
414
  Parameters:
369
- - `summary_limit`: Character budget when tracebacks are disabled.
370
- - `verbose_limit`: Character budget when tracebacks are enabled.
371
- - `overrides`: Mapping of configuration field/value pairs applied during the session.
415
+ - `summary_limit` (`int`, default `500`): Character budget when tracebacks are disabled.
416
+ - `verbose_limit` (`int`, default `10_000`): Character budget when tracebacks are enabled.
417
+ - `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.
418
+ - `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.
372
419
 
373
420
  Use it to restore configuration automatically—even when the wrapped command
374
421
  raises:
375
422
 
376
423
  ```python
424
+ from lib_cli_exit_tools import cli_session
425
+
377
426
  with cli_session(overrides={"traceback": True}) as run:
378
- exit_code = run(click_command, argv=args)
427
+ exit_code = run(my_click_command, argv=["--help"])
379
428
  ```
380
429
 
381
430
  ### `handle_cli_exception(exc, *, signal_specs=None, echo=None) -> int`
@@ -546,7 +595,7 @@ When installed, the generated console scripts (`lib-cli-exit-tools`, `cli-exit-t
546
595
 
547
596
  ## Modern Python Toolchain
548
597
 
549
- - Targets Python 3.13+ exclusively—no runtime shims or compatibility branches remain.
598
+ - Targets Python 3.10+ for modern language features and broad compatibility.
550
599
  - Development extras track the latest stable releases published on PyPI so quality gates match local and CI environments.
551
600
  - GitHub Actions workflows rely on the current major releases of `actions/checkout`, `actions/setup-python`, and `astral-sh/setup-uv`, aligning the automation stack with the 2025 runner images.
552
601
 
@@ -1,9 +1,9 @@
1
1
  [project]
2
2
  name = "lib_cli_exit_tools"
3
- version = "2.1.1"
3
+ version = "2.2.1"
4
4
  description = "CLI exit handling helpers: clean signals, exit codes, and error printing"
5
5
  readme = "README.md"
6
- requires-python = ">=3.13"
6
+ requires-python = ">=3.10"
7
7
  dependencies = ["rich-click>=1.9.4"]
8
8
  license = { text = "MIT" }
9
9
  authors = [{ name = "bitranox", email = "bitranox@gmail.com" }]
@@ -28,7 +28,7 @@ dev = [
28
28
  "codecov-cli>=11.2.6",
29
29
  "coverage[toml]>=7.13.0",
30
30
  "hypothesis>=6.148.7",
31
- "import-linter>=2.7",
31
+ "import-linter>=2.9",
32
32
  "bandit>=1.9.2",
33
33
  "pip-audit>=2.10.0",
34
34
  "pyright>=1.1.407",
@@ -37,6 +37,7 @@ dev = [
37
37
  "pytest-cov>=7.0.0",
38
38
  "ruff>=0.14.8",
39
39
  "textual>=6.8.0",
40
+ "tomli>=2.3.0;python_version<'3.11'",
40
41
  "twine>=6.2.0"
41
42
  ]
42
43
 
@@ -56,7 +57,7 @@ include = ["src/lib_cli_exit_tools/py.typed"]
56
57
 
57
58
  [tool.ruff]
58
59
  line-length = 160
59
- target-version = "py313"
60
+ target-version = "py310"
60
61
 
61
62
  [tool.ruff.lint.per-file-ignores]
62
63
  # ignore the "unused import sys" in the jupyter notebook - which we need (it is a linter hickup)
@@ -155,4 +156,13 @@ forbidden_modules = [
155
156
  # Known vulnerabilities to ignore (unfixable or accepted risk)
156
157
  # GHSA-4xh5-x5gv-qwph: py library ReDoS (no fix available, low risk)
157
158
  # PYSEC-2022-42969: same as above (alias)
158
- ignore-vulns = ["GHSA-4xh5-x5gv-qwph", "PYSEC-2022-42969"]
159
+ # PYSEC-2022-43012, CVE-2024-6345, PYSEC-2025-49: setuptools vulnerabilities
160
+ # These are build-time dependencies only, not runtime. The virtual environment
161
+ # setuptools version is managed by the CI environment/pip itself.
162
+ ignore-vulns = [
163
+ "GHSA-4xh5-x5gv-qwph",
164
+ "PYSEC-2022-42969",
165
+ "PYSEC-2022-43012",
166
+ "CVE-2024-6345",
167
+ "PYSEC-2025-49",
168
+ ]
@@ -24,7 +24,7 @@ name = "lib_cli_exit_tools"
24
24
  #: Human-readable summary shown in CLI help output.
25
25
  title = "CLI exit handling helpers: clean signals, exit codes, and error printing"
26
26
  #: Current release version pulled from ``pyproject.toml`` by automation.
27
- version = "2.1.1"
27
+ version = "2.2.1"
28
28
  #: Repository homepage presented to users.
29
29
  homepage = "https://github.com/bitranox/lib_cli_exit_tools"
30
30
  #: Author attribution surfaced in CLI output.
@@ -8,10 +8,15 @@ Each test verifies exactly one metadata behavior:
8
8
  from __future__ import annotations
9
9
 
10
10
  import runpy
11
- import tomllib
11
+ import sys
12
12
  from pathlib import Path
13
13
  from typing import Any, cast
14
14
 
15
+ if sys.version_info >= (3, 11):
16
+ import tomllib
17
+ else:
18
+ import tomli as tomllib # type: ignore[import-not-found,no-redef]
19
+
15
20
  import pytest
16
21
 
17
22
  PROJECT_ROOT = Path(__file__).resolve().parent.parent