lib-cli-exit-tools 2.3.2__py3-none-any.whl → 2.3.3__py3-none-any.whl
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/__init__.py +0 -0
- lib_cli_exit_tools/__init__conf__.py +4 -2
- lib_cli_exit_tools/__main__.py +0 -0
- lib_cli_exit_tools/adapters/__init__.py +0 -0
- lib_cli_exit_tools/adapters/signals.py +7 -7
- lib_cli_exit_tools/application/__init__.py +0 -0
- lib_cli_exit_tools/application/runner.py +21 -17
- lib_cli_exit_tools/cli/__init__.py +0 -0
- lib_cli_exit_tools/cli/commands.py +1 -2
- lib_cli_exit_tools/cli/group.py +5 -3
- lib_cli_exit_tools/cli/styling.py +4 -1
- lib_cli_exit_tools/cli/typed_click.py +33 -11
- lib_cli_exit_tools/core/__init__.py +0 -0
- lib_cli_exit_tools/core/configuration.py +8 -6
- lib_cli_exit_tools/core/exit_codes.py +4 -4
- lib_cli_exit_tools/lib_cli_exit_tools.py +12 -12
- lib_cli_exit_tools/py.typed +0 -0
- {lib_cli_exit_tools-2.3.2.dist-info → lib_cli_exit_tools-2.3.3.dist-info}/METADATA +14 -13
- lib_cli_exit_tools-2.3.3.dist-info/RECORD +22 -0
- {lib_cli_exit_tools-2.3.2.dist-info → lib_cli_exit_tools-2.3.3.dist-info}/WHEEL +1 -1
- lib_cli_exit_tools-2.3.2.dist-info/RECORD +0 -22
- {lib_cli_exit_tools-2.3.2.dist-info → lib_cli_exit_tools-2.3.3.dist-info}/entry_points.txt +0 -0
- {lib_cli_exit_tools-2.3.2.dist-info → lib_cli_exit_tools-2.3.3.dist-info}/licenses/LICENSE +0 -0
lib_cli_exit_tools/__init__.py
CHANGED
|
File without changes
|
|
@@ -19,12 +19,14 @@ present authoritative project information without invoking packaging APIs.
|
|
|
19
19
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
|
+
import sys
|
|
23
|
+
|
|
22
24
|
#: Distribution name declared in ``pyproject.toml``.
|
|
23
25
|
name = "lib_cli_exit_tools"
|
|
24
26
|
#: Human-readable summary shown in CLI help output.
|
|
25
27
|
title = "CLI exit handling helpers: clean signals, exit codes, and error printing"
|
|
26
28
|
#: Current release version pulled from ``pyproject.toml`` by automation.
|
|
27
|
-
version = "2.3.
|
|
29
|
+
version = "2.3.3"
|
|
28
30
|
#: Repository homepage presented to users.
|
|
29
31
|
homepage = "https://github.com/bitranox/lib_cli_exit_tools"
|
|
30
32
|
#: Author attribution surfaced in CLI output.
|
|
@@ -71,4 +73,4 @@ def print_info() -> None:
|
|
|
71
73
|
pad = max(len(label) for label, _ in fields)
|
|
72
74
|
lines = [f"Info for {name}:", ""]
|
|
73
75
|
lines.extend(f" {label.ljust(pad)} = {value}" for label, value in fields)
|
|
74
|
-
|
|
76
|
+
sys.stdout.write("\n".join(lines) + "\n")
|
lib_cli_exit_tools/__main__.py
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -16,16 +16,16 @@ System Integration:
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
18
|
import signal
|
|
19
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
19
20
|
from contextlib import ExitStack
|
|
20
21
|
from dataclasses import dataclass
|
|
21
22
|
from types import FrameType
|
|
22
|
-
from typing import Callable, Iterable, Sequence
|
|
23
23
|
|
|
24
24
|
__all__ = [
|
|
25
25
|
"CliSignalError",
|
|
26
|
+
"SigBreakInterrupt",
|
|
26
27
|
"SigIntInterrupt",
|
|
27
28
|
"SigTermInterrupt",
|
|
28
|
-
"SigBreakInterrupt",
|
|
29
29
|
"SignalSpec",
|
|
30
30
|
"default_signal_specs",
|
|
31
31
|
"install_signal_handlers",
|
|
@@ -43,15 +43,15 @@ class CliSignalError(RuntimeError):
|
|
|
43
43
|
"""
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
class SigIntInterrupt(CliSignalError):
|
|
46
|
+
class SigIntInterrupt(CliSignalError): # noqa: N818 - public API; renaming breaks every downstream `except SigIntInterrupt`
|
|
47
47
|
"""Raised when the process receives ``SIGINT`` (Ctrl+C)."""
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
class SigTermInterrupt(CliSignalError):
|
|
50
|
+
class SigTermInterrupt(CliSignalError): # noqa: N818 - public API; renaming breaks every downstream `except SigTermInterrupt`
|
|
51
51
|
"""Raised when the process receives ``SIGTERM`` (termination request)."""
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
class SigBreakInterrupt(CliSignalError):
|
|
54
|
+
class SigBreakInterrupt(CliSignalError): # noqa: N818 - public API; renaming breaks every downstream `except SigBreakInterrupt`
|
|
55
55
|
"""Raised when the process receives ``SIGBREAK`` on Windows consoles."""
|
|
56
56
|
|
|
57
57
|
|
|
@@ -160,7 +160,7 @@ def _maybe_sigterm_spec() -> Iterable[SignalSpec]:
|
|
|
160
160
|
"""Yield the ``SIGTERM`` specification when supported by the host."""
|
|
161
161
|
if hasattr(signal, "SIGTERM"):
|
|
162
162
|
yield SignalSpec(
|
|
163
|
-
signum=
|
|
163
|
+
signum=signal.SIGTERM,
|
|
164
164
|
exception=SigTermInterrupt,
|
|
165
165
|
message="Terminated (SIGTERM/SIGBREAK).",
|
|
166
166
|
exit_code=143,
|
|
@@ -171,7 +171,7 @@ def _maybe_sigbreak_spec() -> Iterable[SignalSpec]:
|
|
|
171
171
|
"""Yield the ``SIGBREAK`` specification when running on Windows."""
|
|
172
172
|
if hasattr(signal, "SIGBREAK"):
|
|
173
173
|
yield SignalSpec(
|
|
174
|
-
signum=getattr(signal, "SIGBREAK"),
|
|
174
|
+
signum=getattr(signal, "SIGBREAK"), # noqa: B009 - typeshed only declares SIGBREAK on win32; direct access fails pyright on POSIX
|
|
175
175
|
exception=SigBreakInterrupt,
|
|
176
176
|
message="Terminated (SIGBREAK).",
|
|
177
177
|
exit_code=149,
|
|
File without changes
|
|
@@ -5,9 +5,9 @@ Purpose:
|
|
|
5
5
|
handling, traceback rendering, and exit-code translation across entry
|
|
6
6
|
points.
|
|
7
7
|
Contents:
|
|
8
|
-
* :func:`handle_cli_exception`
|
|
8
|
+
* :func:`handle_cli_exception` - maps exceptions to exit codes and renders
|
|
9
9
|
diagnostics.
|
|
10
|
-
* :func:`run_cli`
|
|
10
|
+
* :func:`run_cli` - orchestrates signal installation, command execution, and
|
|
11
11
|
cleanup.
|
|
12
12
|
* Supporting utilities for Rich-based output and stream management.
|
|
13
13
|
System Integration:
|
|
@@ -19,14 +19,15 @@ System Integration:
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
21
|
import sys
|
|
22
|
-
from
|
|
23
|
-
from
|
|
22
|
+
from collections.abc import Callable, Generator, Iterable, Sequence
|
|
23
|
+
from contextlib import AbstractContextManager, contextmanager, nullcontext, suppress
|
|
24
|
+
from typing import Literal, Protocol, TextIO, TypedDict, cast
|
|
24
25
|
|
|
25
26
|
import rich_click as click
|
|
26
|
-
from rich_click import rich_click as rich_config
|
|
27
27
|
from rich.console import Console
|
|
28
28
|
from rich.text import Text
|
|
29
29
|
from rich.traceback import Traceback
|
|
30
|
+
from rich_click import rich_click as rich_config
|
|
30
31
|
|
|
31
32
|
from ..adapters.signals import SignalSpec, default_signal_specs, install_signal_handlers
|
|
32
33
|
from ..core.configuration import ExitCodeStyle, config, config_overrides
|
|
@@ -57,18 +58,18 @@ class ClickCommand(Protocol):
|
|
|
57
58
|
args: Sequence[str] | None = ...,
|
|
58
59
|
prog_name: str | None = ...,
|
|
59
60
|
complete_var: str | None = ...,
|
|
60
|
-
standalone_mode: bool = ...,
|
|
61
|
+
standalone_mode: bool = ..., # noqa: FBT001 - mirrors Click's actual positional main() signature
|
|
61
62
|
**_: object,
|
|
62
63
|
) -> None: ...
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
__all__ = [
|
|
67
|
+
"SessionOverrides",
|
|
68
|
+
"cli_session",
|
|
69
|
+
"flush_streams",
|
|
66
70
|
"handle_cli_exception",
|
|
67
71
|
"print_exception_message",
|
|
68
|
-
"flush_streams",
|
|
69
72
|
"run_cli",
|
|
70
|
-
"cli_session",
|
|
71
|
-
"SessionOverrides",
|
|
72
73
|
]
|
|
73
74
|
|
|
74
75
|
|
|
@@ -162,7 +163,7 @@ def _build_console(
|
|
|
162
163
|
|
|
163
164
|
target_stream = stream or sys.stderr
|
|
164
165
|
force_flag = rich_config.FORCE_TERMINAL if force_terminal is None else force_terminal
|
|
165
|
-
default_color = cast(RichColorSystem | None, getattr(rich_config, "COLOR_SYSTEM", None))
|
|
166
|
+
default_color = cast("RichColorSystem | None", getattr(rich_config, "COLOR_SYSTEM", None))
|
|
166
167
|
color_flag = default_color if color_system is None else color_system
|
|
167
168
|
return Console(
|
|
168
169
|
file=target_stream,
|
|
@@ -216,6 +217,7 @@ def _decode_output(output: object) -> str | None:
|
|
|
216
217
|
|
|
217
218
|
|
|
218
219
|
def print_exception_message(
|
|
220
|
+
*,
|
|
219
221
|
trace_back: bool | None = None,
|
|
220
222
|
length_limit: int = 500,
|
|
221
223
|
stream: TextIO | None = None,
|
|
@@ -245,10 +247,10 @@ def print_exception_message(
|
|
|
245
247
|
target_stream = _target_stream(stream)
|
|
246
248
|
_emit_subprocess_output(exc_info, target_stream)
|
|
247
249
|
|
|
248
|
-
render_traceback = _resolve_traceback_choice(trace_back)
|
|
250
|
+
render_traceback = _resolve_traceback_choice(trace_back=trace_back)
|
|
249
251
|
console = _console_for_tracebacks(target_stream)
|
|
250
252
|
|
|
251
|
-
_render_exception_view(console, exc_info, render_traceback, length_limit)
|
|
253
|
+
_render_exception_view(console, exc_info, render_traceback=render_traceback, length_limit=length_limit)
|
|
252
254
|
_finalise_console(console)
|
|
253
255
|
flush_streams()
|
|
254
256
|
|
|
@@ -263,7 +265,7 @@ def _target_stream(stream: TextIO | None) -> TextIO:
|
|
|
263
265
|
return stream or sys.stderr
|
|
264
266
|
|
|
265
267
|
|
|
266
|
-
def _resolve_traceback_choice(trace_back: bool | None) -> bool:
|
|
268
|
+
def _resolve_traceback_choice(*, trace_back: bool | None) -> bool:
|
|
267
269
|
"""Decide whether to render a traceback based on explicit or global flags."""
|
|
268
270
|
return config.traceback if trace_back is None else trace_back
|
|
269
271
|
|
|
@@ -283,6 +285,7 @@ def _console_for_tracebacks(stream: TextIO) -> Console:
|
|
|
283
285
|
def _render_exception_view(
|
|
284
286
|
console: Console,
|
|
285
287
|
exc_info: BaseException,
|
|
288
|
+
*,
|
|
286
289
|
render_traceback: bool,
|
|
287
290
|
length_limit: int,
|
|
288
291
|
) -> None:
|
|
@@ -510,7 +513,7 @@ def cli_session(
|
|
|
510
513
|
"""
|
|
511
514
|
|
|
512
515
|
applied = _normalise_session_overrides(overrides)
|
|
513
|
-
manager = _session_config_manager(applied, restore)
|
|
516
|
+
manager = _session_config_manager(applied, restore=restore)
|
|
514
517
|
|
|
515
518
|
with manager:
|
|
516
519
|
handler = _session_exception_handler(summary_limit, verbose_limit)
|
|
@@ -561,7 +564,7 @@ def _normalise_session_overrides(overrides: SessionOverrides | None) -> SessionO
|
|
|
561
564
|
return overrides
|
|
562
565
|
|
|
563
566
|
|
|
564
|
-
def _session_config_manager(applied: SessionOverrides, restore: bool) ->
|
|
567
|
+
def _session_config_manager(applied: SessionOverrides, *, restore: bool) -> AbstractContextManager[object]:
|
|
565
568
|
"""Return the context manager used to apply session overrides."""
|
|
566
569
|
if restore:
|
|
567
570
|
return config_overrides(**applied)
|
|
@@ -630,7 +633,7 @@ def run_cli(
|
|
|
630
633
|
|
|
631
634
|
specs = _resolve_signal_specs(signal_specs)
|
|
632
635
|
handler = _choose_exception_handler(exception_handler, specs)
|
|
633
|
-
restorer = _install_signal_handlers_when_requested(install_signals, signal_installer, specs)
|
|
636
|
+
restorer = _install_signal_handlers_when_requested(install_signals=install_signals, signal_installer=signal_installer, specs=specs)
|
|
634
637
|
|
|
635
638
|
try:
|
|
636
639
|
return _run_command_with_handler(cli, argv, prog_name, handler)
|
|
@@ -658,6 +661,7 @@ def _default_exception_handler(specs: Sequence[SignalSpec]) -> Callable[[BaseExc
|
|
|
658
661
|
|
|
659
662
|
|
|
660
663
|
def _install_signal_handlers_when_requested(
|
|
664
|
+
*,
|
|
661
665
|
install_signals: bool,
|
|
662
666
|
signal_installer: Callable[[Sequence[SignalSpec] | None], Callable[[], None]] | None,
|
|
663
667
|
specs: Sequence[SignalSpec],
|
|
@@ -678,7 +682,7 @@ def _run_command_with_handler(
|
|
|
678
682
|
"""Invoke the Click command and delegate failures to ``handler``."""
|
|
679
683
|
try:
|
|
680
684
|
_invoke_command(cli, argv, prog_name)
|
|
681
|
-
except BaseException as exc: #
|
|
685
|
+
except BaseException as exc: # single funnel for exit codes; handler translates every exception type
|
|
682
686
|
return handler(exc)
|
|
683
687
|
return 0
|
|
684
688
|
|
|
File without changes
|
|
@@ -15,8 +15,7 @@ from __future__ import annotations
|
|
|
15
15
|
|
|
16
16
|
import rich_click as click
|
|
17
17
|
|
|
18
|
-
from .. import __init__conf__
|
|
19
|
-
from .. import lib_cli_exit_tools
|
|
18
|
+
from .. import __init__conf__, lib_cli_exit_tools
|
|
20
19
|
|
|
21
20
|
#: Help flag aliases applied to every Click command so documentation and CLI
|
|
22
21
|
#: behaviour stay consistent (`-h` mirrors `--help`).
|
lib_cli_exit_tools/cli/group.py
CHANGED
|
@@ -14,17 +14,19 @@ System Integration:
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
-
from collections.abc import Sequence
|
|
18
17
|
from dataclasses import dataclass
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
19
|
|
|
20
20
|
import rich_click as click
|
|
21
21
|
|
|
22
|
-
from .. import __init__conf__
|
|
23
|
-
from .. import lib_cli_exit_tools
|
|
22
|
+
from .. import __init__conf__, lib_cli_exit_tools
|
|
24
23
|
from .commands import CLICK_CONTEXT_SETTINGS
|
|
25
24
|
from .styling import _temporary_rich_click_configuration # pyright: ignore[reportPrivateUsage]
|
|
26
25
|
from .typed_click import option, version_option
|
|
27
26
|
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from collections.abc import Sequence
|
|
29
|
+
|
|
28
30
|
|
|
29
31
|
@dataclass
|
|
30
32
|
class CliContextState:
|
|
@@ -16,11 +16,14 @@ System Integration:
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
18
|
from contextlib import contextmanager
|
|
19
|
-
from typing import
|
|
19
|
+
from typing import TYPE_CHECKING, Any, TypedDict
|
|
20
20
|
|
|
21
21
|
import rich_click as click
|
|
22
22
|
from rich_click import rich_click as rich_config
|
|
23
23
|
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from collections.abc import Generator
|
|
26
|
+
|
|
24
27
|
|
|
25
28
|
class RichClickSnapshot(TypedDict):
|
|
26
29
|
"""Type-safe snapshot of rich-click global styling configuration.
|
|
@@ -1,29 +1,51 @@
|
|
|
1
|
-
"""Strictly-typed wrappers for rich_click
|
|
2
|
-
|
|
3
|
-
rich_click ships ``py.typed
|
|
4
|
-
|
|
5
|
-
``reportUnknownMemberType`` at every call site.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
"""Strictly-typed wrappers for rich_click's partially-typed decorators.
|
|
2
|
+
|
|
3
|
+
rich_click ships ``py.typed``, but its ``option`` and ``version_option``
|
|
4
|
+
decorators are typed with a partially-unknown return, so the strict type checker
|
|
5
|
+
reports ``reportUnknownMemberType`` at every call site. click's own decorators
|
|
6
|
+
are fully typed, but they default the parameter class to ``click.Option`` rather
|
|
7
|
+
than rich_click's ``RichOption``, which would change help rendering.
|
|
8
|
+
|
|
9
|
+
This module forwards to rich_click's decorators (so ``RichOption``/``RichArgument``
|
|
10
|
+
are still used at runtime) through a typed ``Protocol``: the ``cast`` is a no-op
|
|
11
|
+
at runtime, and the wrappers keep the exact same public signatures they always
|
|
12
|
+
had, so no rule needs to be silenced anywhere.
|
|
13
|
+
|
|
14
|
+
Other click members (``command``, ``group``, ``echo``, ``Context``, ``Path`` ...)
|
|
15
|
+
type cleanly and are still used directly as ``click.X`` at call sites.
|
|
9
16
|
"""
|
|
10
17
|
|
|
11
18
|
from collections.abc import Callable
|
|
12
|
-
from typing import Any
|
|
19
|
+
from typing import Any, Protocol, cast
|
|
13
20
|
|
|
14
21
|
import rich_click as click
|
|
15
22
|
|
|
23
|
+
# Click decorators turn a command function (or another decorator's result) into a
|
|
24
|
+
# wrapped callable. ``Any`` is fully known to the type checker, so this alias stays
|
|
25
|
+
# clean and works both as ``@option(...)`` and as a value collected into a list.
|
|
16
26
|
_CommandDecorator = Callable[[Callable[..., Any]], Callable[..., Any]]
|
|
17
27
|
|
|
18
28
|
|
|
29
|
+
class _RichClickDecorators(Protocol):
|
|
30
|
+
"""rich_click's decorator surface, declared with complete types."""
|
|
31
|
+
|
|
32
|
+
option: Callable[..., _CommandDecorator]
|
|
33
|
+
version_option: Callable[..., _CommandDecorator]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ``cast`` is type-only; at runtime these forward to rich_click's own decorators,
|
|
37
|
+
# so ``RichOption``/``RichArgument`` behavior is unchanged.
|
|
38
|
+
_click = cast("_RichClickDecorators", click)
|
|
39
|
+
|
|
40
|
+
|
|
19
41
|
def option(*param_decls: str, **attrs: Any) -> _CommandDecorator:
|
|
20
42
|
"""Typed wrapper over :func:`rich_click.option`. See module docstring."""
|
|
21
|
-
return
|
|
43
|
+
return _click.option(*param_decls, **attrs)
|
|
22
44
|
|
|
23
45
|
|
|
24
46
|
def version_option(*param_decls: str, **attrs: Any) -> _CommandDecorator:
|
|
25
47
|
"""Typed wrapper over :func:`rich_click.version_option`. See module docstring."""
|
|
26
|
-
return
|
|
48
|
+
return _click.version_option(*param_decls, **attrs)
|
|
27
49
|
|
|
28
50
|
|
|
29
51
|
__all__ = ["option", "version_option"]
|
|
File without changes
|
|
@@ -5,11 +5,11 @@ Purpose:
|
|
|
5
5
|
adapters can toggle behaviour (tracebacks, exit codes, broken-pipe
|
|
6
6
|
semantics) without re-implementing global state.
|
|
7
7
|
Contents:
|
|
8
|
-
* :class:`_Config`
|
|
9
|
-
* :data:`config`
|
|
10
|
-
* :func:`config_overrides`
|
|
8
|
+
* :class:`_Config` - dataclass capturing toggleable runtime flags.
|
|
9
|
+
* :data:`config` - module-level singleton mutated by CLI adapters and tests.
|
|
10
|
+
* :func:`config_overrides` - context manager that snapshots and restores
|
|
11
11
|
configuration state for embedders and tests.
|
|
12
|
-
* :func:`reset_config`
|
|
12
|
+
* :func:`reset_config` - helper that restores defaults defined by
|
|
13
13
|
:class:`_Config`.
|
|
14
14
|
System Integration:
|
|
15
15
|
Imported by higher layers (`application.runner`, `adapters.click_adapter`)
|
|
@@ -20,11 +20,13 @@ System Integration:
|
|
|
20
20
|
|
|
21
21
|
from __future__ import annotations
|
|
22
22
|
|
|
23
|
-
from collections.abc import Generator, Mapping
|
|
24
23
|
from contextlib import contextmanager
|
|
25
24
|
from dataclasses import dataclass, fields
|
|
26
25
|
from enum import Enum
|
|
27
|
-
from typing import TypedDict
|
|
26
|
+
from typing import TYPE_CHECKING, TypedDict
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from collections.abc import Generator, Mapping
|
|
28
30
|
|
|
29
31
|
__all__ = ["ExitCodeStyle", "_Config", "config", "config_overrides", "reset_config"]
|
|
30
32
|
|
|
@@ -5,8 +5,8 @@ Purpose:
|
|
|
5
5
|
exit codes, honouring both POSIX/Windows errno semantics and BSD sysexits
|
|
6
6
|
conventions.
|
|
7
7
|
Contents:
|
|
8
|
-
* :func:`get_system_exit_code`
|
|
9
|
-
* :func:`_sysexits_mapping`
|
|
8
|
+
* :func:`get_system_exit_code` - primary mapping entry point.
|
|
9
|
+
* :func:`_sysexits_mapping` - internal helper for sysexits mode.
|
|
10
10
|
System Integration:
|
|
11
11
|
Used by application orchestration and CLI adapters to convert unhandled
|
|
12
12
|
exceptions into numeric exit statuses while respecting
|
|
@@ -17,7 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
|
|
18
18
|
import os
|
|
19
19
|
import subprocess # nosec B404 - imported for CalledProcessError type inspection
|
|
20
|
-
from
|
|
20
|
+
from collections.abc import Callable, Iterable, Mapping
|
|
21
21
|
|
|
22
22
|
from .configuration import ExitCodeStyle, config
|
|
23
23
|
|
|
@@ -143,7 +143,7 @@ def _code_from_winerror_attribute(exc: BaseException) -> int | None:
|
|
|
143
143
|
"""
|
|
144
144
|
if not hasattr(exc, "winerror"):
|
|
145
145
|
return None
|
|
146
|
-
return _safe_int(getattr(exc, "winerror"))
|
|
146
|
+
return _safe_int(getattr(exc, "winerror")) # noqa: B009 - exc is typed BaseException; only OSError carries winerror, and only on Windows
|
|
147
147
|
|
|
148
148
|
|
|
149
149
|
def _code_from_broken_pipe(exc: BaseException) -> int | None:
|
|
@@ -25,8 +25,8 @@ from .adapters.signals import (
|
|
|
25
25
|
CliSignalError,
|
|
26
26
|
SigBreakInterrupt,
|
|
27
27
|
SigIntInterrupt,
|
|
28
|
-
SigTermInterrupt,
|
|
29
28
|
SignalSpec,
|
|
29
|
+
SigTermInterrupt,
|
|
30
30
|
default_signal_specs,
|
|
31
31
|
install_signal_handlers,
|
|
32
32
|
)
|
|
@@ -41,24 +41,24 @@ from .core.configuration import ExitCodeStyle, config, config_overrides, reset_c
|
|
|
41
41
|
from .core.exit_codes import get_system_exit_code
|
|
42
42
|
|
|
43
43
|
__all__ = [
|
|
44
|
-
"ExitCodeStyle",
|
|
45
|
-
"config",
|
|
46
|
-
"get_system_exit_code",
|
|
47
|
-
"print_exception_message",
|
|
48
|
-
"flush_streams",
|
|
49
|
-
"SignalSpec",
|
|
50
44
|
"CliSignalError",
|
|
45
|
+
"ExitCodeStyle",
|
|
46
|
+
"SigBreakInterrupt",
|
|
51
47
|
"SigIntInterrupt",
|
|
52
48
|
"SigTermInterrupt",
|
|
53
|
-
"
|
|
49
|
+
"SignalSpec",
|
|
50
|
+
"cli_session",
|
|
51
|
+
"config",
|
|
52
|
+
"config_overrides",
|
|
54
53
|
"default_signal_specs",
|
|
55
|
-
"
|
|
54
|
+
"flush_streams",
|
|
55
|
+
"get_system_exit_code",
|
|
56
56
|
"handle_cli_exception",
|
|
57
57
|
"i_should_fail",
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"config_overrides",
|
|
58
|
+
"install_signal_handlers",
|
|
59
|
+
"print_exception_message",
|
|
61
60
|
"reset_config",
|
|
61
|
+
"run_cli",
|
|
62
62
|
]
|
|
63
63
|
|
|
64
64
|
PUBLIC_API = tuple(__all__)
|
lib_cli_exit_tools/py.typed
CHANGED
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lib_cli_exit_tools
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.3
|
|
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
|
|
@@ -25,18 +25,18 @@ Requires-Dist: rich-click>=1.9.8
|
|
|
25
25
|
Provides-Extra: dev
|
|
26
26
|
Requires-Dist: bandit>=1.9.4; extra == 'dev'
|
|
27
27
|
Requires-Dist: build>=1.5.0; extra == 'dev'
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
Requires-Dist: coverage[toml]>=7.
|
|
30
|
-
Requires-Dist: hypothesis>=6.
|
|
31
|
-
Requires-Dist: import-linter>=2.
|
|
28
|
+
Requires-Dist: click>=8.4.2; extra == 'dev'
|
|
29
|
+
Requires-Dist: coverage[toml]>=7.15.2; extra == 'dev'
|
|
30
|
+
Requires-Dist: hypothesis>=6.161.2; extra == 'dev'
|
|
31
|
+
Requires-Dist: import-linter>=2.13; extra == 'dev'
|
|
32
32
|
Requires-Dist: pip-audit>=2.10.1; extra == 'dev'
|
|
33
|
-
Requires-Dist: pyright[nodejs]>=1.1.
|
|
33
|
+
Requires-Dist: pyright[nodejs]>=1.1.411; extra == 'dev'
|
|
34
34
|
Requires-Dist: pytest-asyncio>=1.4.0; extra == 'dev'
|
|
35
35
|
Requires-Dist: pytest-cov>=7.1.0; extra == 'dev'
|
|
36
|
-
Requires-Dist: pytest>=9.1.
|
|
36
|
+
Requires-Dist: pytest>=9.1.1; extra == 'dev'
|
|
37
37
|
Requires-Dist: rtoml>=0.13.0; extra == 'dev'
|
|
38
|
-
Requires-Dist: ruff>=0.
|
|
39
|
-
Requires-Dist: textual>=8.2.
|
|
38
|
+
Requires-Dist: ruff>=0.16.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: textual>=8.2.8; extra == 'dev'
|
|
40
40
|
Requires-Dist: twine>=6.2.0; extra == 'dev'
|
|
41
41
|
Description-Content-Type: text/markdown
|
|
42
42
|
|
|
@@ -286,6 +286,7 @@ def install_custom_signals(specs: list[SignalSpec] | None) -> Callable[[], None]
|
|
|
286
286
|
stack = ExitStack()
|
|
287
287
|
|
|
288
288
|
for spec in specs or []:
|
|
289
|
+
|
|
289
290
|
def _handler(signum: int, frame: object | None, *, spec: SignalSpec = spec) -> None:
|
|
290
291
|
raise spec.exception(f"Signal {spec.message}")
|
|
291
292
|
|
|
@@ -331,8 +332,8 @@ lib_cli_exit_tools.config.traceback = False # show short messages
|
|
|
331
332
|
try:
|
|
332
333
|
raise FileNotFoundError("missing.txt")
|
|
333
334
|
except Exception as e:
|
|
334
|
-
code = lib_cli_exit_tools.get_system_exit_code(e)
|
|
335
|
-
lib_cli_exit_tools.print_exception_message()
|
|
335
|
+
code = lib_cli_exit_tools.get_system_exit_code(e) # 2 on POSIX
|
|
336
|
+
lib_cli_exit_tools.print_exception_message() # prints: FileNotFoundError: missing.txt
|
|
336
337
|
raise SystemExit(code)
|
|
337
338
|
```
|
|
338
339
|
|
|
@@ -355,9 +356,9 @@ All configuration lives on the module-level `lib_cli_exit_tools.config` object.
|
|
|
355
356
|
```python
|
|
356
357
|
from lib_cli_exit_tools import config
|
|
357
358
|
|
|
358
|
-
config.traceback = True
|
|
359
|
+
config.traceback = True # emit full tracebacks instead of short messages
|
|
359
360
|
config.exit_code_style = "sysexits" # emit BSD-style exit codes (EX_USAGE, EX_NOINPUT, …)
|
|
360
|
-
config.broken_pipe_exit_code = 0
|
|
361
|
+
config.broken_pipe_exit_code = 0 # treat BrokenPipeError as a benign truncation
|
|
361
362
|
```
|
|
362
363
|
|
|
363
364
|
Field reference:
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
lib_cli_exit_tools/__init__.py,sha256=KzpZAMrc328V_nYm2GuTR0Sxyqh0XuKtlHP6dVCs2pc,1905
|
|
2
|
+
lib_cli_exit_tools/__init__conf__.py,sha256=b534uXj2ERNqtNu5PlbbK1lFwd867h0cmF_919XXOkw,2551
|
|
3
|
+
lib_cli_exit_tools/__main__.py,sha256=wODJaNu_NyO7tknvlpPArq0MaDwDum8zqyb73-qshzo,257
|
|
4
|
+
lib_cli_exit_tools/lib_cli_exit_tools.py,sha256=QIKdg_Z1_RkJe5FGaM4-bTHDcX4NTo_ecrSMG6dZyIY,2561
|
|
5
|
+
lib_cli_exit_tools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
lib_cli_exit_tools/adapters/__init__.py,sha256=Jj8FCGY1lAPAduYSW3wTDh8ojpr9BZms7wKEMPq8pWE,504
|
|
7
|
+
lib_cli_exit_tools/adapters/signals.py,sha256=vRgKr3cozB5WJGel1meACJ24OYhACUM1YCyRAqI2S8U,6125
|
|
8
|
+
lib_cli_exit_tools/application/__init__.py,sha256=Yikej3OOOaAjWOvFimg1jUYrCUGu5d3-RBiiai4tHDM,566
|
|
9
|
+
lib_cli_exit_tools/application/runner.py,sha256=15QA5rqZc-Tplljv_bENxvwN4nQzxPCE1Hepm4twom8,24667
|
|
10
|
+
lib_cli_exit_tools/cli/__init__.py,sha256=IVyU1DEgn2xBYfp1BBpund9nucUEPyOu7yt8DShu470,1094
|
|
11
|
+
lib_cli_exit_tools/cli/commands.py,sha256=5eDpFhFMUN1hIJLSdQcWp28iGtAxXPfchP8cwNNRFQE,2404
|
|
12
|
+
lib_cli_exit_tools/cli/group.py,sha256=Ic5nAMewWVeTCtbri6WWa2C1YWIi5ewlRGIFkTGED8Y,4302
|
|
13
|
+
lib_cli_exit_tools/cli/styling.py,sha256=v5RSNfNVvgu8KUpBEyvlFRKCc14p601H5JD4uyeybA4,4303
|
|
14
|
+
lib_cli_exit_tools/cli/typed_click.py,sha256=VLZgUkCZ6AFnf6StvoztK3JVYj9lZmgPSNY2Wjb3B8g,2183
|
|
15
|
+
lib_cli_exit_tools/core/__init__.py,sha256=aR7OIBWOQ1PsA33blOTgLrq5ffKDCuaMVBS5mbrDAZ8,531
|
|
16
|
+
lib_cli_exit_tools/core/configuration.py,sha256=XWM4KYAAhlTZgF71z7JQDOfm7IshezzPZt6z043WQuU,6153
|
|
17
|
+
lib_cli_exit_tools/core/exit_codes.py,sha256=dlwvjsfMFrbEfWJVjYznmQfAa-JAHVrtY7FGPSMxppo,12449
|
|
18
|
+
lib_cli_exit_tools-2.3.3.dist-info/METADATA,sha256=CnxLc0V9HAs9J4CiKkRMm5gNjMAqC7t4xou5nnjq4k4,26310
|
|
19
|
+
lib_cli_exit_tools-2.3.3.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
20
|
+
lib_cli_exit_tools-2.3.3.dist-info/entry_points.txt,sha256=8R-_3UT0KcQ7HQ7idDx6rlG77GhHb0E2N5QnN41dj-M,161
|
|
21
|
+
lib_cli_exit_tools-2.3.3.dist-info/licenses/LICENSE,sha256=6s9SL2InRrwynPjJYiER7ONHBshbGj_yC5EBzfo7l9I,1072
|
|
22
|
+
lib_cli_exit_tools-2.3.3.dist-info/RECORD,,
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
lib_cli_exit_tools/__init__.py,sha256=KzpZAMrc328V_nYm2GuTR0Sxyqh0XuKtlHP6dVCs2pc,1905
|
|
2
|
-
lib_cli_exit_tools/__init__conf__.py,sha256=PgIe-Fn7sU48afeTt7UWbyqPvIyHaU4YwECgRQ5fnbw,2521
|
|
3
|
-
lib_cli_exit_tools/__main__.py,sha256=wODJaNu_NyO7tknvlpPArq0MaDwDum8zqyb73-qshzo,257
|
|
4
|
-
lib_cli_exit_tools/lib_cli_exit_tools.py,sha256=fCLgJypLi6VHjmnh0McUn3CbhBhdzGYdDGcwcLokL4o,2561
|
|
5
|
-
lib_cli_exit_tools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
lib_cli_exit_tools/adapters/__init__.py,sha256=Jj8FCGY1lAPAduYSW3wTDh8ojpr9BZms7wKEMPq8pWE,504
|
|
7
|
-
lib_cli_exit_tools/adapters/signals.py,sha256=Q2D9Htv64nsT5n66cJFDDHRWVXJokaG77I-V8gTDMKg,5772
|
|
8
|
-
lib_cli_exit_tools/application/__init__.py,sha256=Yikej3OOOaAjWOvFimg1jUYrCUGu5d3-RBiiai4tHDM,566
|
|
9
|
-
lib_cli_exit_tools/application/runner.py,sha256=Dz58vHqgKiGNNILRsQSlySCOiYdcpbf6gs67WEgbG_g,24416
|
|
10
|
-
lib_cli_exit_tools/cli/__init__.py,sha256=IVyU1DEgn2xBYfp1BBpund9nucUEPyOu7yt8DShu470,1094
|
|
11
|
-
lib_cli_exit_tools/cli/commands.py,sha256=WDF6hDsyczAb6cQp4fA6KMF1iqJQ1lzjv-RvIxq1itI,2418
|
|
12
|
-
lib_cli_exit_tools/cli/group.py,sha256=nhMICASyfbAB1qDI9bwIHJ5IHxrGBf6uZOZSF3N97cg,4260
|
|
13
|
-
lib_cli_exit_tools/cli/styling.py,sha256=Q23pmmzq7GpPWWbqAXXrJrZoqt_xlJi4Q0gtSJiknBI,4238
|
|
14
|
-
lib_cli_exit_tools/cli/typed_click.py,sha256=xQJtl1EN2HC937N48i_z6P_6CdEpJXXDh_BUbzhNI9g,1249
|
|
15
|
-
lib_cli_exit_tools/core/__init__.py,sha256=aR7OIBWOQ1PsA33blOTgLrq5ffKDCuaMVBS5mbrDAZ8,531
|
|
16
|
-
lib_cli_exit_tools/core/configuration.py,sha256=rv911nWmFPQmJczP_CDNXhJ_5eAJPSBmwYCPr5DN40U,6123
|
|
17
|
-
lib_cli_exit_tools/core/exit_codes.py,sha256=ukinlUROFKb6KmF_-8luxE1C2VJc6UwxIMMr0eB9xFg,12349
|
|
18
|
-
lib_cli_exit_tools-2.3.2.dist-info/METADATA,sha256=VvvVEpr8HKXNcksi81PKNCzpkIJzkjJm8fYf69bKgbU,26339
|
|
19
|
-
lib_cli_exit_tools-2.3.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
20
|
-
lib_cli_exit_tools-2.3.2.dist-info/entry_points.txt,sha256=8R-_3UT0KcQ7HQ7idDx6rlG77GhHb0E2N5QnN41dj-M,161
|
|
21
|
-
lib_cli_exit_tools-2.3.2.dist-info/licenses/LICENSE,sha256=6s9SL2InRrwynPjJYiER7ONHBshbGj_yC5EBzfo7l9I,1072
|
|
22
|
-
lib_cli_exit_tools-2.3.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|