diff --git a/.changelog/017.yaml b/.changelog/017.yaml new file mode 100644 index 0000000..f4df421 --- /dev/null +++ b/.changelog/017.yaml @@ -0,0 +1,15 @@ +name: disable_interactive_shell +ts: 2026-05-22 13:13:32.449577+00:00 +type: make_public +details: created in _internal/_run_env.py +full_path: _internal._run_env.disable_interactive_shell +group: console +--- +name: AskShellSettings +ts: 2026-05-22 13:13:32.918803+00:00 +type: additional_change +auto_generated: true +change_kind: optional_field_added +details: 'added optional field ''disable_interactive_shell'' (default: False)' +field_name: disable_interactive_shell +group: __ROOT__ diff --git a/.groups.yaml b/.groups.yaml index ba4baaa..0e4cb6c 100644 --- a/.groups.yaml +++ b/.groups.yaml @@ -33,6 +33,7 @@ groups: - _internal.rich_progress - _internal.typer_command owned_refs: + - _internal._run_env.disable_interactive_shell - _internal._run_env.interactive_shell - _internal.rich_live.RemoveLivePart - _internal.rich_live.add_renderable diff --git a/ask_shell/_internal/_run_env.py b/ask_shell/_internal/_run_env.py index 2caafca..5909656 100644 --- a/ask_shell/_internal/_run_env.py +++ b/ask_shell/_internal/_run_env.py @@ -1,4 +1,5 @@ import logging +import os import sys from functools import lru_cache from os import getenv @@ -26,7 +27,13 @@ def _not_interactive_reason() -> str: @lru_cache def interactive_shell() -> bool: - if AskShellSettings.from_env().force_interactive_shell: + settings = AskShellSettings.from_env() + if settings.disable_interactive_shell: + logger.debug( + f"Interactive shell disabled by environment variable {settings.ENV_NAME_DISABLE_INTERACTIVE_SHELL}" + ) + return False + if settings.force_interactive_shell: logger.debug( f"Interactive shell forced by environment variable {_global_settings.ENV_NAME_FORCE_INTERACTIVE_SHELL}" ) @@ -37,6 +44,12 @@ def interactive_shell() -> bool: return True +def disable_interactive_shell() -> None: + """Force non-interactive mode for the remainder of this process.""" + os.environ[AskShellSettings.ENV_NAME_DISABLE_INTERACTIVE_SHELL] = "true" + interactive_shell.cache_clear() + + def resolve_terminal_dimensions( settings: AskShellSettings | None = None, ) -> tuple[int | None, int | None]: diff --git a/ask_shell/_internal/_run_env_test.py b/ask_shell/_internal/_run_env_test.py index 48e6d7d..406ff13 100644 --- a/ask_shell/_internal/_run_env_test.py +++ b/ask_shell/_internal/_run_env_test.py @@ -8,12 +8,26 @@ @pytest.fixture(autouse=True) -def _clear_interactive_cache(): +def _clear_interactive_cache(monkeypatch: pytest.MonkeyPatch): + monkeypatch.delenv(AskShellSettings.ENV_NAME_DISABLE_INTERACTIVE_SHELL, raising=False) + monkeypatch.delenv(AskShellSettings.ENV_NAME_FORCE_INTERACTIVE_SHELL, raising=False) interactive_shell.cache_clear() yield + monkeypatch.delenv(AskShellSettings.ENV_NAME_DISABLE_INTERACTIVE_SHELL, raising=False) + monkeypatch.delenv(AskShellSettings.ENV_NAME_FORCE_INTERACTIVE_SHELL, raising=False) interactive_shell.cache_clear() +def test_disable_interactive_shell_forces_non_interactive(monkeypatch: pytest.MonkeyPatch): + monkeypatch.setenv(AskShellSettings.ENV_NAME_FORCE_INTERACTIVE_SHELL, "true") + interactive_shell.cache_clear() + assert interactive_shell() + + monkeypatch.setenv(AskShellSettings.ENV_NAME_DISABLE_INTERACTIVE_SHELL, "true") + interactive_shell.cache_clear() + assert not interactive_shell() + + def test_resolve_terminal_dimensions_interactive(): with patch.object(_run_env, "interactive_shell", return_value=True): assert resolve_terminal_dimensions() == (None, None) diff --git a/ask_shell/console.py b/ask_shell/console.py index ebea383..3d78fb8 100644 --- a/ask_shell/console.py +++ b/ask_shell/console.py @@ -1,4 +1,5 @@ # Generated by pkg-ext +from ask_shell._internal._run_env import disable_interactive_shell as _disable_interactive_shell from ask_shell._internal._run_env import interactive_shell as _interactive_shell from ask_shell._internal.rich_live import RemoveLivePart as _RemoveLivePart from ask_shell._internal.rich_live import add_renderable as _add_renderable @@ -8,6 +9,7 @@ from ask_shell._internal.rich_progress import new_task as _new_task from ask_shell._internal.typer_command import configure_logging as _configure_logging +disable_interactive_shell = _disable_interactive_shell interactive_shell = _interactive_shell RemoveLivePart = _RemoveLivePart add_renderable = _add_renderable diff --git a/ask_shell/settings.py b/ask_shell/settings.py index effab31..7467d7b 100644 --- a/ask_shell/settings.py +++ b/ask_shell/settings.py @@ -106,6 +106,12 @@ class AskShellSettings(StaticSettings): alias=ENV_NAME_FORCE_INTERACTIVE_SHELL, description="Useful for testing", ) + ENV_NAME_DISABLE_INTERACTIVE_SHELL: ClassVar[str] = f"{ENV_PREFIX}DISABLE_INTERACTIVE_SHELL" + disable_interactive_shell: bool = Field( + default=False, + alias=ENV_NAME_DISABLE_INTERACTIVE_SHELL, + description="Force non-interactive mode for long-running servers and automation", + ) ENV_NAME_THREAD_COUNT: ClassVar[str] = f"{ENV_PREFIX}THREAD_COUNT" thread_count: int = Field( default=100, diff --git a/docs/_root/askshellsettings.md b/docs/_root/askshellsettings.md index ab23e66..54acf81 100644 --- a/docs/_root/askshellsettings.md +++ b/docs/_root/askshellsettings.md @@ -12,6 +12,7 @@ class AskShellSettings(StaticSettings): SKIP_APP_NAME: bool = False log_level: Literal[DEBUG, INFO, WARNING, ERROR, CRITICAL, UNSET] = 'UNSET' force_interactive_shell: bool = False + disable_interactive_shell: bool = False thread_count: int = 100 thread_pool_full_wait_time_seconds: float = 5 search_enabled_after_choices: int = 7 @@ -72,6 +73,7 @@ class AskShellSettings(StaticSettings): | Version | Change | |---------|--------| +| unreleased | added optional field 'disable_interactive_shell' (default: False) | | 0.8.0 | added optional field 'terminal_width' (default: 120) | | 0.8.0 | added optional field 'terminal_height' (default: 40) | | 0.7.0 | added optional field 'shell_run_summary' (default: ) | diff --git a/docs/console/index.md b/docs/console/index.md index d76d197..8872417 100644 --- a/docs/console/index.md +++ b/docs/console/index.md @@ -7,6 +7,7 @@ - [`RemoveLivePart`](#removelivepart_def) - [`add_renderable`](#add_renderable_def) - [`configure_logging`](#configure_logging_def) +- [`disable_interactive_shell`](#disable_interactive_shell_def) - [`get_live_console`](#get_live_console_def) - [`interactive_shell`](#interactive_shell_def) - [`log_to_live`](#log_to_live_def) @@ -95,7 +96,7 @@ def get_live_console() -> Console: ### function: `interactive_shell` -- [source](../../ask_shell/_internal/_run_env.py#L27) +- [source](../../ask_shell/_internal/_run_env.py#L28) > **Since:** 0.3.0 ```python @@ -180,4 +181,24 @@ def print_to_live(*objects, sep: str = ' ', end: str = '\n', style: str | Style | Version | Change | |---------|--------| | 0.3.0 | Made public | - \ No newline at end of file + + + + +### function: `disable_interactive_shell` +- [source](../../ask_shell/_internal/_run_env.py#L47) +> **Since:** unreleased + +```python +def disable_interactive_shell() -> None: + ... +``` + +Force non-interactive mode for the remainder of this process. + +### Changes + +| Version | Change | +|---------|--------| +| unreleased | Made public | + \ No newline at end of file