From 68e097f119b35ac88b6c868365c421e3ac15ad14 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Fri, 10 Jul 2020 10:12:45 +0200 Subject: [PATCH 1/2] Introduce Mapping-like protocols typing.Mapping is not a protocol, which has caused problems in the past. (E.g. python/typeshed#3569, see also python/typeshed#3576.) This introduces a few narrow protocols to _typeshed.pyi that can be used in place of Mapping. Not all uses of Mapping can be replaced. For example, cgi.FieldStorage explictly checks whether the supplied headers argument is a Mapping instance. --- stdlib/2and3/_typeshed/__init__.pyi | 17 ++++++++++++++++- stdlib/2and3/cgi.pyi | 26 +++++++++++++++++--------- stdlib/3.9/graphlib.pyi | 5 +++-- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/stdlib/2and3/_typeshed/__init__.pyi b/stdlib/2and3/_typeshed/__init__.pyi index b2368a980784..c6bfec745447 100644 --- a/stdlib/2and3/_typeshed/__init__.pyi +++ b/stdlib/2and3/_typeshed/__init__.pyi @@ -15,12 +15,27 @@ import array import mmap import sys -from typing import Protocol, Text, TypeVar, Union +from typing import AbstractSet, Container, Protocol, Text, Tuple, TypeVar, Union from typing_extensions import Literal +_KT = TypeVar("_KT") +_VT = TypeVar("_VT") +_VT_co = TypeVar("_VT_co", covariant=True) _T_co = TypeVar("_T_co", covariant=True) _T_contra = TypeVar("_T_contra", contravariant=True) +# Mapping-like protocols + +class SupportsItems(Protocol[_KT, _VT_co]): + def items(self) -> AbstractSet[Tuple[_KT, _VT_co]]: ... + +class SupportsGetItem(Container[_KT], Protocol[_KT, _VT_co]): + def __getitem__(self, __k: _KT) -> _VT_co: ... + +class SupportsItemAccess(SupportsGetItem[_KT, _VT], Protocol[_KT, _VT]): + def __setitem__(self, __k: _KT, __v: _VT) -> None: ... + def __delitem__(self, __v: _KT) -> None: ... + # StrPath and AnyPath can be used in places where a # path can be used instead of a string, starting with Python 3.6. if sys.version_info >= (3, 6): diff --git a/stdlib/2and3/cgi.pyi b/stdlib/2and3/cgi.pyi index a081b1b642ad..31f03cc623c2 100644 --- a/stdlib/2and3/cgi.pyi +++ b/stdlib/2and3/cgi.pyi @@ -1,10 +1,14 @@ import sys -from typing import IO, Any, AnyStr, Dict, Iterator, List, Mapping, Optional, Tuple, TypeVar, Union +from _typeshed import SupportsGetItem, SupportsItemAccess +from typing import IO, Any, AnyStr, Dict, Iterable, Iterator, List, Mapping, Optional, Protocol, Tuple, TypeVar, Union _T = TypeVar("_T", bound=FieldStorage) def parse( - fp: Optional[IO[Any]] = ..., environ: Mapping[str, str] = ..., keep_blank_values: bool = ..., strict_parsing: bool = ... + fp: Optional[IO[Any]] = ..., + environ: SupportsItemAccess[str, str] = ..., + keep_blank_values: bool = ..., + strict_parsing: bool = ..., ) -> Dict[str, List[str]]: ... if sys.version_info < (3, 8): @@ -13,15 +17,19 @@ if sys.version_info < (3, 8): if sys.version_info >= (3, 7): def parse_multipart( - fp: IO[Any], pdict: Mapping[str, bytes], encoding: str = ..., errors: str = ... + fp: IO[Any], pdict: SupportsGetItem[str, bytes], encoding: str = ..., errors: str = ... ) -> Dict[str, List[Any]]: ... else: - def parse_multipart(fp: IO[Any], pdict: Mapping[str, bytes]) -> Dict[str, List[bytes]]: ... + def parse_multipart(fp: IO[Any], pdict: SupportsGetItem[str, bytes]) -> Dict[str, List[bytes]]: ... + +class _Environ(Protocol): + def __getitem__(self, __k: str) -> str: ... + def keys(self) -> Iterable[str]: ... def parse_header(line: str) -> Tuple[str, Dict[str, str]]: ... -def test(environ: Mapping[str, str] = ...) -> None: ... -def print_environ(environ: Mapping[str, str] = ...) -> None: ... +def test(environ: _Environ = ...) -> None: ... +def print_environ(environ: _Environ = ...) -> None: ... def print_form(form: Dict[str, Any]) -> None: ... def print_directory() -> None: ... def print_environ_usage() -> None: ... @@ -77,7 +85,7 @@ class FieldStorage(object): fp: Optional[IO[Any]] = ..., headers: Optional[Mapping[str, str]] = ..., outerboundary: bytes = ..., - environ: Mapping[str, str] = ..., + environ: SupportsGetItem[str, str] = ..., keep_blank_values: int = ..., strict_parsing: int = ..., limit: Optional[int] = ..., @@ -91,7 +99,7 @@ class FieldStorage(object): fp: Optional[IO[Any]] = ..., headers: Optional[Mapping[str, str]] = ..., outerboundary: bytes = ..., - environ: Mapping[str, str] = ..., + environ: SupportsGetItem[str, str] = ..., keep_blank_values: int = ..., strict_parsing: int = ..., limit: Optional[int] = ..., @@ -104,7 +112,7 @@ class FieldStorage(object): fp: IO[Any] = ..., headers: Mapping[str, str] = ..., outerboundary: bytes = ..., - environ: Mapping[str, str] = ..., + environ: SupportsGetItem[str, str] = ..., keep_blank_values: int = ..., strict_parsing: int = ..., ) -> None: ... diff --git a/stdlib/3.9/graphlib.pyi b/stdlib/3.9/graphlib.pyi index d1ecb62a900a..ca21b42329d2 100644 --- a/stdlib/3.9/graphlib.pyi +++ b/stdlib/3.9/graphlib.pyi @@ -1,9 +1,10 @@ -from typing import Generic, Iterable, Mapping, Optional, Tuple, TypeVar +from _typeshed import SupportsItems +from typing import Generic, Iterable, Optional, Tuple, TypeVar _T = TypeVar("_T") class TopologicalSorter(Generic[_T]): - def __init__(self, graph: Optional[Mapping[_T, Iterable[_T]]] = ...) -> None: ... + def __init__(self, graph: Optional[SupportsItems[_T, Iterable[_T]]] = ...) -> None: ... def add(self, node: _T, *predecessors: _T) -> None: ... def prepare(self) -> None: ... def is_active(self) -> bool: ... From 9fdcf314b41305e57685fcc465328a6eef08a1db Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Fri, 10 Jul 2020 10:25:15 +0200 Subject: [PATCH 2/2] Fix variances --- stdlib/2and3/_typeshed/__init__.pyi | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/stdlib/2and3/_typeshed/__init__.pyi b/stdlib/2and3/_typeshed/__init__.pyi index c6bfec745447..ed5154364e6a 100644 --- a/stdlib/2and3/_typeshed/__init__.pyi +++ b/stdlib/2and3/_typeshed/__init__.pyi @@ -18,7 +18,8 @@ import sys from typing import AbstractSet, Container, Protocol, Text, Tuple, TypeVar, Union from typing_extensions import Literal -_KT = TypeVar("_KT") +_KT_co = TypeVar("_KT_co", covariant=True) +_KT_contra = TypeVar("_KT_contra", contravariant=True) _VT = TypeVar("_VT") _VT_co = TypeVar("_VT_co", covariant=True) _T_co = TypeVar("_T_co", covariant=True) @@ -26,15 +27,15 @@ _T_contra = TypeVar("_T_contra", contravariant=True) # Mapping-like protocols -class SupportsItems(Protocol[_KT, _VT_co]): - def items(self) -> AbstractSet[Tuple[_KT, _VT_co]]: ... +class SupportsItems(Protocol[_KT_co, _VT_co]): + def items(self) -> AbstractSet[Tuple[_KT_co, _VT_co]]: ... -class SupportsGetItem(Container[_KT], Protocol[_KT, _VT_co]): - def __getitem__(self, __k: _KT) -> _VT_co: ... +class SupportsGetItem(Container[_KT_contra], Protocol[_KT_contra, _VT_co]): + def __getitem__(self, __k: _KT_contra) -> _VT_co: ... -class SupportsItemAccess(SupportsGetItem[_KT, _VT], Protocol[_KT, _VT]): - def __setitem__(self, __k: _KT, __v: _VT) -> None: ... - def __delitem__(self, __v: _KT) -> None: ... +class SupportsItemAccess(SupportsGetItem[_KT_contra, _VT], Protocol[_KT_contra, _VT]): + def __setitem__(self, __k: _KT_contra, __v: _VT) -> None: ... + def __delitem__(self, __v: _KT_contra) -> None: ... # StrPath and AnyPath can be used in places where a # path can be used instead of a string, starting with Python 3.6.