From 034ee8227c4d23481ed537049498cc6a6488a026 Mon Sep 17 00:00:00 2001 From: David Foster Date: Sun, 2 May 2021 20:51:10 -0700 Subject: [PATCH 1/9] Add PEP 655 Required and NotRequired to typing_extensions --- typing_extensions/README.rst | 4 +- .../src_py3/test_typing_extensions.py | 49 +++++++- .../src_py3/typing_extensions.py | 112 ++++++++++++++++++ 3 files changed, 163 insertions(+), 2 deletions(-) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 98f621358..ac20a3f08 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -57,10 +57,12 @@ Python 3.4+ only: ----------------- - ``ChainMap`` -- ``ParamSpec`` - ``Concatenate`` +- ``NotRequired`` +- ``ParamSpec`` - ``ParamSpecArgs`` - ``ParamSpecKwargs`` +- ``Required`` - ``TypeGuard`` Python 3.5+ only: diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index e2889ce0e..47184a91e 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -12,7 +12,10 @@ from typing import Tuple, List, Dict, Iterator, Callable from typing import Generic from typing import no_type_check -from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict +from typing_extensions import ( + NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict, + Required +) from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard try: @@ -228,6 +231,50 @@ def test_no_isinstance(self): issubclass(int, Final) +class RequiredTests(BaseTestCase): + + def test_basics(self): + with self.assertRaises(TypeError): + Required[1] + with self.assertRaises(TypeError): + Required[int, str] + with self.assertRaises(TypeError): + Required[int][str] + + def test_repr(self): + if hasattr(typing, 'Required'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(Required), mod_name + '.Required') + cv = Required[int] + self.assertEqual(repr(cv), mod_name + '.Required[int]') + cv = Required[Employee] + self.assertEqual(repr(cv), mod_name + '.Required[%s.Employee]' % __name__) + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(Required)): + pass + with self.assertRaises(TypeError): + class C(type(Required[int])): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + Required() + with self.assertRaises(TypeError): + type(Required)() + with self.assertRaises(TypeError): + type(Required[Optional[int]])() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, Required[int]) + with self.assertRaises(TypeError): + issubclass(int, Required) + + class IntVarTests(BaseTestCase): def test_valid(self): T_ints = IntVar("T_ints") # noqa diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index 82d1c2dc2..ac3357441 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -2803,3 +2803,115 @@ def is_str(val: Union[str, float]): PEP 647 (User-Defined Type Guards). """ __type__ = None + + +_REQUIRED_DOC = \ + """A special typing construct to mark a key of a total=False TypedDict + as required. For example: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + + There is no runtime checking that a required key is actually provided + when instantiating a related TypedDict. + """ +_NOT_REQUIRED_DOC = \ + """A special typing construct to mark a key of a TypedDict as + potentially missing. For example: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + """ +if hasattr(typing, 'Required'): + Required = typing.Required + NotRequired = typing.NotRequired +elif sys.version_info[:2] >= (3, 9): + class _ExtensionsSpecialForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_ExtensionsSpecialForm + def Required(self, parameters): + _REQUIRED_DOC + item = typing._type_check(parameters, f'{self} accepts only single type.') + return typing._GenericAlias(self, (item,)) + + @_ExtensionsSpecialForm + def NotRequired(self, parameters): + _NOT_REQUIRED_DOC + item = typing._type_check(parameters, f'{self} accepts only single type.') + return typing._GenericAlias(self, (item,)) +elif sys.version_info[:2] >= (3, 7): + class _RequiredForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + '{} accepts only single type'.format(self._name)) + return _GenericAlias(self, (item,)) + + Required = _RequiredForm('Required', doc=_REQUIRED_DOC) + NotRequired = _RequiredForm('NotRequired', doc=_NOT_REQUIRED_DOC) +elif hasattr(typing, '_FinalTypingBase'): + # NOTE: Modeled after _Final's implementation when _FinalTypingBase available + class _MaybeRequired(typing._FinalTypingBase, _root=True): + __slots__ = ('__type__',) + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(typing._type_check(item, + '{} accepts only single type.'.format(cls.__name__[1:])), + _root=True) + raise TypeError('{} cannot be further subscripted' + .format(cls.__name__[1:])) + + def _eval_type(self, globalns, localns): + new_tp = typing._eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += '[{}]'.format(typing._type_repr(self.__type__)) + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, _Final): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + class _Required(_MaybeRequired, _root=True): + _REQUIRED_DOC + + class _NotRequired(_MaybeRequired, _root=True): + _NOT_REQUIRED_DOC + + Required = _Required(_root=True) + NotRequired = _NotRequired(_root=True) +else: + # Unreachable? + pass From 2a673a51d52f1c2a6d438de59b584bab7c74bc1d Mon Sep 17 00:00:00 2001 From: David Foster Date: Sun, 2 May 2021 21:13:20 -0700 Subject: [PATCH 2/9] Fix f-string errors on Python <3.6 --- typing_extensions/src_py3/typing_extensions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index ac3357441..18d6803e6 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -2845,13 +2845,13 @@ def __repr__(self): @_ExtensionsSpecialForm def Required(self, parameters): _REQUIRED_DOC - item = typing._type_check(parameters, f'{self} accepts only single type.') + item = typing._type_check(parameters, '{} accepts only single type'.format(self._name)) return typing._GenericAlias(self, (item,)) @_ExtensionsSpecialForm def NotRequired(self, parameters): _NOT_REQUIRED_DOC - item = typing._type_check(parameters, f'{self} accepts only single type.') + item = typing._type_check(parameters, '{} accepts only single type'.format(self._name)) return typing._GenericAlias(self, (item,)) elif sys.version_info[:2] >= (3, 7): class _RequiredForm(typing._SpecialForm, _root=True): From 52a09463d16e6ec5d6914e80ee8c4fe02867e9c3 Mon Sep 17 00:00:00 2001 From: David Foster Date: Sun, 2 May 2021 21:21:47 -0700 Subject: [PATCH 3/9] Inline _REQUIRED_DOC and _NOT_REQUIRED_DOC --- .../src_py3/typing_extensions.py | 118 ++++++++++++------ 1 file changed, 83 insertions(+), 35 deletions(-) diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index 18d6803e6..3bc5f0c9d 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -2805,35 +2805,6 @@ def is_str(val: Union[str, float]): __type__ = None -_REQUIRED_DOC = \ - """A special typing construct to mark a key of a total=False TypedDict - as required. For example: - - class Movie(TypedDict, total=False): - title: Required[str] - year: int - - m = Movie( - title='The Matrix', # typechecker error if key is omitted - year=1999, - ) - - There is no runtime checking that a required key is actually provided - when instantiating a related TypedDict. - """ -_NOT_REQUIRED_DOC = \ - """A special typing construct to mark a key of a TypedDict as - potentially missing. For example: - - class Movie(TypedDict): - title: str - year: NotRequired[int] - - m = Movie( - title='The Matrix', # typechecker error if key is omitted - year=1999, - ) - """ if hasattr(typing, 'Required'): Required = typing.Required NotRequired = typing.NotRequired @@ -2844,13 +2815,38 @@ def __repr__(self): @_ExtensionsSpecialForm def Required(self, parameters): - _REQUIRED_DOC + """A special typing construct to mark a key of a total=False TypedDict + as required. For example: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + + There is no runtime checking that a required key is actually provided + when instantiating a related TypedDict. + """ item = typing._type_check(parameters, '{} accepts only single type'.format(self._name)) return typing._GenericAlias(self, (item,)) @_ExtensionsSpecialForm def NotRequired(self, parameters): - _NOT_REQUIRED_DOC + """A special typing construct to mark a key of a TypedDict as + potentially missing. For example: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + """ item = typing._type_check(parameters, '{} accepts only single type'.format(self._name)) return typing._GenericAlias(self, (item,)) elif sys.version_info[:2] >= (3, 7): @@ -2863,8 +2859,35 @@ def __getitem__(self, parameters): '{} accepts only single type'.format(self._name)) return _GenericAlias(self, (item,)) - Required = _RequiredForm('Required', doc=_REQUIRED_DOC) - NotRequired = _RequiredForm('NotRequired', doc=_NOT_REQUIRED_DOC) + Required = _RequiredForm('Required', doc= + """A special typing construct to mark a key of a total=False TypedDict + as required. For example: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + + There is no runtime checking that a required key is actually provided + when instantiating a related TypedDict. + """) + NotRequired = _RequiredForm('NotRequired', doc= + """A special typing construct to mark a key of a TypedDict as + potentially missing. For example: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + """) elif hasattr(typing, '_FinalTypingBase'): # NOTE: Modeled after _Final's implementation when _FinalTypingBase available class _MaybeRequired(typing._FinalTypingBase, _root=True): @@ -2905,10 +2928,35 @@ def __eq__(self, other): return self is other class _Required(_MaybeRequired, _root=True): - _REQUIRED_DOC + """A special typing construct to mark a key of a total=False TypedDict + as required. For example: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + + There is no runtime checking that a required key is actually provided + when instantiating a related TypedDict. + """ class _NotRequired(_MaybeRequired, _root=True): - _NOT_REQUIRED_DOC + """A special typing construct to mark a key of a TypedDict as + potentially missing. For example: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + """ Required = _Required(_root=True) NotRequired = _NotRequired(_root=True) From 67cb26d039b22ee418bc5c84c522ca5454b2ad19 Mon Sep 17 00:00:00 2001 From: David Foster Date: Sun, 2 May 2021 21:39:52 -0700 Subject: [PATCH 4/9] Remove Python 3.5.0-3.5.2 support for Required. Test NotRequired. --- typing_extensions/README.rst | 4 +- .../src_py3/test_typing_extensions.py | 120 ++++++++++++------ .../src_py3/typing_extensions.py | 2 +- 3 files changed, 85 insertions(+), 41 deletions(-) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index ac20a3f08..a5ca2e5b7 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -58,11 +58,11 @@ Python 3.4+ only: - ``ChainMap`` - ``Concatenate`` -- ``NotRequired`` +- ``NotRequired`` (except on Python 3.5.0-3.5.2) - ``ParamSpec`` - ``ParamSpecArgs`` - ``ParamSpecKwargs`` -- ``Required`` +- ``Required`` (except on Python 3.5.0-3.5.2) - ``TypeGuard`` Python 3.5+ only: diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index 47184a91e..33ebd1231 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -14,7 +14,7 @@ from typing import no_type_check from typing_extensions import ( NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict, - Required + Required, NotRequired, ) from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard @@ -231,48 +231,92 @@ def test_no_isinstance(self): issubclass(int, Final) -class RequiredTests(BaseTestCase): +if TYPING_3_5_3: + class RequiredTests(BaseTestCase): - def test_basics(self): - with self.assertRaises(TypeError): - Required[1] - with self.assertRaises(TypeError): - Required[int, str] - with self.assertRaises(TypeError): - Required[int][str] + def test_basics(self): + with self.assertRaises(TypeError): + Required[1] + with self.assertRaises(TypeError): + Required[int, str] + with self.assertRaises(TypeError): + Required[int][str] - def test_repr(self): - if hasattr(typing, 'Required'): - mod_name = 'typing' - else: - mod_name = 'typing_extensions' - self.assertEqual(repr(Required), mod_name + '.Required') - cv = Required[int] - self.assertEqual(repr(cv), mod_name + '.Required[int]') - cv = Required[Employee] - self.assertEqual(repr(cv), mod_name + '.Required[%s.Employee]' % __name__) + def test_repr(self): + if hasattr(typing, 'Required'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(Required), mod_name + '.Required') + cv = Required[int] + self.assertEqual(repr(cv), mod_name + '.Required[int]') + cv = Required[Employee] + self.assertEqual(repr(cv), mod_name + '.Required[%s.Employee]' % __name__) + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(Required)): + pass + with self.assertRaises(TypeError): + class C(type(Required[int])): + pass - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(Required)): - pass - with self.assertRaises(TypeError): - class C(type(Required[int])): - pass + def test_cannot_init(self): + with self.assertRaises(TypeError): + Required() + with self.assertRaises(TypeError): + type(Required)() + with self.assertRaises(TypeError): + type(Required[Optional[int]])() - def test_cannot_init(self): - with self.assertRaises(TypeError): - Required() - with self.assertRaises(TypeError): - type(Required)() - with self.assertRaises(TypeError): - type(Required[Optional[int]])() + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, Required[int]) + with self.assertRaises(TypeError): + issubclass(int, Required) + + class NotRequiredTests(BaseTestCase): - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, Required[int]) - with self.assertRaises(TypeError): - issubclass(int, Required) + def test_basics(self): + with self.assertRaises(TypeError): + NotRequired[1] + with self.assertRaises(TypeError): + NotRequired[int, str] + with self.assertRaises(TypeError): + NotRequired[int][str] + + def test_repr(self): + if hasattr(typing, 'NotRequired'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(NotRequired), mod_name + '.NotRequired') + cv = NotRequired[int] + self.assertEqual(repr(cv), mod_name + '.NotRequired[int]') + cv = NotRequired[Employee] + self.assertEqual(repr(cv), mod_name + '.NotRequired[%s.Employee]' % __name__) + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(NotRequired)): + pass + with self.assertRaises(TypeError): + class C(type(NotRequired[int])): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + NotRequired() + with self.assertRaises(TypeError): + type(NotRequired)() + with self.assertRaises(TypeError): + type(NotRequired[Optional[int]])() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, NotRequired[int]) + with self.assertRaises(TypeError): + issubclass(int, NotRequired) class IntVarTests(BaseTestCase): diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index 3bc5f0c9d..73430d9f4 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -2961,5 +2961,5 @@ class Movie(TypedDict): Required = _Required(_root=True) NotRequired = _NotRequired(_root=True) else: - # Unreachable? + # Python 3.5.0 - 3.5.2: Unsupported pass From e6cd7647373c5fc5c55878d37ce2cbc47fcfcf1b Mon Sep 17 00:00:00 2001 From: David Foster Date: Sun, 2 May 2021 21:44:06 -0700 Subject: [PATCH 5/9] Take 2: Remove Python 3.5.0-3.5.2 support for Required. --- typing_extensions/src_py3/test_typing_extensions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index 33ebd1231..49ec15bbf 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -12,10 +12,7 @@ from typing import Tuple, List, Dict, Iterator, Callable from typing import Generic from typing import no_type_check -from typing_extensions import ( - NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict, - Required, NotRequired, -) +from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard try: @@ -79,6 +76,9 @@ # Protocols are hard to backport to the original version of typing 3.5.0 HAVE_PROTOCOLS = sys.version_info[:3] != (3, 5, 0) +if TYPING_3_5_3: + from typing_extensions import Required, NotRequired + class BaseTestCase(TestCase): def assertIsSubclass(self, cls, class_or_tuple, msg=None): From 969c4c803c3cb88621277ee9908eb79dbd4d175e Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 11 Nov 2021 18:29:37 -0800 Subject: [PATCH 6/9] remove condition from tests --- .../src_py3/test_typing_extensions.py | 156 +++++++++--------- 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index 5bda4bede..2fc5b3f2a 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -193,92 +193,92 @@ def test_no_isinstance(self): issubclass(int, Final) -if TYPING_3_5_3: - class RequiredTests(BaseTestCase): +class RequiredTests(BaseTestCase): - def test_basics(self): - with self.assertRaises(TypeError): - Required[1] - with self.assertRaises(TypeError): - Required[int, str] - with self.assertRaises(TypeError): - Required[int][str] + def test_basics(self): + with self.assertRaises(TypeError): + Required[1] + with self.assertRaises(TypeError): + Required[int, str] + with self.assertRaises(TypeError): + Required[int][str] - def test_repr(self): - if hasattr(typing, 'Required'): - mod_name = 'typing' - else: - mod_name = 'typing_extensions' - self.assertEqual(repr(Required), mod_name + '.Required') - cv = Required[int] - self.assertEqual(repr(cv), mod_name + '.Required[int]') - cv = Required[Employee] - self.assertEqual(repr(cv), mod_name + '.Required[%s.Employee]' % __name__) - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(Required)): - pass - with self.assertRaises(TypeError): - class C(type(Required[int])): - pass + def test_repr(self): + if hasattr(typing, 'Required'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(Required), mod_name + '.Required') + cv = Required[int] + self.assertEqual(repr(cv), mod_name + '.Required[int]') + cv = Required[Employee] + self.assertEqual(repr(cv), mod_name + '.Required[%s.Employee]' % __name__) - def test_cannot_init(self): - with self.assertRaises(TypeError): - Required() - with self.assertRaises(TypeError): - type(Required)() - with self.assertRaises(TypeError): - type(Required[Optional[int]])() + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(Required)): + pass + with self.assertRaises(TypeError): + class C(type(Required[int])): + pass - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, Required[int]) - with self.assertRaises(TypeError): - issubclass(int, Required) - - class NotRequiredTests(BaseTestCase): + def test_cannot_init(self): + with self.assertRaises(TypeError): + Required() + with self.assertRaises(TypeError): + type(Required)() + with self.assertRaises(TypeError): + type(Required[Optional[int]])() - def test_basics(self): - with self.assertRaises(TypeError): - NotRequired[1] - with self.assertRaises(TypeError): - NotRequired[int, str] - with self.assertRaises(TypeError): - NotRequired[int][str] + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, Required[int]) + with self.assertRaises(TypeError): + issubclass(int, Required) - def test_repr(self): - if hasattr(typing, 'NotRequired'): - mod_name = 'typing' - else: - mod_name = 'typing_extensions' - self.assertEqual(repr(NotRequired), mod_name + '.NotRequired') - cv = NotRequired[int] - self.assertEqual(repr(cv), mod_name + '.NotRequired[int]') - cv = NotRequired[Employee] - self.assertEqual(repr(cv), mod_name + '.NotRequired[%s.Employee]' % __name__) - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(NotRequired)): - pass - with self.assertRaises(TypeError): - class C(type(NotRequired[int])): - pass - def test_cannot_init(self): - with self.assertRaises(TypeError): - NotRequired() - with self.assertRaises(TypeError): - type(NotRequired)() - with self.assertRaises(TypeError): - type(NotRequired[Optional[int]])() +class NotRequiredTests(BaseTestCase): - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, NotRequired[int]) - with self.assertRaises(TypeError): - issubclass(int, NotRequired) + def test_basics(self): + with self.assertRaises(TypeError): + NotRequired[1] + with self.assertRaises(TypeError): + NotRequired[int, str] + with self.assertRaises(TypeError): + NotRequired[int][str] + + def test_repr(self): + if hasattr(typing, 'NotRequired'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(NotRequired), mod_name + '.NotRequired') + cv = NotRequired[int] + self.assertEqual(repr(cv), mod_name + '.NotRequired[int]') + cv = NotRequired[Employee] + self.assertEqual(repr(cv), mod_name + '.NotRequired[%s.Employee]' % __name__) + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(NotRequired)): + pass + with self.assertRaises(TypeError): + class C(type(NotRequired[int])): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + NotRequired() + with self.assertRaises(TypeError): + type(NotRequired)() + with self.assertRaises(TypeError): + type(NotRequired[Optional[int]])() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, NotRequired[int]) + with self.assertRaises(TypeError): + issubclass(int, NotRequired) class IntVarTests(BaseTestCase): From 224c14c937e14cf28adae477e8710499f59e16f7 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 11 Nov 2021 18:36:15 -0800 Subject: [PATCH 7/9] fix test --- typing_extensions/src_py3/typing_extensions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index ad0040bbf..85a07ed42 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -2164,6 +2164,7 @@ class Movie(TypedDict): """ item = typing._type_check(parameters, '{} accepts only single type'.format(self._name)) return typing._GenericAlias(self, (item,)) + elif sys.version_info[:2] >= (3, 7): class _RequiredForm(typing._SpecialForm, _root=True): def __repr__(self): @@ -2172,7 +2173,7 @@ def __repr__(self): def __getitem__(self, parameters): item = typing._type_check(parameters, '{} accepts only single type'.format(self._name)) - return _GenericAlias(self, (item,)) + return typing._GenericAlias(self, (item,)) Required = _RequiredForm('Required', doc= """A special typing construct to mark a key of a total=False TypedDict From 4c1eeb9a58ad4ffd4b036a8d27f9aa4cb9e58c50 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 11 Nov 2021 18:38:09 -0800 Subject: [PATCH 8/9] fix flake8 --- typing_extensions/src_py3/typing_extensions.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index 85a07ed42..d50f710e8 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -2145,7 +2145,7 @@ class Movie(TypedDict, total=False): There is no runtime checking that a required key is actually provided when instantiating a related TypedDict. """ - item = typing._type_check(parameters, '{} accepts only single type'.format(self._name)) + item = typing._type_check(parameters, f'{self._name} accepts only single type') return typing._GenericAlias(self, (item,)) @_ExtensionsSpecialForm @@ -2162,7 +2162,7 @@ class Movie(TypedDict): year=1999, ) """ - item = typing._type_check(parameters, '{} accepts only single type'.format(self._name)) + item = typing._type_check(parameters, f'{self._name} accepts only single type') return typing._GenericAlias(self, (item,)) elif sys.version_info[:2] >= (3, 7): @@ -2175,8 +2175,9 @@ def __getitem__(self, parameters): '{} accepts only single type'.format(self._name)) return typing._GenericAlias(self, (item,)) - Required = _RequiredForm('Required', doc= - """A special typing construct to mark a key of a total=False TypedDict + Required = _RequiredForm( + 'Required', + doc="""A special typing construct to mark a key of a total=False TypedDict as required. For example: class Movie(TypedDict, total=False): @@ -2191,8 +2192,9 @@ class Movie(TypedDict, total=False): There is no runtime checking that a required key is actually provided when instantiating a related TypedDict. """) - NotRequired = _RequiredForm('NotRequired', doc= - """A special typing construct to mark a key of a TypedDict as + NotRequired = _RequiredForm( + 'NotRequired', + doc="""A special typing construct to mark a key of a TypedDict as potentially missing. For example: class Movie(TypedDict): From 170e69f7302c2f47ea40f15a1150de51529e45bd Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 11 Nov 2021 18:41:42 -0800 Subject: [PATCH 9/9] add to and reformat CHANGELOG --- typing_extensions/CHANGELOG | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 4c3a51d6c..897d9ad66 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,16 +1,16 @@ # Changes in version 4.0.0 -Starting with version 4.0.0, typing_extensions uses Semantic Versioning. -See the README for more information. - -Dropped support for Python versions 3.5 and older. - -Simplified backports for Python 3.6.0 and newer. -Patch by Adam Turner (@AA-Turner). +- Starting with version 4.0.0, typing_extensions uses Semantic Versioning. + See the README for more information. +- Dropped support for Python versions 3.5 and older. +- Simplified backports for Python 3.6.0 and newer. Patch by Adam Turner (@AA-Turner). ## Added in version 4.0.0 -- Runtime support for PEP 673 and `typing_extensions.Self`. +- Runtime support for PEP 673 and `typing_extensions.Self`. Patch by + James Hilton-Balfe (@Gobot1234). +- Runtime support for PEP 655 and `typing_extensions.Required` and `NotRequired`. + Patch by David Foster (@davidfstr). ## Removed in version 4.0.0