Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,10 @@ known-first-party = ["reigns", "towns", "lands", "items", "users"]
keep-runtime-typing = true

[tool.ruff.lint.flake8-tidy-imports]
# Import rich_utils from within functions (lazy), not at the module level (TID253)
banned-module-level-imports = ["typer.rich_utils"]
# Import rich_utils and shellingham from within functions (lazy),
# not at the module level (TID253)
banned-module-level-imports = ["typer.rich_utils", "shellingham"]

[tool.ruff.lint.flake8-tidy-imports.banned-api]
"rich".msg = "Use 'typer.rich_utils' instead of importing from 'rich' directly."
"shellingham.detect_shell".msg = "Use 'typer._completion_shared._get_shell_name' instead of using 'shellingham.detect_shell' directly."
3 changes: 2 additions & 1 deletion tests/test_completion/test_completion_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from pathlib import Path
from unittest import mock

import shellingham
import typer
from typer.testing import CliRunner

Expand Down Expand Up @@ -141,6 +140,8 @@ def test_completion_install_fish():

@requires_completion_permission
def test_completion_install_powershell():
import shellingham

completion_path: Path = (
Path.home() / ".config/powershell/Microsoft.PowerShell_profile.ps1"
)
Expand Down
3 changes: 2 additions & 1 deletion tests/test_completion/test_completion_show.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import sys
from unittest import mock

import shellingham
import typer
from typer.testing import CliRunner

Expand Down Expand Up @@ -142,6 +141,8 @@ def test_completion_source_pwsh():


def test_completion_show_invalid_shell():
import shellingham

with mock.patch.object(
shellingham, "detect_shell", return_value=("xshell", "/usr/bin/xshell")
):
Expand Down
3 changes: 2 additions & 1 deletion tests/test_others.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import click
import pytest
import shellingham
import typer
import typer.completion
from typer.core import _split_opt
Expand Down Expand Up @@ -78,6 +77,8 @@ def convert(

@requires_completion_permission
def test_install_invalid_shell():
import shellingham

app = typer.Typer()

@app.command()
Expand Down
16 changes: 3 additions & 13 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,7 @@
from os import getenv

import pytest

try:
import shellingham
from shellingham import ShellDetectionFailure

shell = shellingham.detect_shell()[0]
except ImportError: # pragma: no cover
shellingham = None
shell = None
except ShellDetectionFailure: # pragma: no cover
shell = None

from typer._completion_shared import _get_shell_name

needs_py310 = pytest.mark.skipif(
sys.version_info < (3, 10), reason="requires python3.10+"
Expand All @@ -23,8 +12,9 @@
not sys.platform.startswith("linux"), reason="Test requires Linux"
)

shell = _get_shell_name()
needs_bash = pytest.mark.skipif(
not shellingham or not shell or "bash" not in shell, reason="Test requires Bash"
shell is None or "bash" not in shell, reason="Test requires Bash"
)

requires_completion_permission = pytest.mark.skipif(
Expand Down
5 changes: 0 additions & 5 deletions typer/_completion_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@
split_arg_string as click_split_arg_string,
)

try:
import shellingham
except ImportError: # pragma: no cover
shellingham = None


def _sanitize_help_text(text: str) -> str:
"""Sanitizes the help text by removing rich tags"""
Expand Down
34 changes: 26 additions & 8 deletions typer/_completion_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,10 @@
import subprocess
from enum import Enum
from pathlib import Path
from typing import Optional, Tuple
from typing import Optional, Tuple, Union

import click

try:
import shellingham
except ImportError: # pragma: no cover
shellingham = None
from typer.core import HAS_SHELLINGHAM


class Shells(str, Enum):
Expand Down Expand Up @@ -213,8 +209,8 @@ def install(
if complete_var is None:
complete_var = "_{}_COMPLETE".format(prog_name.replace("-", "_").upper())
test_disable_detection = os.getenv("_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION")
if shell is None and shellingham is not None and not test_disable_detection:
shell, _ = shellingham.detect_shell()
if shell is None and not test_disable_detection:
shell = _get_shell_name()
if shell == "bash":
installed_path = install_bash(
prog_name=prog_name, complete_var=complete_var, shell=shell
Expand All @@ -238,3 +234,25 @@ def install(
else:
click.echo(f"Shell {shell} is not supported.")
raise click.exceptions.Exit(1)


def _get_shell_name() -> Union[str, None]:
"""Get the current shell name, if available.

The name will always be lowercase. If the shell cannot be detected, None is
returned.
"""
name: Union[str, None] # N.B. shellingham is untyped
if HAS_SHELLINGHAM:
import shellingham

try:
# N.B. detect_shell returns a tuple of (shell name, shell command).
# We only need the name.
name, _cmd = shellingham.detect_shell() # noqa: TID251
except shellingham.ShellDetectionFailure: # pragma: no cover
name = None
else:
name = None # pragma: no cover

return name
17 changes: 7 additions & 10 deletions typer/completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,19 @@
import click

from ._completion_classes import completion_init
from ._completion_shared import Shells, get_completion_script, install
from ._completion_shared import Shells, _get_shell_name, get_completion_script, install
from .core import HAS_SHELLINGHAM
from .models import ParamMeta
from .params import Option
from .utils import get_params_from_function

try:
import shellingham
except ImportError: # pragma: no cover
shellingham = None


_click_patched = False


def get_completion_inspect_parameters() -> Tuple[ParamMeta, ParamMeta]:
completion_init()
test_disable_detection = os.getenv("_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION")
if shellingham and not test_disable_detection:
if HAS_SHELLINGHAM and not test_disable_detection:
parameters = get_params_from_function(_install_completion_placeholder_function)
else:
parameters = get_params_from_function(
Expand Down Expand Up @@ -54,8 +49,10 @@ def show_callback(ctx: click.Context, param: click.Parameter, value: Any) -> Any
test_disable_detection = os.getenv("_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION")
if isinstance(value, str):
shell = value
elif shellingham and not test_disable_detection:
shell, _ = shellingham.detect_shell()
elif not test_disable_detection:
detected_shell = _get_shell_name()
if detected_shell is not None:
shell = detected_shell
script_content = get_completion_script(
prog_name=prog_name, complete_var=complete_var, shell=shell
)
Expand Down
1 change: 1 addition & 0 deletions typer/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
MarkupMode = Literal["markdown", "rich", None]

HAS_RICH = importlib.util.find_spec("rich") is not None
HAS_SHELLINGHAM = importlib.util.find_spec("shellingham") is not None

if HAS_RICH:
DEFAULT_MARKUP_MODE: MarkupMode = "rich"
Expand Down
Loading