diff --git a/invoke/collection.py b/invoke/collection.py index 23dcff928..20ef3576c 100644 --- a/invoke/collection.py +++ b/invoke/collection.py @@ -1,6 +1,6 @@ import copy from types import ModuleType -from typing import Any, Callable, Dict, List, Optional, Tuple +from typing import Any, Callable, Dict, List, Optional, Tuple, Union from .util import Lexicon, helpline @@ -284,7 +284,7 @@ def add_task( def add_collection( self, - coll: "Collection", + coll: Union["Collection", ModuleType], name: Optional[str] = None, default: Optional[bool] = None, ) -> None: diff --git a/invoke/context.py b/invoke/context.py index e9beaf4d1..21c5101e3 100644 --- a/invoke/context.py +++ b/invoke/context.py @@ -389,7 +389,7 @@ def cd(self, path: Union[PathLike, str]) -> Generator[None, None, None]: (such as the various ``Path`` objects out there), and not just string literals. """ - path = str(path) + path = os.fspath(path) self.command_cwds.append(path) try: yield diff --git a/invoke/parser/context.py b/invoke/parser/context.py index 359e9f9e2..1d44712c5 100644 --- a/invoke/parser/context.py +++ b/invoke/parser/context.py @@ -91,7 +91,7 @@ def __init__( self.args = Lexicon() self.positional_args: List[Argument] = [] self.flags = Lexicon() - self.inverse_flags: Dict[str, str] = {} # No need for Lexicon here + self.inverse_flags: Dict[str, Any] = {} # No need for Lexicon here self.name = name self.aliases = aliases for arg in args: diff --git a/invoke/runners.py b/invoke/runners.py index f1c888f44..5ac3cd52f 100644 --- a/invoke/runners.py +++ b/invoke/runners.py @@ -19,6 +19,7 @@ Optional, Tuple, Type, + overload, ) # Import some platform-specific things at top level so they can be mocked for @@ -122,6 +123,16 @@ def __init__(self, context: "Context") -> None: self._asynchronous = False self._disowned = False + @overload + def run(self, command: str, *, disowned: None, **kwargs: Any) -> "Result": + ... + + @overload + def run( + self, command: str, *, disowned: bool, **kwargs: Any + ) -> Optional["Result"]: + ... + def run(self, command: str, **kwargs: Any) -> Optional["Result"]: """ Execute ``command``, returning an instance of `Result` once complete. @@ -1481,7 +1492,7 @@ def __init__( exited: int = 0, pty: bool = False, hide: Tuple[str, ...] = tuple(), - ): + ) -> None: self.stdout = stdout self.stderr = stderr if encoding is None: @@ -1506,6 +1517,9 @@ def return_code(self) -> int: def __bool__(self) -> bool: return self.ok + def __int__(self) -> int: + return self.exited + def __str__(self) -> str: if self.exited is not None: desc = "Command exited with status {}.".format(self.exited) diff --git a/invoke/tasks.py b/invoke/tasks.py index cd3075e9b..a967a2836 100644 --- a/invoke/tasks.py +++ b/invoke/tasks.py @@ -21,6 +21,7 @@ Type, TypeVar, Union, + cast, ) from .context import Context @@ -286,7 +287,7 @@ def get_arguments( return args -def task(*args: Any, **kwargs: Any) -> Callable: +def task(*args: Any, **kwargs: Any) -> "Task[T]": """ Marks wrapped callable object as a valid Invoke task. @@ -357,7 +358,7 @@ def inner(body: Callable) -> Task[T]: return _task # update_wrapper(inner, klass) - return inner + return cast('Task["T"]', inner) class Call: diff --git a/tasks.py b/tasks.py index 12e0c3b84..ce8459971 100644 --- a/tasks.py +++ b/tasks.py @@ -53,7 +53,7 @@ def test( # TODO: replace with invocations' once the "call truly local tester" problem is # solved (see other TODOs). For now this is just a copy/paste/modify. -@task(help=test.help) # type: ignore +@task(help=test.help) def integration( c: "Context", opts: Optional[str] = None, pty: bool = True ) -> None: diff --git a/tests/context.py b/tests/context.py index b7266042a..d1b031881 100644 --- a/tests/context.py +++ b/tests/context.py @@ -213,7 +213,7 @@ class Path: def __init__(self, value): self.value = value - def __str__(self): + def __fspath__(self): return self.value runner = Local.return_value