lib-cli-exit-tools 1.4.0__tar.gz → 1.5.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.
Files changed (60) hide show
  1. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/CHANGELOG.md +4 -0
  2. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/PKG-INFO +12 -8
  3. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/README.md +4 -0
  4. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/docs/system-design/module_reference.md +5 -1
  5. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/notebooks/Quickstart.ipynb +73 -1
  6. lib_cli_exit_tools-1.5.0/notebooks/Quickstart.ipynb.Backup +281 -0
  7. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/packaging/brew/Formula/lib-cli-exit-tools.rb +1 -1
  8. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/packaging/conda/recipe/meta.yaml +1 -1
  9. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/packaging/nix/flake.nix +1 -1
  10. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/pyproject.toml +8 -8
  11. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/src/lib_cli_exit_tools/__init__.py +2 -0
  12. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/src/lib_cli_exit_tools/application/runner.py +4 -3
  13. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/src/lib_cli_exit_tools/cli.py +25 -0
  14. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/src/lib_cli_exit_tools/lib_cli_exit_tools.py +22 -0
  15. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/tests/application/test_runner.py +18 -4
  16. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/tests/cli/test_cli_entrypoints.py +16 -1
  17. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/.env.example +0 -0
  18. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/.github/workflows/ci.yml +0 -0
  19. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/.github/workflows/codeql.yml +0 -0
  20. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/.github/workflows/release.yml +0 -0
  21. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/.gitignore +0 -0
  22. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/AGENTS.md +0 -0
  23. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/CONTRIBUTING.md +0 -0
  24. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/DEVELOPMENT.md +0 -0
  25. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/INSTALL.md +0 -0
  26. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/LICENSE +0 -0
  27. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/Makefile +0 -0
  28. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/codecov.yml +0 -0
  29. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/packaging/README.md +0 -0
  30. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/qlty.toml +0 -0
  31. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/__init__.py +0 -0
  32. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/_utils.py +0 -0
  33. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/build.py +0 -0
  34. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/bump.py +0 -0
  35. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/bump_major.py +0 -0
  36. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/bump_minor.py +0 -0
  37. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/bump_patch.py +0 -0
  38. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/bump_version.py +0 -0
  39. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/clean.py +0 -0
  40. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/dev.py +0 -0
  41. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/generate_nix_flake.py +0 -0
  42. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/install.py +0 -0
  43. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/menu.py +0 -0
  44. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/push.py +0 -0
  45. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/release.py +0 -0
  46. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/run_cli.py +0 -0
  47. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/test.py +0 -0
  48. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/scripts/version_current.py +0 -0
  49. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/src/lib_cli_exit_tools/__init__conf__.py +0 -0
  50. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/src/lib_cli_exit_tools/__main__.py +0 -0
  51. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/src/lib_cli_exit_tools/adapters/__init__.py +0 -0
  52. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/src/lib_cli_exit_tools/adapters/signals.py +0 -0
  53. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/src/lib_cli_exit_tools/application/__init__.py +0 -0
  54. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/src/lib_cli_exit_tools/core/__init__.py +0 -0
  55. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/src/lib_cli_exit_tools/core/configuration.py +0 -0
  56. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/src/lib_cli_exit_tools/core/exit_codes.py +0 -0
  57. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/src/lib_cli_exit_tools/py.typed +0 -0
  58. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/tests/adapters/test_signals.py +0 -0
  59. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/tests/conftest.py +0 -0
  60. {lib_cli_exit_tools-1.4.0 → lib_cli_exit_tools-1.5.0}/tests/core/test_exit_codes.py +0 -0
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.5.0] - 2025-09-26
4
+
5
+ - Refracture
6
+
3
7
  ## [1.4.0] - 2025-09-26
4
8
 
5
9
  - Reafracture, TestSuite
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lib_cli_exit_tools
3
- Version: 1.4.0
3
+ Version: 1.5.0
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
@@ -18,15 +18,15 @@ Classifier: Typing :: Typed
18
18
  Requires-Python: >=3.10
19
19
  Requires-Dist: rich-click>=1.9.1
20
20
  Provides-Extra: dev
21
- Requires-Dist: build>=1.2; extra == 'dev'
21
+ Requires-Dist: build>=1.3; extra == 'dev'
22
22
  Requires-Dist: codecov>=2.1.13; extra == 'dev'
23
23
  Requires-Dist: pyright>=1.1; extra == 'dev'
24
- Requires-Dist: pytest-cov>=5.0; extra == 'dev'
25
- Requires-Dist: pytest>=8.2; extra == 'dev'
26
- Requires-Dist: ruff>=0.5; extra == 'dev'
27
- Requires-Dist: textual>=0.50; extra == 'dev'
28
- Requires-Dist: tomli>=2.0; extra == 'dev'
29
- Requires-Dist: twine>=5.1; extra == 'dev'
24
+ Requires-Dist: pytest-cov>=7.0; extra == 'dev'
25
+ Requires-Dist: pytest>=8.4; extra == 'dev'
26
+ Requires-Dist: ruff>=0.13; extra == 'dev'
27
+ Requires-Dist: textual>=6.1.0; extra == 'dev'
28
+ Requires-Dist: tomli>=2.2.1; extra == 'dev'
29
+ Requires-Dist: twine>=6.2; extra == 'dev'
30
30
  Description-Content-Type: text/markdown
31
31
 
32
32
  # lib_cli_exit_tools
@@ -65,6 +65,7 @@ Console script:
65
65
  lib_cli_exit_tools --help
66
66
  cli-exit-tools --help # alias
67
67
  lib_cli_exit_tools info
68
+ lib_cli_exit_tools fail # intentionally trigger RuntimeError to test error paths
68
69
  ```
69
70
 
70
71
  ### Embed in your own CLI
@@ -189,6 +190,9 @@ Parameters:
189
190
  - `length_limit`: Maximum characters for summary output.
190
191
  - `stream`: Target text stream; defaults to `sys.stderr`.
191
192
 
193
+ ### `i_should_fail()`
194
+ Deterministically raise `RuntimeError('i should fail')` to exercise error-handling paths. Useful for smoke-testing exit-code translation, CLI traceback toggles, and log formatting without inventing ad-hoc failing commands.
195
+
192
196
  ### `flush_streams() -> None`
193
197
  Best-effort flush of `sys.stdout` and `sys.stderr`, ensuring buffered output is written before exit.
194
198
 
@@ -34,6 +34,7 @@ Console script:
34
34
  lib_cli_exit_tools --help
35
35
  cli-exit-tools --help # alias
36
36
  lib_cli_exit_tools info
37
+ lib_cli_exit_tools fail # intentionally trigger RuntimeError to test error paths
37
38
  ```
38
39
 
39
40
  ### Embed in your own CLI
@@ -158,6 +159,9 @@ Parameters:
158
159
  - `length_limit`: Maximum characters for summary output.
159
160
  - `stream`: Target text stream; defaults to `sys.stderr`.
160
161
 
162
+ ### `i_should_fail()`
163
+ Deterministically raise `RuntimeError('i should fail')` to exercise error-handling paths. Useful for smoke-testing exit-code translation, CLI traceback toggles, and log formatting without inventing ad-hoc failing commands.
164
+
161
165
  ### `flush_streams() -> None`
162
166
  Best-effort flush of `sys.stdout` and `sys.stderr`, ensuring buffered output is written before exit.
163
167
 
@@ -59,7 +59,7 @@ Automations and downstream CLIs rely on `lib_cli_exit_tools` for deterministic e
59
59
  ### Module: lib_cli_exit_tools/lib_cli_exit_tools.py
60
60
  * **Purpose:** Provide a façade that re-exports configuration, signal helpers, and orchestration utilities so existing imports stay valid after the refactor.
61
61
  * **Location:** `src/lib_cli_exit_tools/lib_cli_exit_tools.py`
62
- * **Notes:** Contains no implementation logic; it simply aggregates symbols from the layered modules to minimise breaking changes.
62
+ * **Notes:** Aggregates symbols from the layered modules to minimise breaking changes and exposes `i_should_fail()` as a deterministic failure helper for exercising error-handling paths.
63
63
 
64
64
  ### Module: lib_cli_exit_tools/core/configuration.py
65
65
  * **Purpose:** Own the mutable runtime configuration shared across CLI executions.
@@ -131,6 +131,10 @@ Automations and downstream CLIs rely on `lib_cli_exit_tools` for deterministic e
131
131
  #### Function: `cli_info() -> None`
132
132
  * **Purpose:** Emit project metadata by delegating to `__init__conf__.print_info()`.
133
133
 
134
+ #### Function: `cli_fail() -> None`
135
+ * **Purpose:** Invoke `lib_cli_exit_tools.i_should_fail()` so operators can validate error-path handling from the packaged CLI without crafting bespoke failing commands.
136
+ * **Notes:** Surfaces the stable `RuntimeError('i should fail')` message, making it safe for scripted assertions and support playbooks.
137
+
134
138
  #### Function: `main(argv=None) -> int`
135
139
  * **Purpose:** Compose the CLI invocation by delegating to `lib_cli_exit_tools.run_cli`, returning exit codes instead of exiting directly.
136
140
 
@@ -191,6 +191,78 @@
191
191
  "except Exception:\n",
192
192
  " lib_cli_exit_tools.print_exception_message(trace_back=True)"
193
193
  ]
194
+ },
195
+ {
196
+ "cell_type": "markdown",
197
+ "id": "d56cb190",
198
+ "metadata": {},
199
+ "source": [
200
+ "## 7) Registered Command Fail"
201
+ ]
202
+ },
203
+ {
204
+ "cell_type": "code",
205
+ "execution_count": null,
206
+ "id": "b2eb015b",
207
+ "metadata": {},
208
+ "outputs": [],
209
+ "source": [
210
+ "!lib_cli_exit_tools fail"
211
+ ]
212
+ },
213
+ {
214
+ "cell_type": "markdown",
215
+ "id": "6c546140",
216
+ "metadata": {},
217
+ "source": [
218
+ "## 8) Registered Command Fail with Traceback"
219
+ ]
220
+ },
221
+ {
222
+ "cell_type": "code",
223
+ "execution_count": null,
224
+ "id": "db445316",
225
+ "metadata": {},
226
+ "outputs": [],
227
+ "source": [
228
+ "!lib_cli_exit_tools --traceback fail"
229
+ ]
230
+ },
231
+ {
232
+ "cell_type": "markdown",
233
+ "id": "24c157fa",
234
+ "metadata": {},
235
+ "source": [
236
+ "## 9) Script Command Fail"
237
+ ]
238
+ },
239
+ {
240
+ "cell_type": "code",
241
+ "execution_count": null,
242
+ "id": "81a4fa19",
243
+ "metadata": {},
244
+ "outputs": [],
245
+ "source": [
246
+ "!{sys.executable} -m lib_cli_exit_tools fail"
247
+ ]
248
+ },
249
+ {
250
+ "cell_type": "markdown",
251
+ "id": "ab2685a2",
252
+ "metadata": {},
253
+ "source": [
254
+ "## 10) Script Command Fail with Traceback"
255
+ ]
256
+ },
257
+ {
258
+ "cell_type": "code",
259
+ "execution_count": null,
260
+ "id": "ea02859a",
261
+ "metadata": {},
262
+ "outputs": [],
263
+ "source": [
264
+ "!{sys.executable} -m lib_cli_exit_tools --traceback fail"
265
+ ]
194
266
  }
195
267
  ],
196
268
  "metadata": {
@@ -206,4 +278,4 @@
206
278
  },
207
279
  "nbformat": 4,
208
280
  "nbformat_minor": 5
209
- }
281
+ }
@@ -0,0 +1,281 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "7fb27b941602401d91542211134fc71a",
6
+ "metadata": {},
7
+ "source": [
8
+ "# lib_cli_exit_tools Quickstart\n",
9
+ "\n",
10
+ "Use this notebook to install the package, explore the CLI, and observe how rich-formatted tracebacks behave when you enable debugging output.\n"
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "markdown",
15
+ "id": "acae54e37e7d407bbb7b55eff062a284",
16
+ "metadata": {},
17
+ "source": [
18
+ "## 1) Install latest from GitHub (default)\n"
19
+ ]
20
+ },
21
+ {
22
+ "cell_type": "code",
23
+ "execution_count": null,
24
+ "id": "9a63283cbaf04dbcab1f6479b197f3a8",
25
+ "metadata": {},
26
+ "outputs": [],
27
+ "source": [
28
+ "# update pip and setuptools\n",
29
+ "import sys\n",
30
+ "\n",
31
+ "!{sys.executable} -m pip install --upgrade pip\n",
32
+ "!{sys.executable} -m pip install --upgrade setuptools\n",
33
+ "\n",
34
+ "# install lib_cli_exit_tools from GitHub (default)\n",
35
+ "!{sys.executable} -m pip install --upgrade git+https://github.com/bitranox/lib_cli_exit_tools.git"
36
+ ]
37
+ },
38
+ {
39
+ "cell_type": "markdown",
40
+ "id": "8dd0d8092fe74a7c96281538738b07e2",
41
+ "metadata": {},
42
+ "source": [
43
+ "## Or: Install from PyPI (alternative)\n",
44
+ "Uncomment and run the cell below if you prefer the PyPI release.\n"
45
+ ]
46
+ },
47
+ {
48
+ "cell_type": "code",
49
+ "execution_count": null,
50
+ "id": "72eea5119410473aa328ad9291626812",
51
+ "metadata": {},
52
+ "outputs": [],
53
+ "source": [
54
+ "# !{sys.executable} -m pip install --upgrade lib_cli_exit_tools\n"
55
+ ]
56
+ },
57
+ {
58
+ "cell_type": "markdown",
59
+ "id": "8edb47106e1a46a883d545849b8ab81b",
60
+ "metadata": {},
61
+ "source": [
62
+ "## 2) CLI: version and help\n"
63
+ ]
64
+ },
65
+ {
66
+ "cell_type": "code",
67
+ "execution_count": null,
68
+ "id": "10185d26023b46108eb7d9f57d49d2b3",
69
+ "metadata": {},
70
+ "outputs": [],
71
+ "source": [
72
+ "!lib_cli_exit_tools --version\n",
73
+ "!lib_cli_exit_tools --help\n",
74
+ "!lib_cli_exit_tools info"
75
+ ]
76
+ },
77
+ {
78
+ "cell_type": "markdown",
79
+ "id": "8763a12b2bbd4a93a75aff182afb95dc",
80
+ "metadata": {},
81
+ "source": [
82
+ "## 3) Demo command for failure handling\n",
83
+ "The helper below builds a tiny Click command that purposely raises an error so you can compare short messages with rich tracebacks.\n"
84
+ ]
85
+ },
86
+ {
87
+ "cell_type": "code",
88
+ "execution_count": null,
89
+ "id": "7623eae2785240b9bd12b16a66d81610",
90
+ "metadata": {},
91
+ "outputs": [],
92
+ "source": [
93
+ "import click\n",
94
+ "from lib_cli_exit_tools.lib_cli_exit_tools import config, run_cli\n",
95
+ "\n",
96
+ "\n",
97
+ "@click.command()\n",
98
+ "def quickstart_fail() -> None:\n",
99
+ " \"\"\"Raise a demo error to exercise lib_cli_exit_tools.\"\"\"\n",
100
+ " raise RuntimeError(\"demo failure from Quickstart\")\n",
101
+ "\n",
102
+ "\n",
103
+ "def run_quickstart_demo(traceback_enabled: bool) -> int:\n",
104
+ " \"\"\"Execute the demo command with the desired traceback mode.\n",
105
+ "\n",
106
+ " The helper keeps tests deterministic by disabling signal handlers\n",
107
+ " in notebook contexts and resets global flags after execution.\n",
108
+ " \"\"\"\n",
109
+ "\n",
110
+ " original_flag = config.traceback\n",
111
+ " config.traceback = traceback_enabled\n",
112
+ " try:\n",
113
+ " return run_cli(quickstart_fail, argv=[], install_signals=False)\n",
114
+ " finally:\n",
115
+ " config.traceback = original_flag"
116
+ ]
117
+ },
118
+ {
119
+ "cell_type": "markdown",
120
+ "id": "7cdc8c89c7104fffa095e18ddfef8986",
121
+ "metadata": {},
122
+ "source": [
123
+ "## 4) CLI failure with short message (default)\n",
124
+ "Tracebacks are suppressed by default; the CLI emits a concise summary and returns a deterministic exit code.\n"
125
+ ]
126
+ },
127
+ {
128
+ "cell_type": "code",
129
+ "execution_count": null,
130
+ "id": "b118ea5561624da68c537baed56e602f",
131
+ "metadata": {},
132
+ "outputs": [],
133
+ "source": [
134
+ "exit_code = run_quickstart_demo(False)\n",
135
+ "print(f\"Exit code: {exit_code}\")"
136
+ ]
137
+ },
138
+ {
139
+ "cell_type": "markdown",
140
+ "id": "938c804e27f84196a10c8828c723f798",
141
+ "metadata": {},
142
+ "source": [
143
+ "## 5) CLI failure with rich traceback\n",
144
+ "Enable tracebacks to surface a full Rich-formatted stack trace for debugging.\n"
145
+ ]
146
+ },
147
+ {
148
+ "cell_type": "code",
149
+ "execution_count": null,
150
+ "id": "504fb2a444614c0babb325280ed9130a",
151
+ "metadata": {},
152
+ "outputs": [],
153
+ "source": [
154
+ "import lib_cli_exit_tools\n",
155
+ "\n",
156
+ "try:\n",
157
+ " run_quickstart_demo(True)\n",
158
+ "except RuntimeError:\n",
159
+ " lib_cli_exit_tools.print_exception_message(trace_back=True)"
160
+ ]
161
+ },
162
+ {
163
+ "cell_type": "markdown",
164
+ "id": "59bbdb311c014d738909a11f9e486628",
165
+ "metadata": {},
166
+ "source": [
167
+ "## 6) Library usage: exit codes and traceback formatting\n",
168
+ "Use the functional API to translate exceptions into exit codes and to opt in to rich tracebacks programmatically.\n"
169
+ ]
170
+ },
171
+ {
172
+ "cell_type": "code",
173
+ "execution_count": null,
174
+ "id": "b43b363d81ae4b689946ece5c682cd59",
175
+ "metadata": {},
176
+ "outputs": [],
177
+ "source": [
178
+ "import lib_cli_exit_tools\n",
179
+ "\n",
180
+ "# Short message (default)\n",
181
+ "try:\n",
182
+ " raise FileNotFoundError(\"missing.txt\")\n",
183
+ "except Exception as exc:\n",
184
+ " exit_code = lib_cli_exit_tools.get_system_exit_code(exc)\n",
185
+ " lib_cli_exit_tools.print_exception_message(trace_back=False)\n",
186
+ " print(f\"Exit code would be: {exit_code}\")\n",
187
+ "\n",
188
+ "# Rich traceback\n",
189
+ "try:\n",
190
+ " raise ValueError(\"boom\")\n",
191
+ "except Exception:\n",
192
+ " lib_cli_exit_tools.print_exception_message(trace_back=True)"
193
+ ]
194
+ },
195
+ {
196
+ "cell_type": "markdown",
197
+ "id": "d56cb190",
198
+ "metadata": {},
199
+ "source": [
200
+ "## 7) Registered Command Fail"
201
+ ]
202
+ },
203
+ {
204
+ "cell_type": "code",
205
+ "execution_count": null,
206
+ "id": "b2eb015b",
207
+ "metadata": {},
208
+ "outputs": [],
209
+ "source": [
210
+ "!lib_cli_exit_tools fail"
211
+ ]
212
+ },
213
+ {
214
+ "cell_type": "markdown",
215
+ "id": "6c546140",
216
+ "metadata": {},
217
+ "source": [
218
+ "## 8) Registered Command Fail with Traceback"
219
+ ]
220
+ },
221
+ {
222
+ "cell_type": "code",
223
+ "execution_count": null,
224
+ "id": "db445316",
225
+ "metadata": {},
226
+ "outputs": [],
227
+ "source": [
228
+ "!lib_cli_exit_tools --traceback fail"
229
+ ]
230
+ },
231
+ {
232
+ "cell_type": "markdown",
233
+ "id": "24c157fa",
234
+ "metadata": {},
235
+ "source": [
236
+ "## 9) Script Command Fail"
237
+ ]
238
+ },
239
+ {
240
+ "cell_type": "code",
241
+ "execution_count": null,
242
+ "id": "81a4fa19",
243
+ "metadata": {},
244
+ "outputs": [],
245
+ "source": [
246
+ "!{sys.executable} -m lib_cli_exit_tools fail"
247
+ ]
248
+ },
249
+ {
250
+ "cell_type": "markdown",
251
+ "id": "ab2685a2",
252
+ "metadata": {},
253
+ "source": [
254
+ "## 10) Script Command Fail with Traceback"
255
+ ]
256
+ },
257
+ {
258
+ "cell_type": "code",
259
+ "execution_count": null,
260
+ "id": "ea02859a",
261
+ "metadata": {},
262
+ "outputs": [],
263
+ "source": [
264
+ "!{sys.executable} -m lib_cli_exit_tools --traceback fail"
265
+ ]
266
+ }
267
+ ],
268
+ "metadata": {
269
+ "kernelspec": {
270
+ "display_name": "Python 3",
271
+ "language": "python",
272
+ "name": "python3"
273
+ },
274
+ "language_info": {
275
+ "name": "python",
276
+ "version": "3"
277
+ }
278
+ },
279
+ "nbformat": 4,
280
+ "nbformat_minor": 5
281
+ }
@@ -3,7 +3,7 @@ class LibCliExitTools < Formula
3
3
 
4
4
  desc "CLI exit handling helpers: clean signals, exit codes, and error printing"
5
5
  homepage "https://github.com/bitranox/lib_cli_exit_tools"
6
- url "https://github.com/bitranox/lib_cli_exit_tools/archive/refs/tags/v1.4.0.tar.gz"
6
+ url "https://github.com/bitranox/lib_cli_exit_tools/archive/refs/tags/v1.5.0.tar.gz"
7
7
  k3cc316e7d0b09dd79e617cb3e91b14ee414b7e6695b108a326d9d3ccd08dd9"
8
8
  license "MIT"
9
9
 
@@ -1,5 +1,5 @@
1
1
  {% set name = "lib_cli_exit_tools" %}
2
- {% set version = "1.4.0" %}
2
+ {% set version = "1.5.0" %}
3
3
 
4
4
  package:
5
5
  name: {{ name|lower }}
@@ -44,7 +44,7 @@
44
44
  {
45
45
  packages.default = pypkgs.buildPythonPackage {
46
46
  pname = "lib_cli_exit_tools";
47
- version = "1.4.0";
47
+ version = "1.5.0";
48
48
  pyproject = true;
49
49
  src = ../..;
50
50
  nativeBuildInputs = [ hatchlingVendor ];
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lib_cli_exit_tools"
3
- version = "1.4.0"
3
+ version = "1.5.0"
4
4
  description = "CLI exit handling helpers: clean signals, exit codes, and error printing"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -24,15 +24,15 @@ Issues = "https://github.com/bitranox/lib_cli_exit_tools/issues"
24
24
 
25
25
  [project.optional-dependencies]
26
26
  dev = [
27
- "pytest>=8.2",
28
- "pytest-cov>=5.0",
29
- "ruff>=0.5",
27
+ "pytest>=8.4",
28
+ "pytest-cov>=7.0",
29
+ "ruff>=0.13",
30
30
  "pyright>=1.1",
31
- "build>=1.2",
32
- "twine>=5.1",
31
+ "build>=1.3",
32
+ "twine>=6.2",
33
33
  "codecov>=2.1.13",
34
- "textual>=0.50",
35
- "tomli>=2.0",
34
+ "textual>=6.1.0",
35
+ "tomli>=2.2.1",
36
36
  ]
37
37
 
38
38
  [project.scripts]
@@ -25,6 +25,7 @@ from .lib_cli_exit_tools import (
25
25
  handle_cli_exception,
26
26
  install_signal_handlers,
27
27
  print_exception_message,
28
+ i_should_fail,
28
29
  run_cli,
29
30
  )
30
31
 
@@ -42,5 +43,6 @@ __all__ = [
42
43
  "default_signal_specs",
43
44
  "install_signal_handlers",
44
45
  "handle_cli_exception",
46
+ "i_should_fail",
45
47
  "run_cli",
46
48
  ]
@@ -240,8 +240,8 @@ def handle_cli_exception(
240
240
  Returns:
241
241
  Integer exit code suitable for :func:`sys.exit`.
242
242
  Side Effects:
243
- May write to stderr, invoke :func:`print_exception_message`, or re-raise
244
- the original exception when tracebacks are enabled.
243
+ May write to stderr, invoke :func:`print_exception_message`, and render
244
+ rich tracebacks when requested.
245
245
  """
246
246
 
247
247
  specs = list(default_signal_specs() if signal_specs is None else signal_specs)
@@ -265,7 +265,8 @@ def handle_cli_exception(
265
265
  return 1
266
266
 
267
267
  if config.traceback:
268
- raise exc
268
+ print_exception_message(trace_back=True)
269
+ return get_system_exit_code(exc)
269
270
 
270
271
  print_exception_message()
271
272
  return get_system_exit_code(exc)
@@ -6,6 +6,7 @@ Purpose:
6
6
  Contents:
7
7
  * :func:`cli` group exposing shared options.
8
8
  * :func:`cli_info` subcommand reporting distribution metadata.
9
+ * :func:`cli_fail` subcommand triggering a deterministic failure for testing error paths.
9
10
  * :func:`main` entry point used by console scripts and ``python -m``.
10
11
  System Integration:
11
12
  The CLI mutates :data:`lib_cli_exit_tools.config` based on the ``--traceback``
@@ -125,6 +126,30 @@ def cli_info() -> None:
125
126
  __init__conf__.print_info()
126
127
 
127
128
 
129
+ @cli.command("fail", context_settings=CLICK_CONTEXT_SETTINGS)
130
+ def cli_fail() -> None:
131
+ """Intentionally raise a failure to validate error handling.
132
+
133
+ Why:
134
+ Exercise the error-reporting path so engineers can confirm exit-code
135
+ translation and optional traceback behaviour without crafting custom
136
+ failing commands.
137
+ Side Effects:
138
+ Delegates to :func:`lib_cli_exit_tools.i_should_fail`, which raises
139
+ ``RuntimeError`` for the caller to handle.
140
+ Examples:
141
+ >>> from click.testing import CliRunner
142
+ >>> runner = CliRunner()
143
+ >>> result = runner.invoke(cli, ["fail"])
144
+ >>> result.exit_code != 0
145
+ True
146
+ >>> "i should fail" in result.output or "i should fail" in result.stderr
147
+ True
148
+ """
149
+
150
+ lib_cli_exit_tools.i_should_fail()
151
+
152
+
128
153
  def main(argv: Optional[Sequence[str]] = None) -> int:
129
154
  """Run the CLI with :func:`lib_cli_exit_tools.run_cli` wiring.
130
155
 
@@ -9,6 +9,7 @@ Contents:
9
9
  * ``get_system_exit_code`` from :mod:`lib_cli_exit_tools.core.exit_codes`.
10
10
  * ``handle_cli_exception`` and ``run_cli`` from
11
11
  :mod:`lib_cli_exit_tools.application.runner`.
12
+ * :func:`i_should_fail` defined here for intentionally exercising error paths.
12
13
  * Signal helpers from :mod:`lib_cli_exit_tools.adapters.signals`.
13
14
  System Integration:
14
15
  The CLI adapter (:mod:`lib_cli_exit_tools.cli`) and external consumers
@@ -49,5 +50,26 @@ __all__ = [
49
50
  "default_signal_specs",
50
51
  "install_signal_handlers",
51
52
  "handle_cli_exception",
53
+ "i_should_fail",
52
54
  "run_cli",
53
55
  ]
56
+
57
+
58
+ def i_should_fail() -> None:
59
+ """Raise :class:`RuntimeError` to exercise error-handling flows.
60
+
61
+ Why:
62
+ Provide a deterministic failure path so engineers can verify traceback
63
+ toggles, exit-code translation, and structured error reporting during
64
+ manual or automated testing.
65
+ Raises:
66
+ RuntimeError: Always raised with a stable message for assertion-friendly
67
+ diagnostics.
68
+ Examples:
69
+ >>> i_should_fail()
70
+ Traceback (most recent call last):
71
+ ...
72
+ RuntimeError: i should fail
73
+ """
74
+
75
+ raise RuntimeError("i should fail")
@@ -78,11 +78,25 @@ def test_handle_cli_exception_generic_delegates(monkeypatch: pytest.MonkeyPatch)
78
78
  assert calls == {"printed": True, "exc": err}
79
79
 
80
80
 
81
- def test_handle_cli_exception_respects_traceback(monkeypatch: pytest.MonkeyPatch) -> None:
82
- """Traceback mode re-raises instead of swallowing the exception."""
81
+ def test_handle_cli_exception_prints_rich_traceback(monkeypatch: pytest.MonkeyPatch) -> None:
82
+ """Traceback mode renders rich tracebacks and returns exit codes."""
83
+ calls: dict[str, object] = {}
84
+
85
+ def _fake_print(*_args, **kwargs) -> None:
86
+ calls["called"] = True
87
+ calls["trace_back"] = kwargs.get("trace_back")
88
+
89
+ def _fake_exit(exc: BaseException) -> int:
90
+ calls["exc"] = exc
91
+ return 17
92
+
93
+ monkeypatch.setattr("lib_cli_exit_tools.application.runner.print_exception_message", _fake_print)
94
+ monkeypatch.setattr("lib_cli_exit_tools.application.runner.get_system_exit_code", _fake_exit)
95
+
83
96
  config.traceback = True
84
- with pytest.raises(RuntimeError):
85
- handle_cli_exception(RuntimeError("boom"))
97
+ err = RuntimeError("boom")
98
+ assert handle_cli_exception(err) == 17
99
+ assert calls == {"called": True, "trace_back": True, "exc": err}
86
100
 
87
101
 
88
102
  def test_print_exception_message_outputs_summary() -> None:
@@ -19,10 +19,16 @@ from _pytest.capture import CaptureFixture
19
19
 
20
20
  import pytest
21
21
 
22
- from lib_cli_exit_tools import run_cli
22
+ from lib_cli_exit_tools import run_cli, i_should_fail
23
23
  from lib_cli_exit_tools.cli import cli as root_cli, main
24
24
 
25
25
 
26
+ def test_i_should_fail_helper_raises_runtime_error() -> None:
27
+ """The public helper always raises RuntimeError with a stable message."""
28
+ with pytest.raises(RuntimeError, match="^i should fail$"):
29
+ i_should_fail()
30
+
31
+
26
32
  def test_cli_info_command_outputs_metadata(capsys: CaptureFixture[str]) -> None:
27
33
  """The info subcommand surfaces package metadata without stderr output."""
28
34
  exit_code = main(["info"])
@@ -32,6 +38,15 @@ def test_cli_info_command_outputs_metadata(capsys: CaptureFixture[str]) -> None:
32
38
  assert err == ""
33
39
 
34
40
 
41
+ def test_cli_fail_command_emits_runtime_error(capsys: CaptureFixture[str]) -> None:
42
+ """The fail subcommand intentionally raises RuntimeError for error-path tests."""
43
+ exit_code = main(["fail"])
44
+ out, err = capsys.readouterr()
45
+ assert exit_code == 1
46
+ assert out == ""
47
+ assert "RuntimeError: i should fail" in err
48
+
49
+
35
50
  def test_cli_unknown_option_returns_usage_error(capsys: CaptureFixture[str]) -> None:
36
51
  """Unknown options trigger Click usage errors with exit code 2."""
37
52
  exit_code = main(["--does-not-exist"])