From a12975bd8f4a6cdbd05b94bdc4f86a3408061db0 Mon Sep 17 00:00:00 2001 From: Jakob Stadler <127443735+Jakob-Stadler@users.noreply.github.com> Date: Fri, 30 Jun 2023 10:49:54 +0200 Subject: [PATCH 1/4] Fix namespace argument regression in argparse.parse_args The previous commit [1] explicitly added `Namespace` to the first overload, which causes a false-postive when the used `namespace=` argument is a subclass of `Namespace`. Example code, flagged both in pyright and mypy (with most recent typeshed): ```python import argparse import sys # Explicit subclass of Namepsace # not strictly necessary, but it conveys intent better than a standalone class class ExampleNamespace(argparse.Namespace): example_argument: str parser = argparse.ArgumentParser() parser.add_argument('example_argument') ns = parser.parse_args(sys.argv, ExampleNamespace()) reveal_type(ns) # Revealed type is "Namespace" # error: Incompatible types in assignment (expression has type "Namespace", variable has type "ExampleNamespace") [assignment] ens: ExampleNamespace = parser.parse_args(sys.argv, ExampleNamespace()) reveal_type(ens) # Revealed type is "ExampleNamespace" ``` [1] https://github.com/python/typeshed/pull/10307 --- stdlib/argparse.pyi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stdlib/argparse.pyi b/stdlib/argparse.pyi index e41048516dd9..182f96bd0432 100644 --- a/stdlib/argparse.pyi +++ b/stdlib/argparse.pyi @@ -171,7 +171,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): ) -> None: ... @overload - def parse_args(self, args: Sequence[str] | None = None, namespace: Namespace | None = None) -> Namespace: ... # type: ignore[misc] + def parse_args(self, args: Sequence[str] | None = None, namespace: None = None) -> Namespace: ... # type: ignore[misc] @overload def parse_args(self, args: Sequence[str] | None, namespace: _N) -> _N: ... @overload @@ -210,7 +210,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): def format_usage(self) -> str: ... def format_help(self) -> str: ... @overload - def parse_known_args(self, args: Sequence[str] | None = None, namespace: Namespace | None = None) -> tuple[Namespace, list[str]]: ... # type: ignore[misc] + def parse_known_args(self, args: Sequence[str] | None = None, namespace: None = None) -> tuple[Namespace, list[str]]: ... # type: ignore[misc] @overload def parse_known_args(self, args: Sequence[str] | None, namespace: _N) -> tuple[_N, list[str]]: ... @overload @@ -219,13 +219,13 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): def exit(self, status: int = 0, message: str | None = None) -> NoReturn: ... def error(self, message: str) -> NoReturn: ... @overload - def parse_intermixed_args(self, args: Sequence[str] | None = None, namespace: Namespace | None = None) -> Namespace: ... # type: ignore[misc] + def parse_intermixed_args(self, args: Sequence[str] | None = None, namespace: None = None) -> Namespace: ... # type: ignore[misc] @overload def parse_intermixed_args(self, args: Sequence[str] | None, namespace: _N) -> _N: ... @overload def parse_intermixed_args(self, *, namespace: _N) -> _N: ... @overload - def parse_known_intermixed_args(self, args: Sequence[str] | None = None, namespace: Namespace | None = None) -> tuple[Namespace, list[str]]: ... # type: ignore[misc] + def parse_known_intermixed_args(self, args: Sequence[str] | None = None, namespace: None = None) -> tuple[Namespace, list[str]]: ... # type: ignore[misc] @overload def parse_known_intermixed_args(self, args: Sequence[str] | None, namespace: _N) -> tuple[_N, list[str]]: ... @overload From 79ac38f8221e362487c1e664ce334c36c04d34d0 Mon Sep 17 00:00:00 2001 From: Jakob Stadler <127443735+Jakob-Stadler@users.noreply.github.com> Date: Thu, 6 Jul 2023 17:41:42 +0200 Subject: [PATCH 2/4] Second attempt to fix argparse regression This solution may be overly verbose. The real problem is that the TypeVar _N should be bound to "mutable objects" that allow adding attributes via setattr(), and ignore builtin immutable objects like None. --- stdlib/argparse.pyi | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/stdlib/argparse.pyi b/stdlib/argparse.pyi index 182f96bd0432..cc8f96e162d9 100644 --- a/stdlib/argparse.pyi +++ b/stdlib/argparse.pyi @@ -173,8 +173,14 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): @overload def parse_args(self, args: Sequence[str] | None = None, namespace: None = None) -> Namespace: ... # type: ignore[misc] @overload + def parse_args(self, args: Sequence[str] | None, namespace: _N | None) -> _N | Namespace: ... + @overload def parse_args(self, args: Sequence[str] | None, namespace: _N) -> _N: ... @overload + def parse_args(self, *, namespace: None) -> Namespace: ... + @overload + def parse_args(self, *, namespace: _N | None) -> _N | Namespace: ... + @overload def parse_args(self, *, namespace: _N) -> _N: ... @overload def add_subparsers( @@ -212,8 +218,14 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): @overload def parse_known_args(self, args: Sequence[str] | None = None, namespace: None = None) -> tuple[Namespace, list[str]]: ... # type: ignore[misc] @overload + def parse_known_args(self, args: Sequence[str] | None, namespace: _N | None) -> tuple[_N | Namespace, list[str]]: ... + @overload def parse_known_args(self, args: Sequence[str] | None, namespace: _N) -> tuple[_N, list[str]]: ... @overload + def parse_known_args(self, *, namespace: None) -> tuple[Namespace, list[str]]: ... + @overload + def parse_known_args(self, *, namespace: _N | None) -> tuple[_N | Namespace, list[str]]: ... + @overload def parse_known_args(self, *, namespace: _N) -> tuple[_N, list[str]]: ... def convert_arg_line_to_args(self, arg_line: str) -> list[str]: ... def exit(self, status: int = 0, message: str | None = None) -> NoReturn: ... @@ -221,14 +233,26 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): @overload def parse_intermixed_args(self, args: Sequence[str] | None = None, namespace: None = None) -> Namespace: ... # type: ignore[misc] @overload + def parse_intermixed_args(self, args: Sequence[str] | None, namespace: _N | None) -> _N | Namespace: ... + @overload def parse_intermixed_args(self, args: Sequence[str] | None, namespace: _N) -> _N: ... @overload + def parse_intermixed_args(self, *, namespace: None) -> Namespace: ... + @overload + def parse_intermixed_args(self, *, namespace: _N | None) -> _N | Namespace: ... + @overload def parse_intermixed_args(self, *, namespace: _N) -> _N: ... @overload def parse_known_intermixed_args(self, args: Sequence[str] | None = None, namespace: None = None) -> tuple[Namespace, list[str]]: ... # type: ignore[misc] @overload + def parse_known_intermixed_args(self, args: Sequence[str] | None, namespace: _N | None) -> tuple[_N | Namespace, list[str]]: ... + @overload def parse_known_intermixed_args(self, args: Sequence[str] | None, namespace: _N) -> tuple[_N, list[str]]: ... @overload + def parse_known_intermixed_args(self, *, namespace: None) -> tuple[Namespace, list[str]]: ... + @overload + def parse_known_intermixed_args(self, *, namespace: _N | None) -> tuple[_N | Namespace, list[str]]: ... + @overload def parse_known_intermixed_args(self, *, namespace: _N) -> tuple[_N, list[str]]: ... # undocumented def _get_optional_actions(self) -> list[Action]: ... From eef5a7e97cec47907e7b2966998b18682dcc6e04 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 6 Jul 2023 15:43:02 +0000 Subject: [PATCH 3/4] [pre-commit.ci] auto fixes from pre-commit.com hooks --- stdlib/argparse.pyi | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stdlib/argparse.pyi b/stdlib/argparse.pyi index cc8f96e162d9..3c5c5f5c7dab 100644 --- a/stdlib/argparse.pyi +++ b/stdlib/argparse.pyi @@ -245,7 +245,9 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): @overload def parse_known_intermixed_args(self, args: Sequence[str] | None = None, namespace: None = None) -> tuple[Namespace, list[str]]: ... # type: ignore[misc] @overload - def parse_known_intermixed_args(self, args: Sequence[str] | None, namespace: _N | None) -> tuple[_N | Namespace, list[str]]: ... + def parse_known_intermixed_args( + self, args: Sequence[str] | None, namespace: _N | None + ) -> tuple[_N | Namespace, list[str]]: ... @overload def parse_known_intermixed_args(self, args: Sequence[str] | None, namespace: _N) -> tuple[_N, list[str]]: ... @overload From a45041ef30b8fc3b75b0975514356d2dd050067f Mon Sep 17 00:00:00 2001 From: Jakob Stadler <127443735+Jakob-Stadler@users.noreply.github.com> Date: Thu, 6 Jul 2023 18:00:02 +0200 Subject: [PATCH 4/4] Revert second attempt --- stdlib/argparse.pyi | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/stdlib/argparse.pyi b/stdlib/argparse.pyi index 3c5c5f5c7dab..182f96bd0432 100644 --- a/stdlib/argparse.pyi +++ b/stdlib/argparse.pyi @@ -173,14 +173,8 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): @overload def parse_args(self, args: Sequence[str] | None = None, namespace: None = None) -> Namespace: ... # type: ignore[misc] @overload - def parse_args(self, args: Sequence[str] | None, namespace: _N | None) -> _N | Namespace: ... - @overload def parse_args(self, args: Sequence[str] | None, namespace: _N) -> _N: ... @overload - def parse_args(self, *, namespace: None) -> Namespace: ... - @overload - def parse_args(self, *, namespace: _N | None) -> _N | Namespace: ... - @overload def parse_args(self, *, namespace: _N) -> _N: ... @overload def add_subparsers( @@ -218,14 +212,8 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): @overload def parse_known_args(self, args: Sequence[str] | None = None, namespace: None = None) -> tuple[Namespace, list[str]]: ... # type: ignore[misc] @overload - def parse_known_args(self, args: Sequence[str] | None, namespace: _N | None) -> tuple[_N | Namespace, list[str]]: ... - @overload def parse_known_args(self, args: Sequence[str] | None, namespace: _N) -> tuple[_N, list[str]]: ... @overload - def parse_known_args(self, *, namespace: None) -> tuple[Namespace, list[str]]: ... - @overload - def parse_known_args(self, *, namespace: _N | None) -> tuple[_N | Namespace, list[str]]: ... - @overload def parse_known_args(self, *, namespace: _N) -> tuple[_N, list[str]]: ... def convert_arg_line_to_args(self, arg_line: str) -> list[str]: ... def exit(self, status: int = 0, message: str | None = None) -> NoReturn: ... @@ -233,28 +221,14 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): @overload def parse_intermixed_args(self, args: Sequence[str] | None = None, namespace: None = None) -> Namespace: ... # type: ignore[misc] @overload - def parse_intermixed_args(self, args: Sequence[str] | None, namespace: _N | None) -> _N | Namespace: ... - @overload def parse_intermixed_args(self, args: Sequence[str] | None, namespace: _N) -> _N: ... @overload - def parse_intermixed_args(self, *, namespace: None) -> Namespace: ... - @overload - def parse_intermixed_args(self, *, namespace: _N | None) -> _N | Namespace: ... - @overload def parse_intermixed_args(self, *, namespace: _N) -> _N: ... @overload def parse_known_intermixed_args(self, args: Sequence[str] | None = None, namespace: None = None) -> tuple[Namespace, list[str]]: ... # type: ignore[misc] @overload - def parse_known_intermixed_args( - self, args: Sequence[str] | None, namespace: _N | None - ) -> tuple[_N | Namespace, list[str]]: ... - @overload def parse_known_intermixed_args(self, args: Sequence[str] | None, namespace: _N) -> tuple[_N, list[str]]: ... @overload - def parse_known_intermixed_args(self, *, namespace: None) -> tuple[Namespace, list[str]]: ... - @overload - def parse_known_intermixed_args(self, *, namespace: _N | None) -> tuple[_N | Namespace, list[str]]: ... - @overload def parse_known_intermixed_args(self, *, namespace: _N) -> tuple[_N, list[str]]: ... # undocumented def _get_optional_actions(self) -> list[Action]: ...