Skip to content

Type hint improvements #423

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
4 changes: 2 additions & 2 deletions argcomplete/_check_console_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
try:
from importlib.metadata import entry_points as importlib_entry_points
except ImportError:
from importlib_metadata import entry_points as importlib_entry_points # type:ignore
from importlib_metadata import entry_points as importlib_entry_points # type: ignore[import,no-redef]

from ._check_module import ArgcompleteMarkerNotFound, find


def main():
def main() -> None:
# Argument is the full path to the console script.
script_path = sys.argv[1]

Expand Down
6 changes: 3 additions & 3 deletions argcomplete/_check_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
import tokenize

try:
from importlib.util import find_spec # type:ignore
from importlib.util import find_spec # type: ignore
except ImportError:
import typing as t
from collections import namedtuple
from imp import find_module

ModuleSpec = namedtuple("ModuleSpec", ["origin", "has_location", "submodule_search_locations"])

def find_spec( # type:ignore
def find_spec( # type: ignore[misc]
name: str,
package: t.Optional[str] = None,
) -> t.Optional[ModuleSpec]:
Expand Down Expand Up @@ -62,7 +62,7 @@ def find(name, return_package=False):
return path + ".py"


def main():
def main() -> None:
try:
name = sys.argv[1]
except IndexError:
Expand Down
31 changes: 16 additions & 15 deletions argcomplete/completers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import argparse
import os
import subprocess
from collections.abc import Iterable
from typing import Any, Callable, List, Union


def _call(*args, **kwargs):
def _call(*args, **kwargs) -> List[str]:
# TODO: replace "universal_newlines" with "text" once 3.6 support is dropped
kwargs["universal_newlines"] = True
try:
Expand All @@ -22,20 +24,20 @@ class BaseCompleter:

def __call__(
self, *, prefix: str, action: argparse.Action, parser: argparse.ArgumentParser, parsed_args: argparse.Namespace
):
) -> Iterable[str]:
raise NotImplementedError("This method should be implemented by a subclass.")


class ChoicesCompleter(BaseCompleter):
def __init__(self, choices):
def __init__(self, choices: Iterable[Any]):
self.choices = choices

def _convert(self, choice):
def _convert(self, choice: Any) -> str:
if not isinstance(choice, str):
choice = str(choice)
return str(choice)
return choice

def __call__(self, **kwargs):
def __call__(self, **kwargs) -> Iterable[str]:
return (self._convert(c) for c in self.choices)


Expand All @@ -47,15 +49,15 @@ class FilesCompleter(BaseCompleter):
File completer class, optionally takes a list of allowed extensions
"""

def __init__(self, allowednames=(), directories=True):
def __init__(self, allowednames: Union[str, Iterable[str]] = (), directories: bool = True):
# Fix if someone passes in a string instead of a list
if isinstance(allowednames, (str, bytes)):
if isinstance(allowednames, str):
allowednames = [allowednames]

self.allowednames = [x.lstrip("*").lstrip(".") for x in allowednames]
self.directories = directories

def __call__(self, prefix, **kwargs):
def __call__(self, *, prefix: str, **kwargs) -> Iterable[str]:
completion = []
if self.allowednames:
if self.directories:
Expand All @@ -74,17 +76,16 @@ def __call__(self, prefix, **kwargs):


class _FilteredFilesCompleter(BaseCompleter):
def __init__(self, predicate):
def __init__(self, predicate: Callable[[str], bool]):
"""
Create the completer

A predicate accepts as its only argument a candidate path and either
accepts it or rejects it.
"""
assert predicate, "Expected a callable predicate"
self.predicate = predicate

def __call__(self, prefix, **kwargs):
def __call__(self, *, prefix: str, **kwargs) -> Iterable[str]:
"""
Provide completions on prefix
"""
Expand All @@ -105,7 +106,7 @@ def __call__(self, prefix, **kwargs):


class DirectoriesCompleter(_FilteredFilesCompleter):
def __init__(self):
def __init__(self) -> None:
_FilteredFilesCompleter.__init__(self, predicate=os.path.isdir)


Expand All @@ -114,10 +115,10 @@ class SuppressCompleter(BaseCompleter):
A completer used to suppress the completion of specific arguments
"""

def __init__(self):
def __init__(self) -> None:
pass

def suppress(self):
def suppress(self) -> bool:
"""
Decide if the completion should be suppressed
"""
Expand Down
32 changes: 16 additions & 16 deletions argcomplete/finders.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
import os
import sys
from collections.abc import Mapping
from typing import Callable, Dict, List, Optional, Sequence, Union
from typing import Any, Callable, Dict, List, Optional, Sequence, Union

from . import io as _io
from .completers import ChoicesCompleter, FilesCompleter, SuppressCompleter
from .completers import BaseCompleter, ChoicesCompleter, FilesCompleter, SuppressCompleter
from .io import debug, mute_stderr
from .lexers import split_line
from .packages._argparse import IntrospectiveArgumentParser, action_is_greedy, action_is_open, action_is_satisfied
Expand All @@ -26,7 +26,7 @@
}


def default_validator(completion, prefix):
def default_validator(completion: str, prefix: str) -> bool:
return completion.startswith(prefix)


Expand All @@ -40,12 +40,12 @@ class CompletionFinder(object):
def __init__(
self,
argument_parser=None,
always_complete_options=True,
exclude=None,
validator=None,
print_suppressed=False,
default_completer=FilesCompleter(),
append_space=None,
always_complete_options: Union[bool, str] = True,
exclude: Optional[Sequence[str]] = None,
validator: Optional[Callable[[str, str], bool]] = None,
print_suppressed: bool = False,
default_completer: BaseCompleter = FilesCompleter(),
append_space: Optional[bool] = None,
):
self._parser = argument_parser
self.always_complete_options = always_complete_options
Expand All @@ -65,14 +65,14 @@ def __call__(
self,
argument_parser: argparse.ArgumentParser,
always_complete_options: Union[bool, str] = True,
exit_method: Callable = os._exit,
exit_method: Callable[[int], Any] = os._exit,
output_stream=None,
exclude: Optional[Sequence[str]] = None,
validator: Optional[Callable] = None,
validator: Optional[Callable[[str, str], bool]] = None,
print_suppressed: bool = False,
append_space: Optional[bool] = None,
default_completer=FilesCompleter(),
):
default_completer: BaseCompleter = FilesCompleter(),
) -> None:
"""
:param argument_parser: The argument parser to autocomplete on
:param always_complete_options:
Expand Down Expand Up @@ -103,7 +103,7 @@ def __call__(
added to argcomplete.safe_actions, if their values are wanted in the ``parsed_args`` completer argument, or
their execution is otherwise desirable.
"""
self.__init__( # type: ignore
self.__init__( # type: ignore[misc]
argument_parser,
always_complete_options=always_complete_options,
exclude=exclude,
Expand Down Expand Up @@ -242,7 +242,7 @@ def patch(parser):
continue

# TODO: accomplish this with super
class IntrospectAction(action.__class__): # type: ignore
class IntrospectAction(action.__class__): # type: ignore[name-defined]
def __call__(self, parser, namespace, values, option_string=None):
debug("Action stub called on", self)
debug("\targs:", parser, namespace, values, option_string)
Expand Down Expand Up @@ -400,7 +400,7 @@ def _complete_active_option(self, parser, next_positional, cword_prefix, parsed_
else:
debug("Completer is not callable, trying the readline completer protocol instead")
for i in range(9999):
next_completion = completer.complete(cword_prefix, i) # type: ignore
next_completion = completer.complete(cword_prefix, i)
if next_completion is None:
break
if self.validator(next_completion, cword_prefix):
Expand Down
4 changes: 2 additions & 2 deletions argcomplete/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
debug_stream = sys.stderr


def debug(*args):
def debug(*args) -> None:
if _DEBUG:
print(file=debug_stream, *args)

Expand All @@ -33,7 +33,7 @@ def mute_stderr():
sys.stderr = stderr


def warn(*args):
def warn(*args) -> None:
"""
Prints **args** to standard error when running completions. This will interrupt the user's command line interaction;
use it to indicate an error condition that is preventing your completer from working.
Expand Down
13 changes: 8 additions & 5 deletions argcomplete/lexers.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import os
from typing import List, Optional, Tuple

from .exceptions import ArgcompleteException
from .io import debug
from .packages import _shlex


def split_line(line, point=None):
if point is None:
point = len(line)
_SplitResult = Tuple[str, str, str, List[str], Optional[int]]


def split_line(line: str, point: Optional[int] = None) -> _SplitResult:
point_ = len(line) if point is None else point
line = line[:point]
lexer = _shlex.shlex(line, posix=True)
lexer.whitespace_split = True
lexer.wordbreaks = os.environ.get("_ARGCOMPLETE_COMP_WORDBREAKS", "")
words = []

def split_word(word):
def split_word(word: str) -> _SplitResult:
# TODO: make this less ugly
point_in_word = len(word) + point - lexer.instream.tell()
point_in_word = len(word) + point_ - lexer.instream.tell()
if isinstance(lexer.state, (str, bytes)) and lexer.state in lexer.whitespace:
point_in_word += 1
if point_in_word > len(word):
Expand Down
14 changes: 11 additions & 3 deletions argcomplete/shell_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
# files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License.
# See https://github.com/kislyuk/argcomplete for more info.

from collections.abc import Iterable
from shlex import quote
from typing import Optional

bashcode = r"""
# Run something, muting output or redirecting it to the debug stream
Expand Down Expand Up @@ -118,16 +120,22 @@
shell_codes = {"bash": bashcode, "tcsh": tcshcode, "fish": fishcode, "powershell": powershell_code}


def shellcode(executables, use_defaults=True, shell="bash", complete_arguments=None, argcomplete_script=None):
def shellcode(
executables: Iterable[str],
use_defaults: bool = True,
shell: str = "bash",
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.

:param list(str) executables: Executables to be completed (when invoked exactly with this name)
:param Iterable(str) executables: Executables to be completed (when invoked exactly with this name)
:param bool use_defaults: Whether to fallback to readline's default completion when no matches are generated
(affects bash only)
:param str shell: Name of the shell to output code for
:param complete_arguments: Arguments to call complete with (affects bash only)
:type complete_arguments: list(str) or None
:type complete_arguments: Iterable(str) or None
:param argcomplete_script: Script to call complete with, if not the executable to complete.
If supplied, will be used to complete *all* passed executables.
:type argcomplete_script: str or None
Expand Down