lib-cli-exit-tools 2.3.2__py3-none-any.whl → 2.3.4__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 +40 -23
- 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.4.dist-info}/METADATA +15 -14
- lib_cli_exit_tools-2.3.4.dist-info/RECORD +22 -0
- {lib_cli_exit_tools-2.3.2.dist-info → lib_cli_exit_tools-2.3.4.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.4.dist-info}/entry_points.txt +0 -0
- {lib_cli_exit_tools-2.3.2.dist-info → lib_cli_exit_tools-2.3.4.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.4"
|
|
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,20 @@ 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
|
+
) -> object:
|
|
64
|
+
"""Return Click's own result: ctx.exit()'s code, or None on success."""
|
|
65
|
+
...
|
|
63
66
|
|
|
64
67
|
|
|
65
68
|
__all__ = [
|
|
69
|
+
"SessionOverrides",
|
|
70
|
+
"cli_session",
|
|
71
|
+
"flush_streams",
|
|
66
72
|
"handle_cli_exception",
|
|
67
73
|
"print_exception_message",
|
|
68
|
-
"flush_streams",
|
|
69
74
|
"run_cli",
|
|
70
|
-
"cli_session",
|
|
71
|
-
"SessionOverrides",
|
|
72
75
|
]
|
|
73
76
|
|
|
74
77
|
|
|
@@ -162,7 +165,7 @@ def _build_console(
|
|
|
162
165
|
|
|
163
166
|
target_stream = stream or sys.stderr
|
|
164
167
|
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))
|
|
168
|
+
default_color = cast("RichColorSystem | None", getattr(rich_config, "COLOR_SYSTEM", None))
|
|
166
169
|
color_flag = default_color if color_system is None else color_system
|
|
167
170
|
return Console(
|
|
168
171
|
file=target_stream,
|
|
@@ -216,6 +219,7 @@ def _decode_output(output: object) -> str | None:
|
|
|
216
219
|
|
|
217
220
|
|
|
218
221
|
def print_exception_message(
|
|
222
|
+
*,
|
|
219
223
|
trace_back: bool | None = None,
|
|
220
224
|
length_limit: int = 500,
|
|
221
225
|
stream: TextIO | None = None,
|
|
@@ -245,10 +249,10 @@ def print_exception_message(
|
|
|
245
249
|
target_stream = _target_stream(stream)
|
|
246
250
|
_emit_subprocess_output(exc_info, target_stream)
|
|
247
251
|
|
|
248
|
-
render_traceback = _resolve_traceback_choice(trace_back)
|
|
252
|
+
render_traceback = _resolve_traceback_choice(trace_back=trace_back)
|
|
249
253
|
console = _console_for_tracebacks(target_stream)
|
|
250
254
|
|
|
251
|
-
_render_exception_view(console, exc_info, render_traceback, length_limit)
|
|
255
|
+
_render_exception_view(console, exc_info, render_traceback=render_traceback, length_limit=length_limit)
|
|
252
256
|
_finalise_console(console)
|
|
253
257
|
flush_streams()
|
|
254
258
|
|
|
@@ -263,7 +267,7 @@ def _target_stream(stream: TextIO | None) -> TextIO:
|
|
|
263
267
|
return stream or sys.stderr
|
|
264
268
|
|
|
265
269
|
|
|
266
|
-
def _resolve_traceback_choice(trace_back: bool | None) -> bool:
|
|
270
|
+
def _resolve_traceback_choice(*, trace_back: bool | None) -> bool:
|
|
267
271
|
"""Decide whether to render a traceback based on explicit or global flags."""
|
|
268
272
|
return config.traceback if trace_back is None else trace_back
|
|
269
273
|
|
|
@@ -283,6 +287,7 @@ def _console_for_tracebacks(stream: TextIO) -> Console:
|
|
|
283
287
|
def _render_exception_view(
|
|
284
288
|
console: Console,
|
|
285
289
|
exc_info: BaseException,
|
|
290
|
+
*,
|
|
286
291
|
render_traceback: bool,
|
|
287
292
|
length_limit: int,
|
|
288
293
|
) -> None:
|
|
@@ -510,7 +515,7 @@ def cli_session(
|
|
|
510
515
|
"""
|
|
511
516
|
|
|
512
517
|
applied = _normalise_session_overrides(overrides)
|
|
513
|
-
manager = _session_config_manager(applied, restore)
|
|
518
|
+
manager = _session_config_manager(applied, restore=restore)
|
|
514
519
|
|
|
515
520
|
with manager:
|
|
516
521
|
handler = _session_exception_handler(summary_limit, verbose_limit)
|
|
@@ -561,7 +566,7 @@ def _normalise_session_overrides(overrides: SessionOverrides | None) -> SessionO
|
|
|
561
566
|
return overrides
|
|
562
567
|
|
|
563
568
|
|
|
564
|
-
def _session_config_manager(applied: SessionOverrides, restore: bool) ->
|
|
569
|
+
def _session_config_manager(applied: SessionOverrides, *, restore: bool) -> AbstractContextManager[object]:
|
|
565
570
|
"""Return the context manager used to apply session overrides."""
|
|
566
571
|
if restore:
|
|
567
572
|
return config_overrides(**applied)
|
|
@@ -630,7 +635,7 @@ def run_cli(
|
|
|
630
635
|
|
|
631
636
|
specs = _resolve_signal_specs(signal_specs)
|
|
632
637
|
handler = _choose_exception_handler(exception_handler, specs)
|
|
633
|
-
restorer = _install_signal_handlers_when_requested(install_signals, signal_installer, specs)
|
|
638
|
+
restorer = _install_signal_handlers_when_requested(install_signals=install_signals, signal_installer=signal_installer, specs=specs)
|
|
634
639
|
|
|
635
640
|
try:
|
|
636
641
|
return _run_command_with_handler(cli, argv, prog_name, handler)
|
|
@@ -658,6 +663,7 @@ def _default_exception_handler(specs: Sequence[SignalSpec]) -> Callable[[BaseExc
|
|
|
658
663
|
|
|
659
664
|
|
|
660
665
|
def _install_signal_handlers_when_requested(
|
|
666
|
+
*,
|
|
661
667
|
install_signals: bool,
|
|
662
668
|
signal_installer: Callable[[Sequence[SignalSpec] | None], Callable[[], None]] | None,
|
|
663
669
|
specs: Sequence[SignalSpec],
|
|
@@ -677,10 +683,15 @@ def _run_command_with_handler(
|
|
|
677
683
|
) -> int:
|
|
678
684
|
"""Invoke the Click command and delegate failures to ``handler``."""
|
|
679
685
|
try:
|
|
680
|
-
_invoke_command(cli, argv, prog_name)
|
|
681
|
-
except BaseException as exc: #
|
|
686
|
+
outcome = _invoke_command(cli, argv, prog_name)
|
|
687
|
+
except BaseException as exc: # single funnel for exit codes; handler translates every exception type
|
|
682
688
|
return handler(exc)
|
|
683
|
-
return
|
|
689
|
+
# Click is invoked with standalone_mode=False, which makes it *return*
|
|
690
|
+
# ctx.exit()'s code rather than raising it. That is how a command reports a
|
|
691
|
+
# negative answer - nothing found, host did not respond - as distinct from
|
|
692
|
+
# an error. Discarding this value would collapse every such outcome to
|
|
693
|
+
# success; None is Click's plain-success result and stays 0.
|
|
694
|
+
return outcome if isinstance(outcome, int) else 0
|
|
684
695
|
|
|
685
696
|
|
|
686
697
|
def _finalise_cli_run(restorer: Callable[[], None] | None) -> None:
|
|
@@ -689,9 +700,15 @@ def _finalise_cli_run(restorer: Callable[[], None] | None) -> None:
|
|
|
689
700
|
flush_streams()
|
|
690
701
|
|
|
691
702
|
|
|
692
|
-
def _invoke_command(cli: ClickCommand, argv: Sequence[str] | None, prog_name: str | None) ->
|
|
693
|
-
"""Invoke the Click command with ``standalone_mode`` disabled.
|
|
694
|
-
|
|
703
|
+
def _invoke_command(cli: ClickCommand, argv: Sequence[str] | None, prog_name: str | None) -> object:
|
|
704
|
+
"""Invoke the Click command with ``standalone_mode`` disabled.
|
|
705
|
+
|
|
706
|
+
Returns:
|
|
707
|
+
Whatever Click returns: the code passed to ``ctx.exit()``, or ``None``
|
|
708
|
+
when the command simply completed.
|
|
709
|
+
"""
|
|
710
|
+
|
|
711
|
+
return cli.main(args=_normalised_args(argv), standalone_mode=False, prog_name=prog_name)
|
|
695
712
|
|
|
696
713
|
|
|
697
714
|
def _normalised_args(argv: Sequence[str] | None) -> Sequence[str] | None:
|
|
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.4
|
|
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,19 +25,19 @@ 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.163.0; 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.
|
|
40
|
-
Requires-Dist: twine>=
|
|
38
|
+
Requires-Dist: ruff>=0.16.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: textual>=8.2.8; extra == 'dev'
|
|
40
|
+
Requires-Dist: twine>=7.0.0; extra == 'dev'
|
|
41
41
|
Description-Content-Type: text/markdown
|
|
42
42
|
|
|
43
43
|
# lib_cli_exit_tools
|
|
@@ -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=G8Wfni4rgKyD0DwTW3NkaezV8ADKWZSl8oI78ehNbbM,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=vPMibF5pvuBA1vTrWr8wyjk5MKvDcFhxrTbwCXSgnpQ,25333
|
|
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.4.dist-info/METADATA,sha256=jnGFlt6aff1a0ogGme_X3etEd7aZiWGy460RFymGAqc,26310
|
|
19
|
+
lib_cli_exit_tools-2.3.4.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
20
|
+
lib_cli_exit_tools-2.3.4.dist-info/entry_points.txt,sha256=8R-_3UT0KcQ7HQ7idDx6rlG77GhHb0E2N5QnN41dj-M,161
|
|
21
|
+
lib_cli_exit_tools-2.3.4.dist-info/licenses/LICENSE,sha256=6s9SL2InRrwynPjJYiER7ONHBshbGj_yC5EBzfo7l9I,1072
|
|
22
|
+
lib_cli_exit_tools-2.3.4.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
|