Skip to content
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

PEP 655 support redux #937

Merged
merged 11 commits into from Nov 14, 2021
16 changes: 8 additions & 8 deletions 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

Expand Down
2 changes: 2 additions & 0 deletions typing_extensions/README.rst
Expand Up @@ -50,12 +50,14 @@ This module currently contains the following:
- ``Literal``
- ``NewType``
- ``NoReturn``
- ``NotRequired``
- ``overload``
- ``OrderedDict``
- ``ParamSpec``
- ``ParamSpecArgs``
- ``ParamSpecKwargs``
- ``Protocol``
- ``Required``
- ``runtime_checkable``
- ``Text``
- ``Type``
Expand Down
90 changes: 89 additions & 1 deletion typing_extensions/src_py3/test_typing_extensions.py
Expand Up @@ -18,7 +18,7 @@
import typing_extensions
from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict, Self
from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard
from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager
from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired
from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload
try:
from typing_extensions import get_type_hints
Expand Down Expand Up @@ -193,6 +193,94 @@ 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 NotRequiredTests(BaseTestCase):

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):
def test_valid(self):
T_ints = IntVar("T_ints") # noqa
Expand Down
160 changes: 160 additions & 0 deletions typing_extensions/src_py3/typing_extensions.py
Expand Up @@ -2118,3 +2118,163 @@ def __subclasscheck__(self, cls):
raise TypeError(f"{self} cannot be used with issubclass().")

Self = _Self(_root=True)


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):
"""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, f'{self._name} accepts only single type')
return typing._GenericAlias(self, (item,))

@_ExtensionsSpecialForm
def NotRequired(self, parameters):
"""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, f'{self._name} 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 typing._GenericAlias(self, (item,))

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,
)
""")
else:
# 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):
"""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):
"""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)