lib-cli-exit-tools 2.2.1__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 +47 -0
- lib_cli_exit_tools/__init__conf__.py +74 -0
- lib_cli_exit_tools/__main__.py +13 -0
- lib_cli_exit_tools/adapters/__init__.py +16 -0
- lib_cli_exit_tools/adapters/signals.py +178 -0
- lib_cli_exit_tools/application/__init__.py +16 -0
- lib_cli_exit_tools/application/runner.py +714 -0
- lib_cli_exit_tools/cli.py +261 -0
- lib_cli_exit_tools/core/__init__.py +16 -0
- lib_cli_exit_tools/core/configuration.py +155 -0
- lib_cli_exit_tools/core/exit_codes.py +420 -0
- lib_cli_exit_tools/lib_cli_exit_tools.py +83 -0
- lib_cli_exit_tools/py.typed +0 -0
- lib_cli_exit_tools-2.2.1.dist-info/METADATA +647 -0
- lib_cli_exit_tools-2.2.1.dist-info/RECORD +18 -0
- lib_cli_exit_tools-2.2.1.dist-info/WHEEL +4 -0
- lib_cli_exit_tools-2.2.1.dist-info/entry_points.txt +4 -0
- lib_cli_exit_tools-2.2.1.dist-info/licenses/LICENSE +22 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""Click-based CLI adapter for lib_cli_exit_tools.
|
|
2
|
+
|
|
3
|
+
Purpose:
|
|
4
|
+
Provide the end-user command-line surface while delegating error-handling
|
|
5
|
+
and exit-code logic to :mod:`lib_cli_exit_tools.lib_cli_exit_tools`.
|
|
6
|
+
Contents:
|
|
7
|
+
* :func:`cli` group exposing shared options.
|
|
8
|
+
* :func:`cli_info` subcommand reporting distribution metadata.
|
|
9
|
+
* :func:`cli_fail` subcommand triggering a deterministic failure for testing error paths.
|
|
10
|
+
* :func:`main` entry point used by console scripts and ``python -m``.
|
|
11
|
+
System Integration:
|
|
12
|
+
The CLI mutates :data:`lib_cli_exit_tools.config` based on the ``--traceback``
|
|
13
|
+
flag before handing execution off to :func:`lib_cli_exit_tools.run_cli`.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from contextlib import contextmanager
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import Any, Iterator, Sequence, TypedDict
|
|
21
|
+
|
|
22
|
+
import rich_click as click
|
|
23
|
+
from rich_click import rich_click as rich_config
|
|
24
|
+
|
|
25
|
+
from . import __init__conf__
|
|
26
|
+
from . import lib_cli_exit_tools
|
|
27
|
+
|
|
28
|
+
#: Help flag aliases applied to every Click command so documentation and CLI
|
|
29
|
+
#: behaviour stay consistent (`-h` mirrors `--help`).
|
|
30
|
+
#: Note: This uses dict literal syntax as required by Click framework API.
|
|
31
|
+
CLICK_CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RichClickSnapshot(TypedDict):
|
|
35
|
+
"""Type-safe snapshot of rich-click global styling configuration.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
FORCE_TERMINAL: Whether to force terminal styling.
|
|
39
|
+
COLOR_SYSTEM: Color system to use (e.g., "auto", "256", "truecolor", None).
|
|
40
|
+
STYLE_OPTIONS_PANEL_BOX: Box style for options panel.
|
|
41
|
+
STYLE_COMMANDS_PANEL_BOX: Box style for commands panel.
|
|
42
|
+
STYLE_ERRORS_PANEL_BOX: Box style for errors panel.
|
|
43
|
+
|
|
44
|
+
Note:
|
|
45
|
+
Uses Any for field types to accommodate the dynamic nature of rich-click
|
|
46
|
+
configuration, which doesn't provide stable typing guarantees.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
FORCE_TERMINAL: Any
|
|
50
|
+
COLOR_SYSTEM: Any
|
|
51
|
+
STYLE_OPTIONS_PANEL_BOX: Any
|
|
52
|
+
STYLE_COMMANDS_PANEL_BOX: Any
|
|
53
|
+
STYLE_ERRORS_PANEL_BOX: Any
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class CliContextState:
|
|
58
|
+
"""Type-safe container for Click context object state.
|
|
59
|
+
|
|
60
|
+
Why:
|
|
61
|
+
Replaces untyped dict usage in ctx.obj to enforce type safety and
|
|
62
|
+
prevent string literal key access violations.
|
|
63
|
+
|
|
64
|
+
Attributes:
|
|
65
|
+
traceback: Whether to show full Python tracebacks on errors.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
traceback: bool = False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _needs_plain_output(stream: object) -> bool:
|
|
72
|
+
"""Tell whether ``stream`` will honour Rich styling."""
|
|
73
|
+
|
|
74
|
+
return (not _stream_is_tty(stream)) or (not _stream_supports_utf(stream))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _stream_is_tty(stream: object) -> bool:
|
|
78
|
+
"""Return ``True`` when ``stream`` behaves like a TTY."""
|
|
79
|
+
checker = getattr(stream, "isatty", lambda: False)
|
|
80
|
+
try:
|
|
81
|
+
return bool(checker())
|
|
82
|
+
except Exception: # pragma: no cover - defensive shield
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _stream_supports_utf(stream: object) -> bool:
|
|
87
|
+
"""Tell whether ``stream`` reports a UTF-friendly encoding."""
|
|
88
|
+
encoding = (getattr(stream, "encoding", "") or "").lower()
|
|
89
|
+
return "utf" in encoding
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _prefer_ascii_layout() -> None:
|
|
93
|
+
"""Downgrade rich-click global styling to ASCII-friendly defaults."""
|
|
94
|
+
rich_config.FORCE_TERMINAL = False
|
|
95
|
+
rich_config.COLOR_SYSTEM = None
|
|
96
|
+
rich_config.STYLE_OPTIONS_PANEL_BOX = None
|
|
97
|
+
rich_config.STYLE_COMMANDS_PANEL_BOX = None
|
|
98
|
+
rich_config.STYLE_ERRORS_PANEL_BOX = None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _snapshot_rich_click_options() -> RichClickSnapshot:
|
|
102
|
+
"""Capture rich-click global styling toggles for later restoration."""
|
|
103
|
+
|
|
104
|
+
return RichClickSnapshot(
|
|
105
|
+
FORCE_TERMINAL=getattr(rich_config, "FORCE_TERMINAL", None),
|
|
106
|
+
COLOR_SYSTEM=getattr(rich_config, "COLOR_SYSTEM", None),
|
|
107
|
+
STYLE_OPTIONS_PANEL_BOX=getattr(rich_config, "STYLE_OPTIONS_PANEL_BOX", None),
|
|
108
|
+
STYLE_COMMANDS_PANEL_BOX=getattr(rich_config, "STYLE_COMMANDS_PANEL_BOX", None),
|
|
109
|
+
STYLE_ERRORS_PANEL_BOX=getattr(rich_config, "STYLE_ERRORS_PANEL_BOX", None),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _restore_rich_click_options(snapshot: RichClickSnapshot) -> None:
|
|
114
|
+
"""Restore rich-click globals to the captured snapshot."""
|
|
115
|
+
|
|
116
|
+
rich_config.FORCE_TERMINAL = snapshot["FORCE_TERMINAL"]
|
|
117
|
+
rich_config.COLOR_SYSTEM = snapshot["COLOR_SYSTEM"]
|
|
118
|
+
rich_config.STYLE_OPTIONS_PANEL_BOX = snapshot["STYLE_OPTIONS_PANEL_BOX"]
|
|
119
|
+
rich_config.STYLE_COMMANDS_PANEL_BOX = snapshot["STYLE_COMMANDS_PANEL_BOX"]
|
|
120
|
+
rich_config.STYLE_ERRORS_PANEL_BOX = snapshot["STYLE_ERRORS_PANEL_BOX"]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@contextmanager
|
|
124
|
+
def _temporary_rich_click_configuration() -> Iterator[None]:
|
|
125
|
+
"""Apply plain-output safeguards and restore rich-click globals afterwards."""
|
|
126
|
+
|
|
127
|
+
snapshot = _snapshot_rich_click_options()
|
|
128
|
+
stdout_stream = click.get_text_stream("stdout")
|
|
129
|
+
stderr_stream = click.get_text_stream("stderr")
|
|
130
|
+
if _needs_plain_output(stdout_stream) and _needs_plain_output(stderr_stream):
|
|
131
|
+
_prefer_ascii_layout()
|
|
132
|
+
try:
|
|
133
|
+
yield
|
|
134
|
+
finally:
|
|
135
|
+
_restore_rich_click_options(snapshot)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@click.group(help=__init__conf__.title, context_settings=CLICK_CONTEXT_SETTINGS)
|
|
139
|
+
@click.version_option(
|
|
140
|
+
version=__init__conf__.version,
|
|
141
|
+
prog_name=__init__conf__.shell_command,
|
|
142
|
+
message=f"{__init__conf__.shell_command} version {__init__conf__.version}",
|
|
143
|
+
)
|
|
144
|
+
@click.option(
|
|
145
|
+
"--traceback/--no-traceback",
|
|
146
|
+
is_flag=True,
|
|
147
|
+
default=False,
|
|
148
|
+
help="Show full Python traceback on errors",
|
|
149
|
+
)
|
|
150
|
+
@click.pass_context
|
|
151
|
+
def cli(ctx: click.Context, traceback: bool) -> None:
|
|
152
|
+
"""Root Click group that primes shared configuration state.
|
|
153
|
+
|
|
154
|
+
Why:
|
|
155
|
+
Accept a single ``--traceback`` flag that determines whether downstream
|
|
156
|
+
helpers emit stack traces.
|
|
157
|
+
Parameters:
|
|
158
|
+
ctx: Click context object for the current invocation.
|
|
159
|
+
traceback: When ``True`` enables traceback output for subsequent commands.
|
|
160
|
+
Side Effects:
|
|
161
|
+
Mutates ``ctx.obj`` and :data:`lib_cli_exit_tools.config.traceback`.
|
|
162
|
+
Examples:
|
|
163
|
+
>>> from click.testing import CliRunner
|
|
164
|
+
>>> runner = CliRunner()
|
|
165
|
+
>>> result = runner.invoke(cli, ["--help"])
|
|
166
|
+
>>> result.exit_code == 0
|
|
167
|
+
True
|
|
168
|
+
"""
|
|
169
|
+
_store_traceback_flag(ctx, traceback)
|
|
170
|
+
lib_cli_exit_tools.config.traceback = traceback
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _store_traceback_flag(ctx: click.Context, traceback: bool) -> None:
|
|
174
|
+
"""Store traceback flag in typed context state.
|
|
175
|
+
|
|
176
|
+
Parameters:
|
|
177
|
+
ctx: Click context object for the current invocation.
|
|
178
|
+
traceback: Whether to show full Python tracebacks on errors.
|
|
179
|
+
|
|
180
|
+
Side Effects:
|
|
181
|
+
Initializes ctx.obj as CliContextState if not already set and updates
|
|
182
|
+
the traceback field.
|
|
183
|
+
"""
|
|
184
|
+
ctx.ensure_object(CliContextState)
|
|
185
|
+
# Type narrowing: ctx.obj is guaranteed to be CliContextState after ensure_object
|
|
186
|
+
state = ctx.obj
|
|
187
|
+
if isinstance(state, CliContextState): # nosec B101 - type guard, not assertion
|
|
188
|
+
state.traceback = traceback
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@cli.command("info", context_settings=CLICK_CONTEXT_SETTINGS)
|
|
192
|
+
def cli_info() -> None:
|
|
193
|
+
"""Display package metadata exported by :mod:`lib_cli_exit_tools.__init__conf__`.
|
|
194
|
+
|
|
195
|
+
Why:
|
|
196
|
+
Offer a zero-dependency way for users to confirm the installed version
|
|
197
|
+
and provenance of the CLI.
|
|
198
|
+
Side Effects:
|
|
199
|
+
Writes formatted metadata to stdout.
|
|
200
|
+
Examples:
|
|
201
|
+
>>> from click.testing import CliRunner
|
|
202
|
+
>>> runner = CliRunner()
|
|
203
|
+
>>> result = runner.invoke(cli, ["info"])
|
|
204
|
+
>>> "Info for" in result.output
|
|
205
|
+
True
|
|
206
|
+
"""
|
|
207
|
+
__init__conf__.print_info()
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@cli.command("fail", context_settings=CLICK_CONTEXT_SETTINGS)
|
|
211
|
+
def cli_fail() -> None:
|
|
212
|
+
"""Intentionally raise a failure to validate error handling.
|
|
213
|
+
|
|
214
|
+
Why:
|
|
215
|
+
Exercise the error-reporting path so engineers can confirm exit-code
|
|
216
|
+
translation and optional traceback behaviour without crafting custom
|
|
217
|
+
failing commands.
|
|
218
|
+
Side Effects:
|
|
219
|
+
Delegates to :func:`lib_cli_exit_tools.i_should_fail`, which raises
|
|
220
|
+
``RuntimeError`` for the caller to handle.
|
|
221
|
+
Examples:
|
|
222
|
+
>>> from click.testing import CliRunner
|
|
223
|
+
>>> runner = CliRunner()
|
|
224
|
+
>>> result = runner.invoke(cli, ["fail"])
|
|
225
|
+
>>> result.exit_code != 0
|
|
226
|
+
True
|
|
227
|
+
>>> "i should fail" in result.output or "i should fail" in result.stderr
|
|
228
|
+
True
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
lib_cli_exit_tools.i_should_fail()
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
235
|
+
"""Run the CLI with :func:`lib_cli_exit_tools.run_cli` wiring.
|
|
236
|
+
|
|
237
|
+
Why:
|
|
238
|
+
Serve as the target for console scripts and ``python -m`` execution by
|
|
239
|
+
returning an integer exit code instead of exiting directly.
|
|
240
|
+
Parameters:
|
|
241
|
+
argv: Optional iterable of arguments passed to Click (without program name).
|
|
242
|
+
Returns:
|
|
243
|
+
Integer exit code from :func:`lib_cli_exit_tools.run_cli`.
|
|
244
|
+
Side Effects:
|
|
245
|
+
Delegates to Click and may write to stdout/stderr.
|
|
246
|
+
Examples:
|
|
247
|
+
>>> import contextlib, io
|
|
248
|
+
>>> buffer = io.StringIO()
|
|
249
|
+
>>> with contextlib.redirect_stdout(buffer):
|
|
250
|
+
... exit_code = main(["info"])
|
|
251
|
+
>>> exit_code
|
|
252
|
+
0
|
|
253
|
+
>>> "Info for" in buffer.getvalue()
|
|
254
|
+
True
|
|
255
|
+
"""
|
|
256
|
+
with _temporary_rich_click_configuration():
|
|
257
|
+
return lib_cli_exit_tools.run_cli(
|
|
258
|
+
cli,
|
|
259
|
+
argv=list(argv) if argv is not None else None,
|
|
260
|
+
prog_name=__init__conf__.shell_command,
|
|
261
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Core domain logic for lib_cli_exit_tools.
|
|
2
|
+
|
|
3
|
+
Purpose:
|
|
4
|
+
Host configuration dataclasses, exit-code mapping helpers, and other
|
|
5
|
+
framework-agnostic primitives consumed by higher layers.
|
|
6
|
+
Contents:
|
|
7
|
+
Modules extracted from the legacy monolith will expose configuration
|
|
8
|
+
objects and pure helpers for exit handling.
|
|
9
|
+
System Integration:
|
|
10
|
+
Intended to be imported by application orchestration and adapters without
|
|
11
|
+
depending on Click or other I/O frameworks.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
__all__: list[str] = []
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Runtime configuration primitives for lib_cli_exit_tools.
|
|
2
|
+
|
|
3
|
+
Purpose:
|
|
4
|
+
Expose the mutable configuration dataclass shared across the package so
|
|
5
|
+
adapters can toggle behaviour (tracebacks, exit codes, broken-pipe
|
|
6
|
+
semantics) without re-implementing global state.
|
|
7
|
+
Contents:
|
|
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
|
+
configuration state for embedders and tests.
|
|
12
|
+
* :func:`reset_config` – helper that restores defaults defined by
|
|
13
|
+
:class:`_Config`.
|
|
14
|
+
System Integration:
|
|
15
|
+
Imported by higher layers (`application.runner`, `adapters.click_adapter`)
|
|
16
|
+
to align behaviour while keeping the configuration schema centralized. The
|
|
17
|
+
additional helpers remove the need for ad-hoc fixtures when temporarily
|
|
18
|
+
tweaking settings in multi-layer integrations.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from collections.abc import Iterator, Mapping
|
|
24
|
+
from contextlib import contextmanager
|
|
25
|
+
from dataclasses import dataclass, fields
|
|
26
|
+
from typing import Literal, TypedDict
|
|
27
|
+
|
|
28
|
+
__all__ = ["_Config", "config", "config_overrides", "reset_config"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ConfigSnapshot(TypedDict):
|
|
32
|
+
"""Type-safe snapshot of configuration values.
|
|
33
|
+
|
|
34
|
+
Why:
|
|
35
|
+
Provides compile-time safety when capturing and restoring configuration
|
|
36
|
+
state. Mirrors the _Config dataclass fields exactly to prevent typos
|
|
37
|
+
and ensure type consistency.
|
|
38
|
+
Fields:
|
|
39
|
+
traceback: Current traceback emission flag.
|
|
40
|
+
exit_code_style: Current exit code mapping strategy.
|
|
41
|
+
broken_pipe_exit_code: Current broken pipe exit code.
|
|
42
|
+
traceback_force_color: Current traceback color forcing flag.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
traceback: bool
|
|
46
|
+
exit_code_style: Literal["errno", "sysexits"]
|
|
47
|
+
broken_pipe_exit_code: int
|
|
48
|
+
traceback_force_color: bool
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(slots=True)
|
|
52
|
+
class _Config:
|
|
53
|
+
"""Centralized runtime flags shared across all CLI executions.
|
|
54
|
+
|
|
55
|
+
Why:
|
|
56
|
+
Prevent each CLI adapter from re-implementing toggles for traceback
|
|
57
|
+
emission and exit-code semantics. The Click adapter mutates these fields
|
|
58
|
+
once per process based on global command options.
|
|
59
|
+
What:
|
|
60
|
+
Stores the behavioural switches consulted by error printers and
|
|
61
|
+
exit-code helpers. Values are mutated in place so that successive calls
|
|
62
|
+
reuse the same configuration.
|
|
63
|
+
Fields:
|
|
64
|
+
traceback: Enables stack-trace passthrough when ``True`` to aid
|
|
65
|
+
debugging without altering default UX for end users.
|
|
66
|
+
exit_code_style: Selects the exit-code mapping strategy, allowing
|
|
67
|
+
consumers to opt into BSD ``sysexits`` values when shell scripts rely
|
|
68
|
+
on them.
|
|
69
|
+
broken_pipe_exit_code: Exit code returned when a ``BrokenPipeError``
|
|
70
|
+
occurs; defaults to ``141`` so pipelines can detect truncated
|
|
71
|
+
output.
|
|
72
|
+
traceback_force_color: Force Rich to emit ANSI-coloured tracebacks even
|
|
73
|
+
when stdout/stderr are not detected as TTYs.
|
|
74
|
+
Side Effects:
|
|
75
|
+
Mutations are process wide because :data:`config` exports a module-level
|
|
76
|
+
instance. Callers should restore values in tests to avoid leakage.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
traceback: bool = False
|
|
80
|
+
exit_code_style: Literal["errno", "sysexits"] = "errno"
|
|
81
|
+
broken_pipe_exit_code: int = 141
|
|
82
|
+
traceback_force_color: bool = False
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
#: Shared configuration singleton consulted by CLI orchestration helpers.
|
|
86
|
+
config = _Config()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _field_names() -> tuple[str, ...]:
|
|
90
|
+
"""Return the ordered configuration field names."""
|
|
91
|
+
|
|
92
|
+
return tuple(field.name for field in fields(_Config))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _default_values() -> ConfigSnapshot:
|
|
96
|
+
"""Return the canonical configuration defaults as a type-safe snapshot."""
|
|
97
|
+
|
|
98
|
+
defaults = _Config()
|
|
99
|
+
return ConfigSnapshot(
|
|
100
|
+
traceback=defaults.traceback,
|
|
101
|
+
exit_code_style=defaults.exit_code_style,
|
|
102
|
+
broken_pipe_exit_code=defaults.broken_pipe_exit_code,
|
|
103
|
+
traceback_force_color=defaults.traceback_force_color,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _snapshot_current_settings() -> ConfigSnapshot:
|
|
108
|
+
"""Capture the current configuration values as a type-safe snapshot."""
|
|
109
|
+
|
|
110
|
+
return ConfigSnapshot(
|
|
111
|
+
traceback=config.traceback,
|
|
112
|
+
exit_code_style=config.exit_code_style,
|
|
113
|
+
broken_pipe_exit_code=config.broken_pipe_exit_code,
|
|
114
|
+
traceback_force_color=config.traceback_force_color,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _restore_settings(snapshot: ConfigSnapshot) -> None:
|
|
119
|
+
"""Bring :data:`config` back to the provided type-safe snapshot."""
|
|
120
|
+
|
|
121
|
+
config.traceback = snapshot["traceback"]
|
|
122
|
+
config.exit_code_style = snapshot["exit_code_style"]
|
|
123
|
+
config.broken_pipe_exit_code = snapshot["broken_pipe_exit_code"]
|
|
124
|
+
config.traceback_force_color = snapshot["traceback_force_color"]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _reject_unknown_fields(overrides: Mapping[str, object]) -> None:
|
|
128
|
+
"""Guard against typos in override names."""
|
|
129
|
+
|
|
130
|
+
known = set(_field_names())
|
|
131
|
+
unknown = set(overrides) - known
|
|
132
|
+
if unknown:
|
|
133
|
+
raise AttributeError(f"Unknown configuration fields: {sorted(unknown)}")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def reset_config() -> None:
|
|
137
|
+
"""Restore the shared configuration to its default values."""
|
|
138
|
+
|
|
139
|
+
_restore_settings(_default_values())
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@contextmanager
|
|
143
|
+
def config_overrides(**overrides: object) -> Iterator[_Config]:
|
|
144
|
+
"""Snapshot configuration state and optionally apply temporary overrides."""
|
|
145
|
+
|
|
146
|
+
_reject_unknown_fields(overrides)
|
|
147
|
+
snapshot = _snapshot_current_settings()
|
|
148
|
+
|
|
149
|
+
for name, value in overrides.items():
|
|
150
|
+
setattr(config, name, value)
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
yield config
|
|
154
|
+
finally:
|
|
155
|
+
_restore_settings(snapshot)
|