diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa560265..88366bcb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-22.04, ubuntu-latest, macos-14, macos-latest] - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.15"] steps: - uses: actions/checkout@v7 diff --git a/argcomplete/completers.py b/argcomplete/completers.py index 458de776..e79a477c 100644 --- a/argcomplete/completers.py +++ b/argcomplete/completers.py @@ -6,7 +6,7 @@ import subprocess from collections.abc import Callable, Generator, Iterable, Mapping from shlex import quote -from typing import Final +from typing import Final, Union _Ignored = object @@ -32,9 +32,9 @@ def __call__( class ChoicesCompleter(BaseCompleter): - choices: Final[Mapping[str, str | bytes]] + choices: Final[Mapping[str, Union[str, bytes]]] - def __init__(self, choices: Mapping[str, str | bytes]) -> None: + def __init__(self, choices: Mapping[str, Union[str, bytes]]) -> None: self.choices = choices def _convert(self, choice): @@ -57,7 +57,7 @@ class FilesCompleter(BaseCompleter): allowednames: Final[list[str]] directories: Final[bool] - def __init__(self, allowednames: Iterable[str] | str = (), directories: bool = True) -> None: + def __init__(self, allowednames: Union[Iterable[str], str] = (), directories: bool = True) -> None: # Fix if someone passes in a string instead of a list if isinstance(allowednames, (str, bytes)): allowednames = [allowednames] diff --git a/argcomplete/finders.py b/argcomplete/finders.py index 6a28403e..3afa4d0d 100644 --- a/argcomplete/finders.py +++ b/argcomplete/finders.py @@ -7,7 +7,7 @@ import os import sys from collections.abc import Container, Mapping -from typing import Callable, Literal, TextIO +from typing import Callable, Literal, Optional, TextIO, Union from . import io as _io from .completers import BaseCompleter, ChoicesCompleter, FilesCompleter, SuppressCompleter @@ -37,10 +37,10 @@ class CompletionFinder: :meth:`CompletionFinder.__call__()`. """ - _parser: argparse.ArgumentParser | None - _formatter: argparse.HelpFormatter | None - always_complete_options: bool | Literal["long", "short"] - exclude: Container[str] | None + _parser: Optional[argparse.ArgumentParser] + _formatter: Optional[argparse.HelpFormatter] + always_complete_options: Union[bool, Literal["long", "short"]] + exclude: Optional[Container[str]] validator: Callable[[str, str], bool] print_suppressed: bool completing: bool @@ -53,13 +53,13 @@ class CompletionFinder: def __init__( self, - argument_parser: argparse.ArgumentParser | None = None, - always_complete_options: bool | Literal["long", "short"] = True, - exclude: Container[str] | None = None, - validator: Callable[[str, str], bool] | None = None, + argument_parser: Optional[argparse.ArgumentParser] = None, + always_complete_options: Union[bool, Literal["long", "short"]] = True, + exclude: Optional[Container[str]] = None, + validator: Optional[Callable[[str, str], bool]] = None, print_suppressed: bool = False, default_completer: BaseCompleter = FilesCompleter(), - append_space: bool | None = None, + append_space: Optional[bool] = None, ) -> None: self._parser = argument_parser # type: ignore[assignment] self._formatter = None @@ -79,13 +79,13 @@ def __init__( def __call__( self, argument_parser: argparse.ArgumentParser, - always_complete_options: bool | str = True, + always_complete_options: Union[bool, str] = True, exit_method: Callable = os._exit, - output_stream: TextIO | None = None, - exclude: Container[str] | None = None, - validator: Callable[[str, str], bool] | None = None, + output_stream: Optional[TextIO] = None, + exclude: Optional[Container[str]] = None, + validator: Optional[Callable[[str, str], bool]] = None, print_suppressed: bool = False, - append_space: bool | None = None, + append_space: Optional[bool] = None, default_completer: BaseCompleter = FilesCompleter(), ) -> None: """ @@ -523,7 +523,7 @@ def filter_completions(self, completions: list[str]) -> list[str]: return filtered_completions def quote_completions( - self, completions: list[str], cword_prequote: str, last_wordbreak_pos: int | None + self, completions: list[str], cword_prequote: str, last_wordbreak_pos: Optional[int] ) -> list[str]: """ If the word under the cursor started with a quote (as indicated by a nonempty ``cword_prequote``), escapes @@ -588,7 +588,7 @@ def quote_completions( return escaped_completions - def rl_complete(self, text: str, state: int) -> str | None: + def rl_complete(self, text: str, state: int) -> Optional[str]: """ Alternate entry point for using the argcomplete completer in a readline-based REPL. See also `rlcompleter `_. diff --git a/argcomplete/lexers.py b/argcomplete/lexers.py index 82907c81..cf1125b5 100644 --- a/argcomplete/lexers.py +++ b/argcomplete/lexers.py @@ -1,11 +1,12 @@ import os +from typing import Optional from .exceptions import ArgcompleteException from .io import debug from .packages import _shlex -def split_line(line: str, point: int | None = None) -> tuple[str, str, str, list[str], int | None]: +def split_line(line: str, point: Optional[int] = None) -> tuple[str, str, str, list[str], Optional[int]]: if point is None: point = len(line) line = line[:point] @@ -14,7 +15,7 @@ def split_line(line: str, point: int | None = None) -> tuple[str, str, str, list lexer.wordbreaks = os.environ.get("_ARGCOMPLETE_COMP_WORDBREAKS", "") words = [] - def split_word(word: str) -> tuple[str, str, str, list[str], int | None]: + def split_word(word: str) -> tuple[str, str, str, list[str], Optional[int]]: # TODO: make this less ugly point_in_word = len(word) + point - lexer.instream.tell() if isinstance(lexer.state, (str, bytes)) and lexer.state in lexer.whitespace: diff --git a/argcomplete/scripts/activate_global_python_argcomplete.py b/argcomplete/scripts/activate_global_python_argcomplete.py index 3f14477e..ccd5dbd0 100755 --- a/argcomplete/scripts/activate_global_python_argcomplete.py +++ b/argcomplete/scripts/activate_global_python_argcomplete.py @@ -14,6 +14,7 @@ import site import subprocess import sys +from typing import Optional, Union import argcomplete @@ -108,7 +109,7 @@ def get_consent() -> bool: return False -def append_to_config_file(path: str | os.PathLike[str], shellcode: str) -> None: +def append_to_config_file(path: Union[str, os.PathLike[str]], shellcode: str) -> None: if os.path.exists(path): with open(path, 'r') as fh: if shellcode in fh.read(): @@ -126,7 +127,7 @@ def append_to_config_file(path: str | os.PathLike[str], shellcode: str) -> None: print("Added.", file=sys.stderr) -def link_zsh_user_rcfile(zsh_fpath: str | None = None) -> None: +def link_zsh_user_rcfile(zsh_fpath: Optional[str] = None) -> None: zsh_rcfile = os.path.join(os.path.expanduser(os.environ.get("ZDOTDIR", "~")), ".zshenv") append_to_config_file(zsh_rcfile, zsh_shellcode.format(zsh_fpath=zsh_fpath or get_activator_dir())) diff --git a/argcomplete/shell_integration.py b/argcomplete/shell_integration.py index 94b88eb0..3edde4a4 100644 --- a/argcomplete/shell_integration.py +++ b/argcomplete/shell_integration.py @@ -5,6 +5,7 @@ from collections.abc import Iterable from shlex import quote +from typing import Optional bashcode = r"""#compdef %(executables)s # Run something, muting output or redirecting it to the debug stream @@ -140,8 +141,8 @@ def shellcode( executables: Iterable[str], use_defaults: bool = True, shell: str = "bash", - complete_arguments: Iterable[str] | None = None, - argcomplete_script: str | None = None, + complete_arguments: Optional[Iterable[str]] = None, + argcomplete_script: Optional[str] = None, ) -> str: """ Provide the shell code required to register a python executable for use with the argcomplete module.