Align regex, dataclasses and typing with Python 3.15 (#9189)

Expose Pattern.prefixmatch and Scanner.prefixmatch and refresh their canonical re consumers, dataclasses annotation handling, typing APIs and regression fixtures from CPython d220409a13b77de7a6c30abe16ea90db01528edc.

Assisted-by: OpenAI Codex:model version unavailable
This commit is contained in:
youknowdot authored and GitHub committed 2026-10-18 22:37:35 +09:00
1 parent 7750dbcb86
commit 7137540b93
15 files changed
+1176 -494

No files matched your search

+89 -80
View File
@@ -1,13 +1,12 @@
import re
import sys
import copy
import types
import inspect
import keyword
import itertools
import annotationlib
import abc
from reprlib import recursive_repr
lazy import copy
lazy import re
__all__ = ['dataclass',
@@ -178,21 +177,16 @@ class _HAS_DEFAULT_FACTORY_CLASS:
return '<factory>'
_HAS_DEFAULT_FACTORY = _HAS_DEFAULT_FACTORY_CLASS()
# A sentinel object to detect if a parameter is supplied or not. Use
# a class to give it a better repr.
class _MISSING_TYPE:
pass
MISSING = _MISSING_TYPE()
# A sentinel object to detect if a parameter is supplied or not.
MISSING = sentinel("MISSING")
# A sentinel object to indicate that following fields are keyword-only by
# default. Use a class to give it a better repr.
class _KW_ONLY_TYPE:
pass
KW_ONLY = _KW_ONLY_TYPE()
# default.
KW_ONLY = sentinel("KW_ONLY")
# Since most per-field metadata will be unused, create an empty
# read-only proxy that can be shared among all fields.
_EMPTY_METADATA = types.MappingProxyType({})
# read-only dictionary that can be shared among all fields.
_EMPTY_METADATA = frozendict()
# Markers for the various kinds of fields and pseudo-fields.
class _FIELD_BASE:
@@ -217,9 +211,8 @@ _PARAMS = '__dataclass_params__'
_POST_INIT_NAME = '__post_init__'
# String regex that string annotations for ClassVar or InitVar must match.
# Allows "identifier.identifier[" or "identifier[".
# https://bugs.python.org/issue33453 for details.
_MODULE_IDENTIFIER_RE = re.compile(r'^(?:\s*(\w+)\s*\.)?\s*(\w+)')
# This regular expression is compiled on demand so that 're' module can be imported lazily
_MODULE_IDENTIFIER_RE = None
# Atomic immutable types which don't require any recursive handling and for which deepcopy
# returns the same object. We can provide a fast-path for these types in asdict and astuple.
@@ -763,22 +756,16 @@ def _is_kw_only(a_type, dataclasses):
return a_type is dataclasses.KW_ONLY
def _is_type(annotation, cls, a_module, a_type, is_type_predicate):
# Given a type annotation string, does it refer to a_type in
# a_module? For example, when checking that annotation denotes a
# ClassVar, then a_module is typing, and a_type is
# typing.ClassVar.
def _get_type_from_annotation(annotation, cls):
# Loosely parse a string annotation and return its type.
# It's possible to look up a_module given a_type, but it involves
# looking in sys.modules (again!), and seems like a waste since
# the caller already knows a_module.
# We can't perform a full type hint evaluation at the point where @dataclass
# was invoked because class's module is not fully initialized yet. So we resort
# to parsing string annotation using regexp, and extracting a type before
# the first square bracket.
# - annotation is a string type annotation
# - cls is the class that this annotation was found in
# - a_module is the module we want to match
# - a_type is the type in that module we want to match
# - is_type_predicate is a function called with (obj, a_module)
# that determines if obj is of the desired type.
# Since this test does not do a local namespace lookup (and
# instead only a module (global) lookup), there are some things it
@@ -804,22 +791,26 @@ def _is_type(annotation, cls, a_module, a_type, is_type_predicate):
# a eval() penalty for every single field of every dataclass
# that's defined. It was judged not worth it.
match = _MODULE_IDENTIFIER_RE.match(annotation)
if match:
ns = None
module_name = match.group(1)
if not module_name:
# No module name, assume the class's module did
# "from dataclasses import InitVar".
ns = sys.modules.get(cls.__module__).__dict__
else:
# Look up module_name in the class's module.
module = sys.modules.get(cls.__module__)
if module and module.__dict__.get(module_name) is a_module:
ns = sys.modules.get(a_type.__module__).__dict__
if ns and is_type_predicate(ns.get(match.group(2)), a_module):
return True
return False
# String regex that string annotations for ClassVar or InitVar must match.
# Allows "identifier.identifier[" or "identifier[".
# https://github.com/python/cpython/issues/77634 for details.
global _MODULE_IDENTIFIER_RE
if _MODULE_IDENTIFIER_RE is None:
_MODULE_IDENTIFIER_RE = re.compile(r'^\s*(\w+(?:\s*\.\s*\w+)*)')
match = _MODULE_IDENTIFIER_RE.prefixmatch(annotation)
if not match:
return None
# Note: _MODULE_IDENTIFIER_RE guarantees that path is non-empty
path = match[1].split(".")
root = sys.modules.get(cls.__module__)
for path_item in path:
root = getattr(root, path_item.strip(), None)
if root is None:
return None
return root
def _get_field(cls, a_name, a_type, default_kw_only):
@@ -857,6 +848,10 @@ def _get_field(cls, a_name, a_type, default_kw_only):
# is actually of the correct type.
# For the complete discussion, see https://bugs.python.org/issue33453
if isinstance(a_type, str):
a_type_annotation = _get_type_from_annotation(a_type, cls)
else:
a_type_annotation = a_type
# If typing has not been imported, then it's impossible for any
# annotation to be a ClassVar. So, only look for ClassVar if
@@ -864,10 +859,7 @@ def _get_field(cls, a_name, a_type, default_kw_only):
# module).
typing = sys.modules.get('typing')
if typing:
if (_is_classvar(a_type, typing)
or (isinstance(f.type, str)
and _is_type(f.type, cls, typing, typing.ClassVar,
_is_classvar))):
if _is_classvar(a_type_annotation, typing):
f._field_type = _FIELD_CLASSVAR
# If the type is InitVar, or if it's a matching string annotation,
@@ -876,10 +868,7 @@ def _get_field(cls, a_name, a_type, default_kw_only):
# The module we're checking against is the module we're
# currently in (dataclasses.py).
dataclasses = sys.modules[__name__]
if (_is_initvar(a_type, dataclasses)
or (isinstance(f.type, str)
and _is_type(f.type, cls, dataclasses, dataclasses.InitVar,
_is_initvar))):
if _is_initvar(a_type_annotation, dataclasses):
f._field_type = _FIELD_INITVAR
# Validations for individual fields. This is delayed until now,
@@ -982,6 +971,29 @@ _hash_action = {(False, False, False, False): None,
# See https://bugs.python.org/issue32929#msg312829 for an if-statement
# version of this table.
# A non-data descriptor to autogenerate class docstring
# from the signature of its __init__ method on demand.
# The primary reason is to be able to lazy import `inspect` module.
class _AutoDocstring:
def __get__(self, _obj, cls):
try:
# In some cases fetching a signature is not possible.
# But, we surely should not fail in this case.
import inspect
text_sig = str(inspect.signature(
cls,
annotation_format=annotationlib.Format.FORWARDREF,
)).replace(' -> None', '')
except TypeError, ValueError:
text_sig = ''
doc = cls.__name__ + text_sig
setattr(cls, '__doc__', doc)
return doc
_auto_docstring = _AutoDocstring()
def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
match_args, kw_only, slots, weakref_slot):
@@ -1050,10 +1062,11 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
dataclasses = sys.modules[__name__]
for name, type in cls_annotations.items():
# See if this is a marker to change the value of kw_only.
if (_is_kw_only(type, dataclasses)
or (isinstance(type, str)
and _is_type(type, cls, dataclasses, dataclasses.KW_ONLY,
_is_kw_only))):
if isinstance(type, str):
a_type_annotation = _get_type_from_annotation(type, cls)
else:
a_type_annotation = type
if _is_kw_only(a_type_annotation, dataclasses):
# Switch the default to kw_only=True, and ignore this
# annotation: it's not a real field.
if KW_ONLY_seen:
@@ -1209,23 +1222,13 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
if hash_action:
cls.__hash__ = hash_action(cls, field_list, func_builder)
# Generate the methods and add them to the class. This needs to be done
# before the __doc__ logic below, since inspect will look at the __init__
# signature.
# Generate the methods and add them to the class.
func_builder.add_fns_to_class(cls)
if not getattr(cls, '__doc__'):
# Create a class doc-string.
try:
# In some cases fetching a signature is not possible.
# But, we surely should not fail in this case.
text_sig = str(inspect.signature(
cls,
annotation_format=annotationlib.Format.FORWARDREF,
)).replace(' -> None', '')
except (TypeError, ValueError):
text_sig = ''
cls.__doc__ = (cls.__name__ + text_sig)
# Create a class doc-string lazily via descriptor protocol
# to avoid importing `inspect` module.
cls.__doc__ = _auto_docstring
if match_args:
# I could probably compute this once.
@@ -1344,10 +1347,6 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields):
if '__slots__' in cls.__dict__:
raise TypeError(f'{cls.__name__} already specifies __slots__')
# gh-102069: Remove existing __weakref__ descriptor.
# gh-135228: Make sure the original class can be garbage collected.
sys._clear_type_descriptors(cls)
# Create a new dict for our new class.
cls_dict = dict(cls.__dict__)
field_names = tuple(f.name for f in fields(cls))
@@ -1365,6 +1364,11 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields):
# available in _MARKER.
cls_dict.pop(field_name, None)
# Remove __dict__ and `__weakref__` descriptors.
# They'll be added back if applicable.
cls_dict.pop('__dict__', None)
cls_dict.pop('__weakref__', None) # gh-102069
# And finally create the class.
qualname = getattr(cls, '__qualname__', None)
newcls = type(cls)(cls.__name__, cls.__bases__, cls_dict)
@@ -1385,8 +1389,11 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields):
# make an update, since all closures for a class will share a
# given cell.
for member in newcls.__dict__.values():
# If this is a wrapped function, unwrap it.
member = inspect.unwrap(member)
if not isinstance(member, type) and hasattr(member, '__wrapped__'):
import inspect
member = inspect.unwrap(member)
if isinstance(member, types.FunctionType):
if _update_func_cell_for__class__(member, cls, newcls):
@@ -1498,7 +1505,8 @@ def asdict(obj, *, dict_factory=dict):
If given, 'dict_factory' will be used instead of built-in dict.
The function applies recursively to field values that are
dataclass instances. This will also look into built-in containers:
tuples, lists, and dicts. Other objects are copied with 'copy.deepcopy()'.
tuples, lists, dicts, and frozendicts. Other objects are copied
with 'copy.deepcopy()'.
"""
if not _is_dataclass_instance(obj):
raise TypeError("asdict() should be called on dataclass instances")
@@ -1554,7 +1562,7 @@ def _asdict_inner(obj, dict_factory):
return obj_type(*[_asdict_inner(v, dict_factory) for v in obj])
else:
return obj_type(_asdict_inner(v, dict_factory) for v in obj)
elif issubclass(obj_type, dict):
elif issubclass(obj_type, (dict, frozendict)):
if hasattr(obj_type, 'default_factory'):
# obj is a defaultdict, which has a different constructor from
# dict as it requires the default_factory as its first arg.
@@ -1589,7 +1597,8 @@ def astuple(obj, *, tuple_factory=tuple):
If given, 'tuple_factory' will be used instead of built-in tuple.
The function applies recursively to field values that are
dataclass instances. This will also look into built-in containers:
tuples, lists, and dicts. Other objects are copied with 'copy.deepcopy()'.
tuples, lists, dicts, and frozendicts. Other objects are copied
with 'copy.deepcopy()'.
"""
if not _is_dataclass_instance(obj):
@@ -1618,7 +1627,7 @@ def _astuple_inner(obj, tuple_factory):
# generator (which is not true for namedtuples, handled
# above).
return type(obj)(_astuple_inner(v, tuple_factory) for v in obj)
elif isinstance(obj, dict):
elif isinstance(obj, (dict, frozendict)):
obj_type = type(obj)
if hasattr(obj_type, 'default_factory'):
# obj is a defaultdict, which has a different constructor from
+34 -20
View File
@@ -85,17 +85,18 @@ resulting RE will match the second character.
\\ Matches a literal backslash.
This module exports the following functions:
match Match a regular expression pattern to the beginning of a string.
fullmatch Match a regular expression pattern to all of a string.
search Search a string for the presence of a pattern.
sub Substitute occurrences of a pattern found in a string.
subn Same as sub, but also return the number of substitutions made.
split Split a string by the occurrences of a pattern.
findall Find all occurrences of a pattern in a string.
finditer Return an iterator yielding a Match object for each match.
compile Compile a pattern into a Pattern object.
purge Clear the regular expression cache.
escape Backslash all non-alphanumerics in a string.
prefixmatch Match a regular expression pattern to the beginning of a str.
match The original name of prefixmatch prior to 3.15.
fullmatch Match a regular expression pattern to all of a string.
search Search a string for the presence of a pattern.
sub Substitute occurrences of a pattern found in a string.
subn Same as sub, but also return the number of substitutions made.
split Split a string by the occurrences of a pattern.
findall Find all occurrences of a pattern in a string.
finditer Return an iterator yielding a Match object for each match.
compile Compile a pattern into a Pattern object.
purge Clear the regular expression cache.
escape Backslash all non-alphanumerics in a string.
Each function other than purge and escape can take an optional 'flags' argument
consisting of one or more of the following module constants, joined by "|".
@@ -130,15 +131,13 @@ import _sre
# public symbols
__all__ = [
"match", "fullmatch", "search", "sub", "subn", "split",
"prefixmatch", "match", "fullmatch", "search", "sub", "subn", "split",
"findall", "finditer", "compile", "purge", "escape",
"error", "Pattern", "Match", "A", "I", "L", "M", "S", "X", "U",
"ASCII", "IGNORECASE", "LOCALE", "MULTILINE", "DOTALL", "VERBOSE",
"UNICODE", "NOFLAG", "RegexFlag", "PatternError"
]
__version__ = "2.2.1"
@enum.global_enum
@enum._simple_enum(enum.IntFlag, boundary=enum.KEEP)
class RegexFlag:
@@ -161,10 +160,13 @@ PatternError = error = _compiler.PatternError
# --------------------------------------------------------------------
# public interface
def match(pattern, string, flags=0):
def prefixmatch(pattern, string, flags=0):
"""Try to apply the pattern at the start of the string, returning
a Match object, or None if no match was found."""
return _compile(pattern, flags).match(string)
return _compile(pattern, flags).prefixmatch(string)
# Our original name which was less explicitly clear about the behavior for prefixmatch.
match = prefixmatch
def fullmatch(pattern, string, flags=0):
"""Try to apply the pattern to all of the string, returning
@@ -313,7 +315,7 @@ def escape(pattern):
return pattern.translate(_special_chars_map).encode('latin1')
Pattern = type(_compiler.compile('', 0))
Match = type(_compiler.compile('', 0).match(''))
Match = type(_compiler.compile('', 0).prefixmatch(''))
# --------------------------------------------------------------------
# internals
@@ -399,9 +401,12 @@ class Scanner:
s = _parser.State()
s.flags = flags
for phrase, action in lexicon:
sub_pattern = _parser.parse(phrase, flags)
if sub_pattern.state.groups != 1:
raise ValueError("Cannot use capturing groups in re.Scanner")
gid = s.opengroup()
p.append(_parser.SubPattern(s, [
(SUBPATTERN, (gid, 0, 0, _parser.parse(phrase, flags))),
(SUBPATTERN, (gid, 0, 0, sub_pattern)),
]))
s.closegroup(gid, p[-1])
p = _parser.SubPattern(s, [(BRANCH, (None, p))])
@@ -409,10 +414,10 @@ class Scanner:
def scan(self, string):
result = []
append = result.append
match = self.scanner.scanner(string).match
_match = self.scanner.scanner(string).prefixmatch
i = 0
while True:
m = match()
m = _match()
if not m:
break
j = m.end()
@@ -426,3 +431,12 @@ class Scanner:
append(action)
i = j
return result, string[i:]
def __getattr__(name):
if name == "__version__":
from warnings import _deprecated
_deprecated("__version__", remove=(3, 20))
return "2.2.1" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+1 -1
View File
@@ -375,7 +375,7 @@ def _optimize_charset(charset, iscased=None, fixup=None, fixes=None):
# less significant byte is a bit index in the chunk (just like the
# CHARSET matching).
charmap = bytes(charmap) # should be hashable
charmap = charmap.take_bytes() # should be hashable
comps = {}
mapping = bytearray(256)
block = 0
-1
View File
@@ -455,7 +455,6 @@ def _parse_sub(source, state, verbose, nested):
items = []
itemsappend = items.append
sourcematch = source.match
start = source.tell()
while True:
itemsappend(_parse(source, state, verbose, nested + 1,
not nested and not items))
+62 -9
View File
@@ -27,11 +27,21 @@ import typing # Needed for the string "typing.ClassVar[int]" to work as an
import dataclasses # Needed for the string "dataclasses.InitVar[int]" to work as an annotation.
from test import support
from test.support import import_helper
from test.support import cpython_only, import_helper
# Just any custom exception we can catch.
class CustomError(Exception): pass
class TestImportTime(unittest.TestCase):
@cpython_only
def test_lazy_import(self):
import_helper.ensure_lazy_imports(
"dataclasses", {"inspect", "re", "copy"}
)
class TestCase(unittest.TestCase):
def test_no_fields(self):
@dataclass
@@ -71,7 +81,7 @@ class TestCase(unittest.TestCase):
expected_output = "Field(name='id',type=None," \
f"default=1,default_factory={MISSING!r}," \
"init=True,repr=False,hash=None," \
"compare=True,metadata=mappingproxy({})," \
"compare=True,metadata=frozendict()," \
f"kw_only={MISSING!r}," \
"doc='Docstring'," \
"_field_type=None)"
@@ -985,7 +995,7 @@ class TestCase(unittest.TestCase):
self.assertNotIn('x', D.__dict__)
def test_missing_repr(self):
self.assertIn('MISSING_TYPE object', repr(MISSING))
self.assertEqual(repr(MISSING), 'MISSING')
def test_dont_include_other_annotations(self):
@dataclass
@@ -1693,17 +1703,24 @@ class TestCase(unittest.TestCase):
class GroupDict:
id: int
users: Dict[str, User]
@dataclass
class GroupFrozenDict:
id: int
users: frozendict[str, User]
a = User('Alice', 1)
b = User('Bob', 2)
gl = GroupList(0, [a, b])
gt = GroupTuple(0, (a, b))
gd = GroupDict(0, {'first': a, 'second': b})
gfd = GroupFrozenDict(0, frozendict({'first': a, 'second': b}))
self.assertEqual(asdict(gl), {'id': 0, 'users': [{'name': 'Alice', 'id': 1},
{'name': 'Bob', 'id': 2}]})
self.assertEqual(asdict(gt), {'id': 0, 'users': ({'name': 'Alice', 'id': 1},
{'name': 'Bob', 'id': 2})})
self.assertEqual(asdict(gd), {'id': 0, 'users': {'first': {'name': 'Alice', 'id': 1},
'second': {'name': 'Bob', 'id': 2}}})
expected_dict = {'id': 0, 'users': {'first': {'name': 'Alice', 'id': 1},
'second': {'name': 'Bob', 'id': 2}}}
self.assertEqual(asdict(gd), expected_dict)
self.assertEqual(asdict(gfd), expected_dict)
def test_helper_asdict_builtin_object_containers(self):
@dataclass
@@ -1884,14 +1901,21 @@ class TestCase(unittest.TestCase):
class GroupDict:
id: int
users: Dict[str, User]
@dataclass
class GroupFrozenDict:
id: int
users: frozendict[str, User]
a = User('Alice', 1)
b = User('Bob', 2)
gl = GroupList(0, [a, b])
gt = GroupTuple(0, (a, b))
gd = GroupDict(0, {'first': a, 'second': b})
gfd = GroupFrozenDict(0, frozendict({'first': a, 'second': b}))
self.assertEqual(astuple(gl), (0, [('Alice', 1), ('Bob', 2)]))
self.assertEqual(astuple(gt), (0, (('Alice', 1), ('Bob', 2))))
self.assertEqual(astuple(gd), (0, {'first': ('Alice', 1), 'second': ('Bob', 2)}))
d = {'first': ('Alice', 1), 'second': ('Bob', 2)}
self.assertEqual(astuple(gd), (0, d))
self.assertEqual(astuple(gfd), (0, frozendict(d)))
def test_helper_astuple_builtin_object_containers(self):
@dataclass
@@ -2295,6 +2319,20 @@ class TestDocString(unittest.TestCase):
self.assertDocStrEqual(C.__doc__, "C()")
def test_docstring_slotted(self):
@dataclass(slots=True)
class C:
x: int
self.assertDocStrEqual(C.__doc__, "C(x:int)")
def test_docstring_recursive(self):
@dataclass()
class C:
x: list[C]
self.assertDocStrEqual(C.__doc__, "C(x:list[test.test_dataclasses.TestDocString.test_docstring_recursive.<locals>.C])")
def test_docstring_one_field(self):
@dataclass
class C:
@@ -4444,10 +4482,17 @@ class TestStringAnnotations(unittest.TestCase):
from test.test_dataclasses import dataclass_module_1_str
from test.test_dataclasses import dataclass_module_2
from test.test_dataclasses import dataclass_module_2_str
from test.test_dataclasses import dataclass_module_3
from test.test_dataclasses import dataclass_module_3_str
from test.test_dataclasses import dataclass_module_4
from test.test_dataclasses import dataclass_module_4_str
for m in (dataclass_module_1, dataclass_module_1_str,
dataclass_module_2, dataclass_module_2_str,
):
for m in (
dataclass_module_1, dataclass_module_1_str,
dataclass_module_2, dataclass_module_2_str,
dataclass_module_3, dataclass_module_3_str,
dataclass_module_4, dataclass_module_4_str,
):
with self.subTest(m=m):
# There's a difference in how the ClassVars are
# interpreted when using string annotations or
@@ -4751,6 +4796,14 @@ class TestMakeDataclass(unittest.TestCase):
self.assertEqual(c.x, 10)
self.assertEqual(c.__custom__, True)
def test_empty_annotation_string(self):
@dataclass
class DataclassWithEmptyTypeAnnotation:
x: ""
c = DataclassWithEmptyTypeAnnotation(10)
self.assertEqual(c.x, 10)
class TestReplace(unittest.TestCase):
def test(self):
+8
View File
@@ -0,0 +1,8 @@
# We need this to test a case when a type
# is imported via some other package,
# like ClassVar from typing_extensions instead of typing.
# https://github.com/python/cpython/issues/133956
from typing import ClassVar
from dataclasses import InitVar
__all__ = ["ClassVar", "InitVar"]
+32
View File
@@ -0,0 +1,32 @@
#from __future__ import annotations
USING_STRINGS = False
# dataclass_module_3.py and dataclass_module_3_str.py are identical
# except only the latter uses string annotations.
from dataclasses import dataclass
import test.test_dataclasses._types_proxy as tp
T_CV2 = tp.ClassVar[int]
T_CV3 = tp.ClassVar
T_IV2 = tp.InitVar[int]
T_IV3 = tp.InitVar
@dataclass
class CV:
T_CV4 = tp.ClassVar
cv0: tp.ClassVar[int] = 20
cv1: tp.ClassVar = 30
cv2: T_CV2
cv3: T_CV3
not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar.
@dataclass
class IV:
T_IV4 = tp.InitVar
iv0: tp.InitVar[int]
iv1: tp.InitVar
iv2: T_IV2
iv3: T_IV3
not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar.
+32
View File
@@ -0,0 +1,32 @@
from __future__ import annotations
USING_STRINGS = True
# dataclass_module_3.py and dataclass_module_3_str.py are identical
# except only the latter uses string annotations.
from dataclasses import dataclass
import test.test_dataclasses._types_proxy as tp
T_CV2 = tp.ClassVar[int]
T_CV3 = tp.ClassVar
T_IV2 = tp.InitVar[int]
T_IV3 = tp.InitVar
@dataclass
class CV:
T_CV4 = tp.ClassVar
cv0: tp.ClassVar[int] = 20
cv1: tp.ClassVar = 30
cv2: T_CV2
cv3: T_CV3
not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar.
@dataclass
class IV:
T_IV4 = tp.InitVar
iv0: tp.InitVar[int]
iv1: tp.InitVar
iv2: T_IV2
iv3: T_IV3
not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar.
+38
View File
@@ -0,0 +1,38 @@
#from __future__ import annotations
USING_STRINGS = False
# dataclass_module_4.py and dataclass_module_4_str.py are identical
# except only the latter uses string annotations.
from dataclasses import dataclass
import dataclasses
import typing
class TypingProxy:
class Nested:
ClassVar = typing.ClassVar
InitVar = dataclasses.InitVar
T_CV2 = TypingProxy.Nested.ClassVar[int]
T_CV3 = TypingProxy.Nested.ClassVar
T_IV2 = TypingProxy.Nested.InitVar[int]
T_IV3 = TypingProxy.Nested.InitVar
@dataclass
class CV:
T_CV4 = TypingProxy.Nested.ClassVar
cv0: TypingProxy.Nested.ClassVar[int] = 20
cv1: TypingProxy.Nested.ClassVar = 30
cv2: T_CV2
cv3: T_CV3
not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar.
@dataclass
class IV:
T_IV4 = TypingProxy.Nested.InitVar
iv0: TypingProxy.Nested.InitVar[int]
iv1: TypingProxy.Nested.InitVar
iv2: T_IV2
iv3: T_IV3
not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar.
+38
View File
@@ -0,0 +1,38 @@
from __future__ import annotations
USING_STRINGS = True
# dataclass_module_4.py and dataclass_module_4_str.py are identical
# except only the latter uses string annotations.
from dataclasses import dataclass
import dataclasses
import typing
class TypingProxy:
class Nested:
ClassVar = typing.ClassVar
InitVar = dataclasses.InitVar
T_CV2 = TypingProxy.Nested.ClassVar[int]
T_CV3 = TypingProxy.Nested.ClassVar
T_IV2 = TypingProxy.Nested.InitVar[int]
T_IV3 = TypingProxy.Nested.InitVar
@dataclass
class CV:
T_CV4 = TypingProxy.Nested.ClassVar
cv0: TypingProxy.Nested.ClassVar[int] = 20
cv1: TypingProxy.Nested.ClassVar = 30
cv2: T_CV2
cv3: T_CV3
not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar.
@dataclass
class IV:
T_IV4 = TypingProxy.Nested.InitVar
iv0: TypingProxy.Nested.InitVar[int]
iv1: TypingProxy.Nested.InitVar
iv2: T_IV2
iv3: T_IV3
not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar.
+46 -33
View File
@@ -91,10 +91,13 @@ class ReTests(unittest.TestCase):
self.assertEqual(re.search('x+', 'axx').span(), (1, 3))
self.assertIsNone(re.search('x', 'aaa'))
self.assertEqual(re.match('a*', 'xxx').span(0), (0, 0))
self.assertEqual(re.prefixmatch('a*', 'xxx').span(0), (0, 0))
self.assertEqual(re.match('a*', 'xxx').span(), (0, 0))
self.assertEqual(re.match('x*', 'xxxa').span(0), (0, 3))
self.assertEqual(re.prefixmatch('x*', 'xxxa').span(0), (0, 3))
self.assertEqual(re.match('x*', 'xxxa').span(), (0, 3))
self.assertIsNone(re.match('a+', 'xxx'))
self.assertIsNone(re.prefixmatch('a+', 'xxx'))
def test_branching(self):
"""Test Branching
@@ -181,6 +184,7 @@ class ReTests(unittest.TestCase):
def test_bug_1661(self):
# Verify that flags do not get silently ignored with compiled patterns
pattern = re.compile('.')
self.assertRaises(ValueError, re.prefixmatch, pattern, 'A', re.I)
self.assertRaises(ValueError, re.match, pattern, 'A', re.I)
self.assertRaises(ValueError, re.search, pattern, 'A', re.I)
self.assertRaises(ValueError, re.findall, pattern, 'A', re.I)
@@ -518,6 +522,8 @@ class ReTests(unittest.TestCase):
self.assertEqual(re.match(b'(a)', string).group(0), b'a')
self.assertEqual(re.match(b'(a)', string).group(1), b'a')
self.assertEqual(re.match(b'(a)', string).group(1, 1), (b'a', b'a'))
self.assertEqual(re.prefixmatch(b'(a)', string).group(1, 1),
(b'a', b'a'))
for a in ("\xe0", "\u0430", "\U0001d49c"):
self.assertEqual(re.match(a, a).groups(), ())
self.assertEqual(re.match('(%s)' % a, a).groups(), (a,))
@@ -559,10 +565,8 @@ class ReTests(unittest.TestCase):
self.assertEqual(m.group(2, 1), ('b', 'a'))
self.assertEqual(m.group(Index(2), Index(1)), ('b', 'a'))
def test_match_getitem(self):
pat = re.compile('(?:(?P<a1>a)|(?P<b2>b))(?P<c3>c)?')
m = pat.match('a')
def do_test_match_getitem(self, match_fn):
m = match_fn('a')
self.assertEqual(m['a1'], 'a')
self.assertEqual(m['b2'], None)
self.assertEqual(m['c3'], None)
@@ -586,7 +590,7 @@ class ReTests(unittest.TestCase):
with self.assertRaisesRegex(IndexError, 'no such group'):
'a1={a2}'.format_map(m)
m = pat.match('ac')
m = match_fn('ac')
self.assertEqual(m['a1'], 'a')
self.assertEqual(m['b2'], None)
self.assertEqual(m['c3'], 'c')
@@ -603,6 +607,14 @@ class ReTests(unittest.TestCase):
# No len().
self.assertRaises(TypeError, len, m)
def test_match_getitem(self):
pat = re.compile('(?:(?P<a1>a)|(?P<b2>b))(?P<c3>c)?')
self.do_test_match_getitem(pat.match)
def test_prefixmatch_getitem(self):
pat = re.compile('(?:(?P<a1>a)|(?P<b2>b))(?P<c3>c)?')
self.do_test_match_getitem(pat.prefixmatch)
def test_re_fullmatch(self):
# Issue 16203: Proposal: add re.fullmatch() method.
self.assertEqual(re.fullmatch(r"a", "a").span(), (0, 1))
@@ -1641,6 +1653,24 @@ class ReTests(unittest.TestCase):
(['sum', 'op=', 3, 'op*', 'foo', 'op+', 312.5,
'op+', 'bar'], ''))
def test_bug_gh140797(self):
# gh140797: Capturing groups are not allowed in re.Scanner
msg = r"Cannot use capturing groups in re\.Scanner"
# Capturing group throws an error
with self.assertRaisesRegex(ValueError, msg):
Scanner([("(a)b", None)])
# Named Group
with self.assertRaisesRegex(ValueError, msg):
Scanner([("(?P<name>a)", None)])
# Non-capturing groups should pass normally
s = Scanner([("(?:a)b", lambda scanner, token: token)])
result, rem = s.scan("ab")
self.assertEqual(result,['ab'])
self.assertEqual(rem,'')
def test_bug_448951(self):
# bug 448951 (similar to 429357, but with single char match)
# (Also test greedy matches.)
@@ -2028,7 +2058,7 @@ class ReTests(unittest.TestCase):
# The huge memuse is because of re.sub() using a list and a join()
# to create the replacement result.
@bigmemtest(size=_2G, memuse=16 + 2)
@bigmemtest(size=_2G, memuse=16 + 3)
def test_large_subn(self, size):
# Issue #10182: indices were 32-bit-truncated.
s = 'a' * size
@@ -2938,33 +2968,6 @@ class ImplementationTest(unittest.TestCase):
pat = re.compile("")
check_disallow_instantiation(self, type(pat.scanner("")))
def test_deprecated_modules(self):
deprecated = {
'sre_compile': ['compile', 'error',
'SRE_FLAG_IGNORECASE', 'SUBPATTERN',
'_compile_info'],
'sre_constants': ['error', 'SRE_FLAG_IGNORECASE', 'SUBPATTERN',
'_NamedIntConstant'],
'sre_parse': ['SubPattern', 'parse',
'SRE_FLAG_IGNORECASE', 'SUBPATTERN',
'_parse_sub'],
}
for name in deprecated:
with self.subTest(module=name):
sys.modules.pop(name, None)
with self.assertWarns(DeprecationWarning) as w:
__import__(name)
self.assertEqual(str(w.warning),
f"module {name!r} is deprecated")
self.assertEqual(w.filename, __file__)
self.assertIn(name, sys.modules)
mod = sys.modules[name]
self.assertEqual(mod.__name__, name)
self.assertEqual(mod.__package__, '')
for attr in deprecated[name]:
self.assertHasAttr(mod, attr)
del sys.modules[name]
@cpython_only
def test_case_helpers(self):
import _sre
@@ -3150,5 +3153,15 @@ class ExternalTests(unittest.TestCase):
self.assertTrue(obj.search(s))
class TestModule(unittest.TestCase):
def test_deprecated__version__(self):
with self.assertWarnsRegex(
DeprecationWarning,
"'__version__' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(re, "__version__")
self.assertEqual(cm.filename, __file__)
if __name__ == "__main__":
unittest.main()
+57 -1
View File
@@ -8,6 +8,11 @@ from typing import (
Callable, TypeAliasType, TypeVar, TypeVarTuple, ParamSpec, Unpack, get_args,
)
type GlobalTypeAlias = int
def get_type_alias():
type TypeAliasInFunc = str
return TypeAliasInFunc
class TypeParamsInvalidTest(unittest.TestCase):
def test_name_collisions(self):
@@ -70,6 +75,8 @@ class TypeParamsAccessTest(unittest.TestCase):
class TypeParamsAliasValueTest(unittest.TestCase):
type TypeAliasInClass = dict
def test_alias_value_01(self):
type TA1 = int
@@ -142,6 +149,38 @@ class TypeParamsAliasValueTest(unittest.TestCase):
self.assertIs(specialized2.__origin__, VeryGeneric)
self.assertEqual(specialized2.__args__, (int, str, float, [bool, range]))
def test___name__(self):
type TypeAliasLocal = GlobalTypeAlias
self.assertEqual(GlobalTypeAlias.__name__, 'GlobalTypeAlias')
self.assertEqual(get_type_alias().__name__, 'TypeAliasInFunc')
self.assertEqual(self.TypeAliasInClass.__name__, 'TypeAliasInClass')
self.assertEqual(TypeAliasLocal.__name__, 'TypeAliasLocal')
with self.assertRaisesRegex(
AttributeError,
"readonly attribute",
):
setattr(TypeAliasLocal, '__name__', 'TA')
def test___qualname__(self):
type TypeAliasLocal = GlobalTypeAlias
self.assertEqual(GlobalTypeAlias.__qualname__,
'GlobalTypeAlias')
self.assertEqual(get_type_alias().__qualname__,
'get_type_alias.<locals>.TypeAliasInFunc')
self.assertEqual(self.TypeAliasInClass.__qualname__,
'TypeParamsAliasValueTest.TypeAliasInClass')
self.assertEqual(TypeAliasLocal.__qualname__,
'TypeParamsAliasValueTest.test___qualname__.<locals>.TypeAliasLocal')
with self.assertRaisesRegex(
AttributeError,
"readonly attribute",
):
setattr(TypeAliasLocal, '__qualname__', 'TA')
def test_repr(self):
type Simple = int
self.assertEqual(repr(Simple), Simple.__qualname__)
@@ -195,15 +234,25 @@ class TypeAliasConstructorTest(unittest.TestCase):
def test_basic(self):
TA = TypeAliasType("TA", int)
self.assertEqual(TA.__name__, "TA")
self.assertEqual(TA.__qualname__, "TA")
self.assertIs(TA.__value__, int)
self.assertEqual(TA.__type_params__, ())
self.assertEqual(TA.__module__, __name__)
def test_with_qualname(self):
TA = TypeAliasType("TA", str, qualname="Class.TA")
self.assertEqual(TA.__name__, "TA")
self.assertEqual(TA.__qualname__, "Class.TA")
self.assertIs(TA.__value__, str)
self.assertEqual(TA.__type_params__, ())
self.assertEqual(TA.__module__, __name__)
def test_attributes_with_exec(self):
ns = {}
exec("type TA = int", ns, ns)
TA = ns["TA"]
self.assertEqual(TA.__name__, "TA")
self.assertEqual(TA.__qualname__, "TA")
self.assertIs(TA.__value__, int)
self.assertEqual(TA.__type_params__, ())
self.assertIs(TA.__module__, None)
@@ -212,6 +261,7 @@ class TypeAliasConstructorTest(unittest.TestCase):
T = TypeVar("T")
TA = TypeAliasType("TA", list[T], type_params=(T,))
self.assertEqual(TA.__name__, "TA")
self.assertEqual(TA.__qualname__, "TA")
self.assertEqual(TA.__value__, list[T])
self.assertEqual(TA.__type_params__, (T,))
self.assertEqual(TA.__module__, __name__)
@@ -220,6 +270,7 @@ class TypeAliasConstructorTest(unittest.TestCase):
def test_not_generic(self):
TA = TypeAliasType("TA", list[int], type_params=())
self.assertEqual(TA.__name__, "TA")
self.assertEqual(TA.__qualname__, "TA")
self.assertEqual(TA.__value__, list[int])
self.assertEqual(TA.__type_params__, ())
self.assertEqual(TA.__module__, __name__)
@@ -270,8 +321,9 @@ class TypeAliasConstructorTest(unittest.TestCase):
TypeAliasType("A", int, type_params=(T, 2))
def test_keywords(self):
TA = TypeAliasType(name="TA", value=int)
TA = TypeAliasType(name="TA", value=int, type_params=(), qualname=None)
self.assertEqual(TA.__name__, "TA")
self.assertEqual(TA.__qualname__, "TA")
self.assertIs(TA.__value__, int)
self.assertEqual(TA.__type_params__, ())
self.assertEqual(TA.__module__, __name__)
@@ -285,6 +337,8 @@ class TypeAliasConstructorTest(unittest.TestCase):
TypeAliasType("TA", list, ())
with self.assertRaises(TypeError):
TypeAliasType("TA", list, type_params=42)
with self.assertRaises(TypeError):
TypeAliasType("TA", list, qualname=range(5))
class TypeAliasTypeTest(unittest.TestCase):
@@ -318,6 +372,8 @@ class TypeAliasTypeTest(unittest.TestCase):
mod_generics_cache.__name__)
self.assertEqual(mod_generics_cache.OldStyle.__module__,
mod_generics_cache.__name__)
Alias.__module__ = "ham.spam.eggs"
self.assertEqual(Alias.__module__, "ham.spam.eggs")
def test_unpack(self):
type Alias = tuple[int, int]
+475 -170
View File
@@ -13,6 +13,7 @@ import os
import pickle
import re
import sys
import warnings
from unittest import TestCase, main
from unittest.mock import patch
from copy import copy, deepcopy
@@ -28,32 +29,31 @@ from typing import Generic, ClassVar, Final, final, Protocol
from typing import assert_type, cast, runtime_checkable
from typing import get_type_hints
from typing import get_origin, get_args, get_protocol_members
from typing import override
from typing import override, disjoint_base
from typing import is_typeddict, is_protocol
from typing import reveal_type
from typing import dataclass_transform
from typing import no_type_check, no_type_check_decorator
from typing import no_type_check
from typing import Type
from typing import NamedTuple, NotRequired, Required, ReadOnly, TypedDict
from typing import NamedTuple, NotRequired, Required, ReadOnly, TypedDict, NoExtraItems
from typing import IO, TextIO, BinaryIO
from typing import Pattern, Match
from typing import Annotated, ForwardRef
from typing import Self, LiteralString
from typing import TypeAlias
from typing import ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs
from typing import TypeGuard, TypeIs, NoDefault
from typing import TypeForm, TypeGuard, TypeIs, NoDefault
import abc
import textwrap
import typing
import weakref
import warnings
import types
from test.support import (
captured_stderr, cpython_only, infinite_recursion, requires_docstrings, import_helper, run_code,
EqualToForwardRef,
exceeds_recursion_limit, run_with_limited_c_stack,
skip_wasi_stack_overflow, skip_emscripten_stack_overflow,
captured_stderr, cpython_only, requires_docstrings, import_helper, run_code,
subTests, EqualToForwardRef,
exceeds_recursion_limit, skip_if_huge_c_stack, skip_wasi_stack_overflow,
skip_emscripten_stack_overflow,
)
from test.typinganndata import (
ann_module695, mod_generics_cache, _typed_dict_helper,
@@ -784,7 +784,7 @@ class TypeParameterDefaultsTests(BaseTestCase):
self.assertIs(U_None.__default__, None)
self.assertIs(U_None.has_default(), True)
class X[**Ts]: ...
class X[*Ts]: ...
Ts, = X.__type_params__
self.assertIs(Ts.__default__, NoDefault)
self.assertIs(Ts.has_default(), False)
@@ -1293,6 +1293,62 @@ class TypeVarTupleTests(BaseTestCase):
with self.assertRaises(TypeError):
Ts()
def test_default_variance(self):
Ts = TypeVarTuple('Ts')
self.assertIs(Ts.__covariant__, False)
self.assertIs(Ts.__contravariant__, False)
self.assertIs(Ts.__infer_variance__, False)
self.assertIsNone(Ts.__bound__)
def test_covariant(self):
Ts_co = TypeVarTuple('Ts_co', covariant=True)
self.assertIs(Ts_co.__covariant__, True)
self.assertIs(Ts_co.__contravariant__, False)
self.assertIs(Ts_co.__infer_variance__, False)
def test_contravariant(self):
Ts_contra = TypeVarTuple('Ts_contra', contravariant=True)
self.assertIs(Ts_contra.__covariant__, False)
self.assertIs(Ts_contra.__contravariant__, True)
self.assertIs(Ts_contra.__infer_variance__, False)
def test_infer_variance(self):
Ts = TypeVarTuple('Ts', infer_variance=True)
self.assertIs(Ts.__covariant__, False)
self.assertIs(Ts.__contravariant__, False)
self.assertIs(Ts.__infer_variance__, True)
def test_bound(self):
Ts_bound = TypeVarTuple('Ts_bound', bound=int)
self.assertIs(Ts_bound.__bound__, int)
Ts_tuple_bound = TypeVarTuple('Ts_tuple_bound', bound=(int, str))
self.assertEqual(Ts_tuple_bound.__bound__, (int, str))
obj = object()
Ts_object = TypeVarTuple('Ts_object', bound=obj)
self.assertIs(Ts_object.__bound__, obj)
Ts_no_bound = TypeVarTuple('Ts_no_bound')
self.assertIsNone(Ts_no_bound.__bound__)
def test_no_bivariant(self):
with self.assertRaises(ValueError):
TypeVarTuple('Ts', covariant=True, contravariant=True)
def test_cannot_combine_explicit_and_infer(self):
with self.assertRaises(ValueError):
TypeVarTuple('Ts', covariant=True, infer_variance=True)
with self.assertRaises(ValueError):
TypeVarTuple('Ts', contravariant=True, infer_variance=True)
def test_repr_with_variance(self):
Ts = TypeVarTuple('Ts')
self.assertEqual(repr(Ts), '~Ts')
Ts_co = TypeVarTuple('Ts_co', covariant=True)
self.assertEqual(repr(Ts_co), '+Ts_co')
Ts_contra = TypeVarTuple('Ts_contra', contravariant=True)
self.assertEqual(repr(Ts_contra), '-Ts_contra')
Ts_infer = TypeVarTuple('Ts_infer', infer_variance=True)
self.assertEqual(repr(Ts_infer), 'Ts_infer')
def test_unpacked_typevartuple_is_equal_to_itself(self):
Ts = TypeVarTuple('Ts')
self.assertEqual((*Ts,)[0], (*Ts,)[0])
@@ -1621,7 +1677,10 @@ class TypeVarTupleTests(BaseTestCase):
self.assertEqual(gth(func1), {'args': Unpack[Ts]})
def func2(*args: *tuple[int, str]): pass
self.assertEqual(gth(func2), {'args': Unpack[tuple[int, str]]})
hint = gth(func2)['args']
self.assertIsInstance(hint, types.GenericAlias)
self.assertEqual(hint.__args__[0], int)
self.assertIs(hint.__unpacked__, True)
class CustomVariadic(Generic[*Ts]): pass
@@ -1636,7 +1695,10 @@ class TypeVarTupleTests(BaseTestCase):
{'args': Unpack[Ts]})
def func2(*args: '*tuple[int, str]'): pass
self.assertEqual(gth(func2), {'args': Unpack[tuple[int, str]]})
hint = gth(func2)['args']
self.assertIsInstance(hint, types.GenericAlias)
self.assertEqual(hint.__args__[0], int)
self.assertIs(hint.__unpacked__, True)
class CustomVariadic(Generic[*Ts]): pass
@@ -3890,8 +3952,8 @@ class ProtocolTests(BaseTestCase):
self.assertIsNot(get_protocol_members(PR), P.__protocol_attrs__)
acceptable_extra_attrs = {
'_is_protocol', '_is_runtime_protocol', '__parameters__',
'__init__', '__annotations__', '__subclasshook__', '__annotate__',
'_is_protocol', '_is_runtime_protocol', '__typing_is_deprecated_inherited_runtime_protocol__',
'__parameters__', '__init__', '__annotations__', '__subclasshook__', '__annotate__',
'__annotations_cache__', '__annotate_func__',
}
self.assertLessEqual(vars(NonP).keys(), vars(C).keys() | acceptable_extra_attrs)
@@ -3983,6 +4045,7 @@ class ProtocolTests(BaseTestCase):
def test_defining_generic_protocols(self):
T = TypeVar('T')
T2 = TypeVar('T2')
S = TypeVar('S')
@runtime_checkable
@@ -3992,17 +4055,26 @@ class ProtocolTests(BaseTestCase):
class P(PR[int, T], Protocol[T]):
y = 1
self.assertEqual(P.__parameters__, (T,))
with self.assertRaises(TypeError):
PR[int]
with self.assertRaises(TypeError):
P[int, str]
with self.assertRaisesRegex(
TypeError,
re.escape('Some type variables (~S) are not listed in Protocol[~T, ~T2]'),
):
class ExtraTypeVars(P[S], Protocol[T, T2]): ...
class C(PR[int, T]): pass
self.assertEqual(C.__parameters__, (T,))
self.assertIsInstance(C[str](), C)
def test_defining_generic_protocols_old_style(self):
T = TypeVar('T')
T2 = TypeVar('T2')
S = TypeVar('S')
@runtime_checkable
@@ -4021,9 +4093,19 @@ class ProtocolTests(BaseTestCase):
class P1(Protocol, Generic[T]):
def bar(self, x: T) -> str: ...
self.assertEqual(P1.__parameters__, (T,))
class P2(Generic[T], Protocol):
def bar(self, x: T) -> str: ...
self.assertEqual(P2.__parameters__, (T,))
msg = re.escape('Some type variables (~S) are not listed in Protocol[~T, ~T2]')
with self.assertRaisesRegex(TypeError, msg):
class ExtraTypeVars(P1[S], Protocol[T, T2]): ...
with self.assertRaisesRegex(TypeError, msg):
class ExtraTypeVars(P2[S], Protocol[T, T2]): ...
@runtime_checkable
class PSub(P1[str], Protocol):
x = 1
@@ -4036,6 +4118,28 @@ class ProtocolTests(BaseTestCase):
self.assertIsInstance(Test(), PSub)
def test_protocol_parameter_order(self):
# https://github.com/python/cpython/issues/137191
T1 = TypeVar("T1")
T2 = TypeVar("T2", default=object)
class A(Protocol[T1]): ...
class B0(A[T2], Generic[T1, T2]): ...
self.assertEqual(B0.__parameters__, (T1, T2))
class B1(A[T2], Protocol, Generic[T1, T2]): ...
self.assertEqual(B1.__parameters__, (T1, T2))
class B2(A[T2], Protocol[T1, T2]): ...
self.assertEqual(B2.__parameters__, (T1, T2))
class B3[T1, T2](A[T2], Protocol):
@staticmethod
def get_typeparams():
return (T1, T2)
self.assertEqual(B3.__parameters__, B3.get_typeparams())
def test_pep695_generic_protocol_callable_members(self):
@runtime_checkable
class Foo[T](Protocol):
@@ -4421,6 +4525,70 @@ class ProtocolTests(BaseTestCase):
with self.assertRaisesRegex(TypeError, "@runtime_checkable"):
isinstance(1, P)
@subTests(['check_obj', 'check_func'], ([42, isinstance], [frozenset, issubclass]))
def test_inherited_runtime_protocol_deprecated(self, check_obj, check_func):
"""See GH-132604."""
class BareProto(Protocol):
"""I am not runtime-checkable."""
@runtime_checkable
class RCProto1(Protocol):
"""I am runtime-checkable."""
class InheritedRCProto1(RCProto1, Protocol):
"""I am accidentally runtime-checkable (by inheritance)."""
@runtime_checkable
class RCProto2(InheritedRCProto1, Protocol):
"""Explicit RC -> inherited RC -> explicit RC."""
def spam(self): ...
@runtime_checkable
class RCProto3(BareProto, Protocol):
"""Not RC -> explicit RC."""
class InheritedRCProto2(RCProto3, Protocol):
"""Not RC -> explicit RC -> inherited RC."""
def eggs(self): ...
class InheritedRCProto3(RCProto2, Protocol):
"""Explicit RC -> inherited RC -> explicit RC -> inherited RC."""
class Concrete1(BareProto):
pass
class Concrete2(InheritedRCProto2):
pass
class Concrete3(InheritedRCProto3):
pass
depr_message_re = (
r"<class .+\.InheritedRCProto\d'> isn't explicitly decorated "
r"with @runtime_checkable but it is used in issubclass\(\) or "
r"isinstance\(\). Instance and class checks can only be used with "
r"@runtime_checkable protocols. This will raise a TypeError in Python 3.20."
)
for inherited_runtime_proto in InheritedRCProto1, InheritedRCProto2, InheritedRCProto3:
with self.assertWarnsRegex(DeprecationWarning, depr_message_re):
check_func(check_obj, inherited_runtime_proto)
# Don't warn for explicitly checkable protocols and concrete implementations.
with warnings.catch_warnings():
warnings.simplefilter("error", DeprecationWarning)
for checkable in RCProto1, RCProto2, RCProto3, Concrete1, Concrete2, Concrete3:
check_func(check_obj, checkable)
# Don't warn for uncheckable protocols.
with warnings.catch_warnings():
warnings.simplefilter("error", DeprecationWarning)
with self.assertRaises(TypeError): # Self-test. Protocol below can't be runtime-checkable.
check_func(check_obj, BareProto)
def test_super_call_init(self):
class P(Protocol):
x: int
@@ -4932,7 +5100,7 @@ class GenericTests(BaseTestCase):
self.assertEqual(MM2.__bases__, (collections.abc.MutableMapping, Generic))
@cpython_only
@run_with_limited_c_stack()
@skip_if_huge_c_stack()
@skip_wasi_stack_overflow()
@skip_emscripten_stack_overflow()
def test_parameters_deep_recursion(self):
@@ -5821,6 +5989,7 @@ class GenericTests(BaseTestCase):
Final[int],
Literal[1, 2],
Concatenate[int, ParamSpec("P")],
TypeForm[int],
TypeGuard[int],
TypeIs[range],
):
@@ -5887,6 +6056,22 @@ class GenericTests(BaseTestCase):
with self.assertRaises(TypeError):
a[int]
def test_parameter_added_after_parameters_cached(self):
# gh-155752: GenericAlias parameters are cached before substitution, so
# an argument can gain __typing_subst__ after the tuple is calculated.
class Parameter:
pass
first = Parameter()
first.__typing_subst__ = lambda value: value
late = Parameter()
alias = types.GenericAlias(dict, (first, late))
self.assertEqual(alias.__parameters__, (first,))
late.__typing_subst__ = lambda value: value
with self.assertRaisesRegex(TypeError, "not found in __parameters__"):
alias[0]
def test_return_non_tuple_while_unpacking(self):
# GH-138497: GenericAlias objects didn't ensure that __typing_subst__ actually
# returned a tuple
@@ -6380,62 +6565,8 @@ class NoTypeCheckTests(BaseTestCase):
for clazz in [C, D, E, F]:
self.assertEqual(get_type_hints(clazz), expected_result)
def test_meta_no_type_check(self):
depr_msg = (
"'typing.no_type_check_decorator' is deprecated "
"and slated for removal in Python 3.15"
)
with self.assertWarnsRegex(DeprecationWarning, depr_msg):
@no_type_check_decorator
def magic_decorator(func):
return func
self.assertEqual(magic_decorator.__name__, 'magic_decorator')
@magic_decorator
def foo(a: 'whatevers') -> {}:
pass
@magic_decorator
class C:
def foo(a: 'whatevers') -> {}:
pass
self.assertEqual(foo.__name__, 'foo')
th = get_type_hints(foo)
self.assertEqual(th, {})
cth = get_type_hints(C.foo)
self.assertEqual(cth, {})
ith = get_type_hints(C().foo)
self.assertEqual(ith, {})
class InternalsTests(BaseTestCase):
def test_deprecation_for_no_type_params_passed_to__evaluate(self):
with self.assertWarnsRegex(
DeprecationWarning,
(
"Failing to pass a value to the 'type_params' parameter "
"of 'typing._eval_type' is deprecated"
)
) as cm:
self.assertEqual(typing._eval_type(list["int"], globals(), {}), list[int])
self.assertEqual(cm.filename, __file__)
f = ForwardRef("int")
with self.assertWarnsRegex(
DeprecationWarning,
(
"Failing to pass a value to the 'type_params' parameter "
"of 'typing.ForwardRef._evaluate' is deprecated"
)
) as cm:
self.assertIs(f._evaluate(globals(), {}, recursive_guard=frozenset()), int)
self.assertEqual(cm.filename, __file__)
def test_collect_parameters(self):
typing = import_helper.import_fresh_module("typing")
with self.assertWarnsRegex(
@@ -7236,6 +7367,24 @@ class GetTypeHintsTests(BaseTestCase):
right_hints = get_type_hints(t.add_right, globals(), locals())
self.assertEqual(right_hints['node'], Node[T])
def test_get_type_hints_preserve_generic_alias_subclasses(self):
# https://github.com/python/cpython/issues/130870
# A real world example of this is `collections.abc.Callable`. When parameterized,
# the result is a subclass of `types.GenericAlias`.
class MyAlias(types.GenericAlias):
pass
class MyClass:
def __class_getitem__(cls, args):
return MyAlias(cls, args)
# Using a forward reference is important, otherwise it works as expected.
# `y` tests that the `GenericAlias` subclass is preserved when stripping `Annotated`.
def func(x: MyClass['int'], y: MyClass[Annotated[int, ...]]): ...
assert isinstance(get_type_hints(func)['x'], MyAlias)
assert isinstance(get_type_hints(func)['y'], MyAlias)
def test_stringified_typeddict(self):
ns = run_code(
"""
@@ -7249,6 +7398,13 @@ class GetTypeHintsTests(BaseTestCase):
self.assertEqual(TD.__annotations__, {'a': EqualToForwardRef('UniqueT', owner=TD, module=TD.__module__)})
self.assertEqual(get_type_hints(TD), {'a': TD.__type_params__[0]})
def test_get_type_hints_order(self):
"""Ensure that the order of function annotations matches the order they're defined"""
def f(positional: int, /, normal: str, *args: bytes, kwarg: list, **kwargs: bool) -> tuple:
pass
self.assertEqual(list(gth(f)), ["positional", "normal", "args", "kwarg", "kwargs", "return"])
class GetUtilitiesTestCase(TestCase):
def test_get_origin(self):
@@ -7339,6 +7495,7 @@ class GetUtilitiesTestCase(TestCase):
self.assertEqual(get_args(Required[int]), (int,))
self.assertEqual(get_args(NotRequired[int]), (int,))
self.assertEqual(get_args(TypeAlias), ())
self.assertEqual(get_args(TypeForm[int]), (int,))
self.assertEqual(get_args(TypeGuard[int]), (int,))
self.assertEqual(get_args(TypeIs[range]), (range,))
Ts = TypeVarTuple('Ts')
@@ -7576,14 +7733,23 @@ class CollectionsAbcTests(BaseTestCase):
self.assertNotIsInstance((), typing.MutableSequence)
def test_bytestring(self):
previous_typing_module = sys.modules.pop("typing", None)
self.addCleanup(sys.modules.__setitem__, "typing", previous_typing_module)
with self.assertWarns(DeprecationWarning):
self.assertIsInstance(b'', typing.ByteString)
from typing import ByteString
with self.assertWarns(DeprecationWarning):
self.assertIsInstance(bytearray(b''), typing.ByteString)
self.assertIsInstance(b'', ByteString)
with self.assertWarns(DeprecationWarning):
class Foo(typing.ByteString): ...
self.assertIsInstance(bytearray(b''), ByteString)
with self.assertWarns(DeprecationWarning):
class Bar(typing.ByteString, typing.Awaitable): ...
self.assertIsSubclass(bytes, ByteString)
with self.assertWarns(DeprecationWarning):
self.assertIsSubclass(bytearray, ByteString)
with self.assertWarns(DeprecationWarning):
class Foo(ByteString): ...
with self.assertWarns(DeprecationWarning):
class Bar(ByteString, typing.Awaitable): ...
def test_list(self):
self.assertIsSubclass(list, typing.List)
@@ -8326,78 +8492,13 @@ class NamedTupleTests(BaseTestCase):
self.assertIs(type(a), Group)
self.assertEqual(a, (1, [2]))
def test_namedtuple_keyword_usage(self):
with self.assertWarnsRegex(
DeprecationWarning,
"Creating NamedTuple classes using keyword arguments is deprecated"
):
LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int)
nick = LocalEmployee('Nick', 25)
self.assertIsInstance(nick, tuple)
self.assertEqual(nick.name, 'Nick')
self.assertEqual(LocalEmployee.__name__, 'LocalEmployee')
self.assertEqual(LocalEmployee._fields, ('name', 'age'))
self.assertEqual(LocalEmployee.__annotations__, dict(name=str, age=int))
with self.assertRaisesRegex(
TypeError,
"Either list of fields or keywords can be provided to NamedTuple, not both"
):
NamedTuple('Name', [('x', int)], y=str)
with self.assertRaisesRegex(
TypeError,
"Either list of fields or keywords can be provided to NamedTuple, not both"
):
NamedTuple('Name', [], y=str)
with self.assertRaisesRegex(
TypeError,
(
r"Cannot pass `None` as the 'fields' parameter "
r"and also specify fields using keyword arguments"
)
):
NamedTuple('Name', None, x=int)
def test_namedtuple_special_keyword_names(self):
with self.assertWarnsRegex(
DeprecationWarning,
"Creating NamedTuple classes using keyword arguments is deprecated"
):
NT = NamedTuple("NT", cls=type, self=object, typename=str, fields=list)
self.assertEqual(NT.__name__, 'NT')
self.assertEqual(NT._fields, ('cls', 'self', 'typename', 'fields'))
a = NT(cls=str, self=42, typename='foo', fields=[('bar', tuple)])
self.assertEqual(a.cls, str)
self.assertEqual(a.self, 42)
self.assertEqual(a.typename, 'foo')
self.assertEqual(a.fields, [('bar', tuple)])
def test_empty_namedtuple(self):
expected_warning = re.escape(
"Failing to pass a value for the 'fields' parameter is deprecated "
"and will be disallowed in Python 3.15. "
"To create a NamedTuple class with 0 fields "
"using the functional syntax, "
"pass an empty list, e.g. `NT1 = NamedTuple('NT1', [])`."
)
with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
NT1 = NamedTuple('NT1')
with self.assertRaisesRegex(TypeError, "missing.*required.*argument"):
BAD = NamedTuple('BAD')
expected_warning = re.escape(
"Passing `None` as the 'fields' parameter is deprecated "
"and will be disallowed in Python 3.15. "
"To create a NamedTuple class with 0 fields "
"using the functional syntax, "
"pass an empty list, e.g. `NT2 = NamedTuple('NT2', [])`."
)
with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
NT2 = NamedTuple('NT2', None)
NT3 = NamedTuple('NT2', [])
NT1 = NamedTuple('NT1', {})
NT2 = NamedTuple('NT2', ())
NT3 = NamedTuple('NT3', [])
class CNT(NamedTuple):
pass # empty body
@@ -8412,16 +8513,18 @@ class NamedTupleTests(BaseTestCase):
def test_namedtuple_errors(self):
with self.assertRaises(TypeError):
NamedTuple.__new__()
with self.assertRaisesRegex(TypeError, "object is not iterable"):
NamedTuple('Name', None)
with self.assertRaisesRegex(
TypeError,
"missing 1 required positional argument"
"missing 2 required positional arguments"
):
NamedTuple()
with self.assertRaisesRegex(
TypeError,
"takes from 1 to 2 positional arguments but 3 were given"
"takes 2 positional arguments but 3 were given"
):
NamedTuple('Emp', [('name', str)], None)
@@ -8433,10 +8536,22 @@ class NamedTupleTests(BaseTestCase):
with self.assertRaisesRegex(
TypeError,
"missing 1 required positional argument: 'typename'"
"got some positional-only arguments passed as keyword arguments"
):
NamedTuple(typename='Emp', name=str, id=int)
with self.assertRaisesRegex(
TypeError,
"got an unexpected keyword argument"
):
NamedTuple('Name', [('x', int)], y=str)
with self.assertRaisesRegex(
TypeError,
"got an unexpected keyword argument"
):
NamedTuple('Name', [], y=str)
def test_copy_and_pickle(self):
global Emp # pickle wants to reference the class by name
Emp = NamedTuple('Emp', [('name', str), ('cool', int)])
@@ -8567,6 +8682,15 @@ class NamedTupleTests(BaseTestCase):
def name(self):
return __class__.__name__
def test_named_tuple_non_sequence_input(self):
field_names = ["x", "y"]
field_values = [int, int]
Point = NamedTuple("Point", zip(field_names, field_values))
p = Point(1, 2)
self.assertEqual(p.x, 1)
self.assertEqual(p.y, 2)
self.assertEqual(repr(p),"Point(x=1, y=2)")
class TypedDictTests(BaseTestCase):
def test_basics_functional_syntax(self):
@@ -8937,6 +9061,32 @@ class TypedDictTests(BaseTestCase):
class Wrong(*bases):
pass
def test_closed_values(self):
class Implicit(TypedDict): ...
class ExplicitTrue(TypedDict, closed=True): ...
class ExplicitFalse(TypedDict, closed=False): ...
self.assertIsNone(Implicit.__closed__)
self.assertIs(ExplicitTrue.__closed__, True)
self.assertIs(ExplicitFalse.__closed__, False)
def test_extra_items_class_arg(self):
class TD(TypedDict, extra_items=int):
a: str
self.assertIs(TD.__extra_items__, int)
self.assertEqual(TD.__annotations__, {'a': str})
self.assertEqual(TD.__required_keys__, frozenset({'a'}))
self.assertEqual(TD.__optional_keys__, frozenset())
class NoExtra(TypedDict):
a: str
self.assertIs(NoExtra.__extra_items__, NoExtraItems)
self.assertEqual(NoExtra.__annotations__, {'a': str})
self.assertEqual(NoExtra.__required_keys__, frozenset({'a'}))
self.assertEqual(NoExtra.__optional_keys__, frozenset())
def test_is_typeddict(self):
self.assertIs(is_typeddict(Point2D), True)
self.assertIs(is_typeddict(Union[str, int]), False)
@@ -9180,39 +9330,27 @@ class TypedDictTests(BaseTestCase):
self.assertEqual(CallTypedDict.__orig_bases__, (TypedDict,))
def test_zero_fields_typeddicts(self):
T1 = TypedDict("T1", {})
T1a = TypedDict("T1a", {})
T1b = TypedDict("T1b", [])
T1c = TypedDict("T1c", ())
class T2(TypedDict): pass
class T3[tvar](TypedDict): pass
S = TypeVar("S")
class T4(TypedDict, Generic[S]): pass
expected_warning = re.escape(
"Failing to pass a value for the 'fields' parameter is deprecated "
"and will be disallowed in Python 3.15. "
"To create a TypedDict class with 0 fields "
"using the functional syntax, "
"pass an empty dictionary, e.g. `T5 = TypedDict('T5', {})`."
)
with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
T5 = TypedDict('T5')
expected_warning = re.escape(
"Passing `None` as the 'fields' parameter is deprecated "
"and will be disallowed in Python 3.15. "
"To create a TypedDict class with 0 fields "
"using the functional syntax, "
"pass an empty dictionary, e.g. `T6 = TypedDict('T6', {})`."
)
with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
T6 = TypedDict('T6', None)
for klass in T1, T2, T3, T4, T5, T6:
for klass in T1a, T1b, T1c, T2, T3, T4:
with self.subTest(klass=klass.__name__):
self.assertEqual(klass.__annotations__, {})
self.assertEqual(klass.__required_keys__, set())
self.assertEqual(klass.__optional_keys__, set())
self.assertIsInstance(klass(), dict)
def test_errors(self):
with self.assertRaisesRegex(TypeError, "missing 1 required.*argument"):
TypedDict('TD')
with self.assertRaisesRegex(TypeError, "object is not iterable"):
TypedDict('TD', None)
def test_readonly_inheritance(self):
class Base1(TypedDict):
a: ReadOnly[int]
@@ -9276,6 +9414,71 @@ class TypedDictTests(BaseTestCase):
},
)
def test_closed_inheritance(self):
class Base(TypedDict, extra_items=ReadOnly[Union[str, None]]):
a: int
self.assertEqual(Base.__required_keys__, frozenset({"a"}))
self.assertEqual(Base.__optional_keys__, frozenset({}))
self.assertEqual(Base.__readonly_keys__, frozenset({}))
self.assertEqual(Base.__mutable_keys__, frozenset({"a"}))
self.assertEqual(Base.__annotations__, {"a": int})
self.assertEqual(Base.__extra_items__, ReadOnly[Union[str, None]])
self.assertIsNone(Base.__closed__)
class Child(Base, extra_items=int):
a: str
self.assertEqual(Child.__required_keys__, frozenset({'a'}))
self.assertEqual(Child.__optional_keys__, frozenset({}))
self.assertEqual(Child.__readonly_keys__, frozenset({}))
self.assertEqual(Child.__mutable_keys__, frozenset({'a'}))
self.assertEqual(Child.__annotations__, {"a": str})
self.assertIs(Child.__extra_items__, int)
self.assertIsNone(Child.__closed__)
class GrandChild(Child, closed=True):
a: float
self.assertEqual(GrandChild.__required_keys__, frozenset({'a'}))
self.assertEqual(GrandChild.__optional_keys__, frozenset({}))
self.assertEqual(GrandChild.__readonly_keys__, frozenset({}))
self.assertEqual(GrandChild.__mutable_keys__, frozenset({'a'}))
self.assertEqual(GrandChild.__annotations__, {"a": float})
self.assertIs(GrandChild.__extra_items__, NoExtraItems)
self.assertIs(GrandChild.__closed__, True)
class GrandGrandChild(GrandChild):
...
self.assertEqual(GrandGrandChild.__required_keys__, frozenset({'a'}))
self.assertEqual(GrandGrandChild.__optional_keys__, frozenset({}))
self.assertEqual(GrandGrandChild.__readonly_keys__, frozenset({}))
self.assertEqual(GrandGrandChild.__mutable_keys__, frozenset({'a'}))
self.assertEqual(GrandGrandChild.__annotations__, {"a": float})
self.assertIs(GrandGrandChild.__extra_items__, NoExtraItems)
self.assertIsNone(GrandGrandChild.__closed__)
def test_implicit_extra_items(self):
class Base(TypedDict):
a: int
self.assertIs(Base.__extra_items__, NoExtraItems)
self.assertIsNone(Base.__closed__)
class ChildA(Base, closed=True):
...
self.assertEqual(ChildA.__extra_items__, NoExtraItems)
self.assertIs(ChildA.__closed__, True)
def test_cannot_combine_closed_and_extra_items(self):
with self.assertRaisesRegex(
TypeError,
"Cannot combine closed=True and extra_items"
):
class TD(TypedDict, closed=True, extra_items=range):
x: str
def test_annotations(self):
# _type_check is applied
with self.assertRaisesRegex(TypeError, "Plain typing.Final is not valid as type argument"):
@@ -9505,6 +9708,12 @@ class RETests(BaseTestCase):
class B(typing.Pattern):
pass
def test_typed_dict_signature(self):
self.assertListEqual(
list(inspect.signature(TypedDict).parameters),
['typename', 'fields', 'total', 'closed', 'extra_items']
)
class AnnotatedTests(BaseTestCase):
@@ -10380,6 +10589,17 @@ class ParamSpecTests(BaseTestCase):
self.assertEqual(G2[[int, str], float], list[C])
self.assertEqual(G3[[int, str], float], list[C] | int)
def test_paramspec_bound(self):
P = ParamSpec('P', bound=[int, str])
self.assertEqual(P.__bound__, [int, str])
P2 = ParamSpec('P2', bound=(int, str))
self.assertEqual(P2.__bound__, (int, str))
obj = object()
P3 = ParamSpec('P3', bound=obj)
self.assertIs(P3.__bound__, obj)
P4 = ParamSpec('P4')
self.assertIs(P4.__bound__, None)
def test_paramspec_gets_copied(self):
# bpo-46581
P = ParamSpec('P')
@@ -10587,6 +10807,72 @@ class TypeIsTests(BaseTestCase):
issubclass(int, TypeIs)
class TypeFormTests(BaseTestCase):
def test_basics(self):
TypeForm[int] # OK
self.assertEqual(TypeForm[int], TypeForm[int])
def foo(arg) -> TypeForm[int]: ...
self.assertEqual(gth(foo), {'return': TypeForm[int]})
with self.assertRaises(TypeError):
TypeForm[int, str]
def test_repr(self):
self.assertEqual(repr(TypeForm), 'typing.TypeForm')
cv = TypeForm[int]
self.assertEqual(repr(cv), 'typing.TypeForm[int]')
cv = TypeForm[Employee]
self.assertEqual(repr(cv), 'typing.TypeForm[%s.Employee]' % __name__)
cv = TypeForm[tuple[int]]
self.assertEqual(repr(cv), 'typing.TypeForm[tuple[int]]')
def test_cannot_subclass(self):
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
class C(type(TypeForm)):
pass
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
class D(type(TypeForm[int])):
pass
with self.assertRaisesRegex(TypeError,
r'Cannot subclass typing\.TypeForm'):
class E(TypeForm):
pass
with self.assertRaisesRegex(TypeError,
r'Cannot subclass typing\.TypeForm\[int\]'):
class F(TypeForm[int]):
pass
def test_call(self):
objs = [
1,
"int",
int,
tuple[int, str],
Tuple[int, str],
]
for obj in objs:
with self.subTest(obj=obj):
self.assertIs(TypeForm(obj), obj)
with self.assertRaises(TypeError):
TypeForm()
with self.assertRaises(TypeError):
TypeForm("too", "many")
def test_cannot_init_type(self):
with self.assertRaises(TypeError):
type(TypeForm)()
with self.assertRaises(TypeError):
type(TypeForm[Optional[int]])()
def test_no_isinstance(self):
with self.assertRaises(TypeError):
isinstance(1, TypeForm[int])
with self.assertRaises(TypeError):
issubclass(int, TypeForm)
SpecialAttrsP = typing.ParamSpec('SpecialAttrsP')
SpecialAttrsT = typing.TypeVar('SpecialAttrsT', int, float, complex)
@@ -10594,6 +10880,10 @@ SpecialAttrsT = typing.TypeVar('SpecialAttrsT', int, float, complex)
class SpecialAttrsTests(BaseTestCase):
def test_special_attrs(self):
with warnings.catch_warnings(
action='ignore', category=DeprecationWarning
):
typing_ByteString = typing.ByteString
cls_to_check = {
# ABC classes
typing.AbstractSet: 'AbstractSet',
@@ -10602,7 +10892,7 @@ class SpecialAttrsTests(BaseTestCase):
typing.AsyncIterable: 'AsyncIterable',
typing.AsyncIterator: 'AsyncIterator',
typing.Awaitable: 'Awaitable',
typing.ByteString: 'ByteString',
typing_ByteString: 'ByteString',
typing.Callable: 'Callable',
typing.ChainMap: 'ChainMap',
typing.Collection: 'Collection',
@@ -10684,6 +10974,7 @@ class SpecialAttrsTests(BaseTestCase):
typing.Never: 'Never',
typing.Optional: 'Optional',
typing.TypeAlias: 'TypeAlias',
typing.TypeForm: 'TypeForm',
typing.TypeGuard: 'TypeGuard',
typing.TypeIs: 'TypeIs',
typing.TypeVar: 'TypeVar',
@@ -10698,6 +10989,7 @@ class SpecialAttrsTests(BaseTestCase):
typing.Literal[1, 2]: 'Literal',
typing.Literal[True, 2]: 'Literal',
typing.Optional[Any]: 'Union',
typing.TypeForm[Any]: 'TypeForm',
typing.TypeGuard[Any]: 'TypeGuard',
typing.TypeIs[Any]: 'TypeIs',
typing.Union[Any]: 'Any',
@@ -10780,6 +11072,18 @@ class SpecialAttrsTests(BaseTestCase):
self.assertNotIn('__magic__', dir_items)
class DisjointBaseTests(BaseTestCase):
def test_disjoint_base_unmodified(self):
class C: ...
self.assertIs(C, disjoint_base(C))
def test_dunder_disjoint_base(self):
@disjoint_base
class C: ...
self.assertIs(C.__disjoint_base__, True)
class RevealTypeTests(BaseTestCase):
def test_reveal_type(self):
obj = object()
@@ -10959,7 +11263,8 @@ class AllTests(BaseTestCase):
# there's a few types and metaclasses that aren't exported
not k.endswith(('Meta', '_contra', '_co')) and
not k.upper() == k and
# but export all things that have __module__ == 'typing'
k not in {"ByteString"} and
# but export all other things that have __module__ == 'typing'
getattr(v, '__module__', None) == typing.__name__
)
}
+262 -179
View File
@@ -28,6 +28,7 @@ import operator
import sys
import types
from types import GenericAlias
lazy import annotationlib
from _typing import (
_idfunc,
@@ -65,7 +66,6 @@ __all__ = [
# ABCs (from collections.abc).
'AbstractSet', # collections.abc.Set.
'ByteString',
'Container',
'ContextManager',
'Hashable',
@@ -127,6 +127,7 @@ __all__ = [
'cast',
'clear_overloads',
'dataclass_transform',
'disjoint_base',
'evaluate_forward_ref',
'final',
'get_args',
@@ -140,8 +141,8 @@ __all__ = [
'Never',
'NewType',
'no_type_check',
'no_type_check_decorator',
'NoDefault',
'NoExtraItems',
'NoReturn',
'NotRequired',
'overload',
@@ -156,21 +157,13 @@ __all__ = [
'Text',
'TYPE_CHECKING',
'TypeAlias',
'TypeForm',
'TypeGuard',
'TypeIs',
'TypeAliasType',
'Unpack',
]
class _LazyAnnotationLib:
def __getattr__(self, attr):
global _lazy_annotationlib
import annotationlib
_lazy_annotationlib = annotationlib
return getattr(annotationlib, attr)
_lazy_annotationlib = _LazyAnnotationLib()
def _type_convert(arg, module=None, *, allow_special_forms=False, owner=None):
"""For converting None to type(None), and strings to ForwardRef."""
@@ -254,19 +247,30 @@ def _type_repr(obj):
if isinstance(obj, tuple):
# Special case for `repr` of types with `ParamSpec`:
return '[' + ', '.join(_type_repr(t) for t in obj) + ']'
return _lazy_annotationlib.type_repr(obj)
return annotationlib.type_repr(obj)
def _collect_type_parameters(args, *, enforce_default_ordering: bool = True):
def _collect_type_parameters(
args,
*,
enforce_default_ordering: bool = True,
validate_all: bool = False,
):
"""Collect all type parameters in args
in order of first appearance (lexicographic order).
Having an explicit `Generic` or `Protocol` base class determines
the exact parameter order.
For example::
>>> P = ParamSpec('P')
>>> T = TypeVar('T')
>>> _collect_type_parameters((T, Callable[P, T]))
(~T, ~P)
>>> _collect_type_parameters((list[T], Generic[P, T]))
(~P, ~T)
"""
# required type parameter cannot appear after parameter with default
default_encountered = False
@@ -298,6 +302,17 @@ def _collect_type_parameters(args, *, enforce_default_ordering: bool = True):
' follows type parameter with a default')
parameters.append(t)
elif (
not validate_all
and isinstance(t, _GenericAlias)
and t.__origin__ in (Generic, Protocol)
):
# If we see explicit `Generic[...]` or `Protocol[...]` base classes,
# we need to just copy them as-is.
# Unless `validate_all` is passed, in this case it means that
# we are doing a validation of `Generic` subclasses,
# then we collect all unique parameters to be able to inspect them.
parameters = t.__parameters__
else:
if _is_unpacked_typevartuple(t):
type_var_tuple_encountered = True
@@ -408,6 +423,17 @@ def _tp_cache(func=None, /, *, typed=False):
return decorator
def _rebuild_generic_alias(alias: GenericAlias, args: tuple[object, ...]) -> GenericAlias:
is_unpacked = alias.__unpacked__
if _should_unflatten_callable_args(alias, args):
t = alias.__origin__[(args[:-1], args[-1])]
else:
t = alias.__origin__[args]
if is_unpacked:
t = Unpack[t]
return t
def _deprecation_warning_for_no_type_params_passed(funcname: str) -> None:
import warnings
@@ -421,16 +447,7 @@ def _deprecation_warning_for_no_type_params_passed(funcname: str) -> None:
warnings.warn(depr_message, category=DeprecationWarning, stacklevel=3)
class _Sentinel:
__slots__ = ()
def __repr__(self):
return '<sentinel>'
_sentinel = _Sentinel()
def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=frozenset(),
def _eval_type(t, globalns, localns, type_params, *, recursive_guard=frozenset(),
format=None, owner=None, parent_fwdref=None, prefer_fwd_module=False):
"""Evaluate all forward references in the given type t.
@@ -438,10 +455,7 @@ def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=f
recursive_guard is used to prevent infinite recursion with a recursive
ForwardRef.
"""
if type_params is _sentinel:
_deprecation_warning_for_no_type_params_passed("typing._eval_type")
type_params = ()
if isinstance(t, _lazy_annotationlib.ForwardRef):
if isinstance(t, annotationlib.ForwardRef):
# If the forward_ref has __forward_module__ set, evaluate() infers the globals
# from the module, and it will probably pick better than the globals we have here.
# We do this only for calls from get_type_hints() (which opts in through the
@@ -467,25 +481,20 @@ def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=f
_make_forward_ref(arg, parent_fwdref=parent_fwdref) if isinstance(arg, str) else arg
for arg in t.__args__
)
is_unpacked = t.__unpacked__
if _should_unflatten_callable_args(t, args):
t = t.__origin__[(args[:-1], args[-1])]
else:
t = t.__origin__[args]
if is_unpacked:
t = Unpack[t]
else:
args = t.__args__
ev_args = tuple(
_eval_type(
a, globalns, localns, type_params, recursive_guard=recursive_guard,
format=format, owner=owner, prefer_fwd_module=prefer_fwd_module,
)
for a in t.__args__
for a in args
)
if ev_args == t.__args__:
return t
if isinstance(t, GenericAlias):
return GenericAlias(t.__origin__, ev_args)
return _rebuild_generic_alias(t, ev_args)
if isinstance(t, Union):
return functools.reduce(operator.or_, ev_args)
else:
@@ -573,6 +582,13 @@ class _TypedCacheSpecialForm(_SpecialForm, _root=True):
return self._getitem(self, *parameters)
class _TypeFormForm(_SpecialForm, _root=True):
# TypeForm(X) is equivalent to X but indicates to the type checker
# that the object is a TypeForm.
def __call__(self, obj, /):
return obj
class _AnyMeta(type):
def __instancecheck__(self, obj):
if self is Any:
@@ -883,6 +899,31 @@ def TypeGuard(self, parameters):
return _GenericAlias(self, (item,))
@_TypeFormForm
def TypeForm(self, parameters):
"""A special form representing the value that results from the evaluation
of a type expression.
This value encodes the information supplied in the type expression, and it
represents the type described by that type expression.
When used in a type expression, TypeForm describes a set of type form
objects. It accepts a single type argument, which must be a valid type
expression. ``TypeForm[T]`` describes the set of all type form objects that
represent the type T or types that are assignable to T.
Usage::
def cast[T](typ: TypeForm[T], value: Any) -> T: ...
reveal_type(cast(int, "x")) # int
See PEP 747 for more information.
"""
item = _type_check(parameters, f'{self} accepts only single type.')
return _GenericAlias(self, (item,))
@_SpecialForm
def TypeIs(self, parameters):
"""Special typing construct for marking user-defined type predicate functions.
@@ -958,7 +999,7 @@ def _make_forward_ref(code, *, parent_fwdref=None, **kwargs):
kwargs['module'] = parent_fwdref.__forward_module__
if parent_fwdref.__owner__ is not None:
kwargs['owner'] = parent_fwdref.__owner__
forward_ref = _lazy_annotationlib.ForwardRef(code, **kwargs)
forward_ref = annotationlib.ForwardRef(code, **kwargs)
# For compatibility, eagerly compile the forwardref's code.
forward_ref.__forward_code__
return forward_ref
@@ -993,18 +1034,18 @@ def evaluate_forward_ref(
VALUE.
"""
if format == _lazy_annotationlib.Format.STRING:
if format == annotationlib.Format.STRING:
return forward_ref.__resolved_str__
if forward_ref.__forward_arg__ in _recursive_guard:
return forward_ref
if format is None:
format = _lazy_annotationlib.Format.VALUE
format = annotationlib.Format.VALUE
value = forward_ref.evaluate(globals=globals, locals=locals,
type_params=type_params, owner=owner, format=format)
if (isinstance(value, _lazy_annotationlib.ForwardRef)
and format == _lazy_annotationlib.Format.FORWARDREF):
if (isinstance(value, annotationlib.ForwardRef)
and format == annotationlib.Format.FORWARDREF):
return value
if isinstance(value, str):
@@ -1186,20 +1227,22 @@ def _generic_init_subclass(cls, *args, **kwargs):
if error:
raise TypeError("Cannot inherit from plain Generic")
if '__orig_bases__' in cls.__dict__:
tvars = _collect_type_parameters(cls.__orig_bases__)
tvars = _collect_type_parameters(cls.__orig_bases__, validate_all=True)
# Look for Generic[T1, ..., Tn].
# If found, tvars must be a subset of it.
# If not found, tvars is it.
# Also check for and reject plain Generic,
# and reject multiple Generic[...].
gvars = None
basename = None
for base in cls.__orig_bases__:
if (isinstance(base, _GenericAlias) and
base.__origin__ is Generic):
base.__origin__ in (Generic, Protocol)):
if gvars is not None:
raise TypeError(
"Cannot inherit from Generic[...] multiple times.")
gvars = base.__parameters__
basename = base.__origin__.__name__
if gvars is not None:
tvarset = set(tvars)
gvarset = set(gvars)
@@ -1207,7 +1250,7 @@ def _generic_init_subclass(cls, *args, **kwargs):
s_vars = ', '.join(str(t) for t in tvars if t not in gvarset)
s_args = ', '.join(str(g) for g in gvars)
raise TypeError(f"Some type variables ({s_vars}) are"
f" not listed in Generic[{s_args}]")
f" not listed in {basename}[{s_args}]")
tvars = gvars
cls.__parameters__ = tuple(tvars)
@@ -1605,21 +1648,6 @@ class _SpecialGenericAlias(_NotIterable, _BaseGenericAlias, _root=True):
return Union[left, self]
class _DeprecatedGenericAlias(_SpecialGenericAlias, _root=True):
def __init__(
self, origin, nparams, *, removal_version, inst=True, name=None
):
super().__init__(origin, nparams, inst=inst, name=name)
self._removal_version = removal_version
def __instancecheck__(self, inst):
import warnings
warnings._deprecated(
f"{self.__module__}.{self._name}", remove=self._removal_version
)
return super().__instancecheck__(inst)
class _CallableGenericAlias(_NotIterable, _GenericAlias, _root=True):
def __repr__(self):
assert self._name == 'Callable'
@@ -1832,6 +1860,7 @@ class _TypingEllipsis:
_TYPING_INTERNALS = frozenset({
'__parameters__', '__orig_bases__', '__orig_class__',
'_is_protocol', '_is_runtime_protocol', '__protocol_attrs__',
'__typing_is_deprecated_inherited_runtime_protocol__',
'__non_callable_proto_members__', '__type_params__',
})
@@ -1861,8 +1890,8 @@ def _get_protocol_attrs(cls):
annotations = base.__annotations__
except Exception:
# Only go through annotationlib to handle deferred annotations if we need to
annotations = _lazy_annotationlib.get_annotations(
base, format=_lazy_annotationlib.Format.FORWARDREF
annotations = annotationlib.get_annotations(
base, format=annotationlib.Format.FORWARDREF
)
for attr in (*base.__dict__, *annotations):
if not attr.startswith('_abc_') and attr not in EXCLUDED_ATTRIBUTES:
@@ -1916,7 +1945,9 @@ def _allow_reckless_class_checks(depth=2):
The abc and functools modules indiscriminately call isinstance() and
issubclass() on the whole MRO of a user class, which may contain protocols.
"""
return _caller(depth) in {'abc', 'functools', None}
# gh-136047: When `_abc` module is not available, `_py_abc` is required to
# allow `_py_abc.ABCMeta` fallback.
return _caller(depth) in {'abc', '_py_abc', 'functools', None}
_PROTO_ALLOWLIST = {
@@ -2019,6 +2050,16 @@ class _ProtocolMeta(ABCMeta):
"Instance and class checks can only be used with "
"@runtime_checkable protocols"
)
if getattr(cls, '__typing_is_deprecated_inherited_runtime_protocol__', False):
# See GH-132604.
import warnings
depr_message = (
f"{cls!r} isn't explicitly decorated with @runtime_checkable but "
"it is used in issubclass() or isinstance(). Instance and class "
"checks can only be used with @runtime_checkable protocols. "
"This will raise a TypeError in Python 3.20."
)
warnings.warn(depr_message, category=DeprecationWarning, stacklevel=2)
if (
# this attribute is set by @runtime_checkable:
cls.__non_callable_proto_members__
@@ -2048,6 +2089,18 @@ class _ProtocolMeta(ABCMeta):
raise TypeError("Instance and class checks can only be used with"
" @runtime_checkable protocols")
if getattr(cls, '__typing_is_deprecated_inherited_runtime_protocol__', False):
# See GH-132604.
import warnings
depr_message = (
f"{cls!r} isn't explicitly decorated with @runtime_checkable but "
"it is used in issubclass() or isinstance(). Instance and class "
"checks can only be used with @runtime_checkable protocols. "
"This will raise a TypeError in Python 3.20."
)
warnings.warn(depr_message, category=DeprecationWarning, stacklevel=2)
if _abc_instancecheck(cls, instance):
return True
@@ -2086,8 +2139,8 @@ def _proto_hook(cls, other):
try:
annos = base.__annotations__
except Exception:
annos = _lazy_annotationlib.get_annotations(
base, format=_lazy_annotationlib.Format.FORWARDREF
annos = annotationlib.get_annotations(
base, format=annotationlib.Format.FORWARDREF
)
if attr in annos:
break
@@ -2140,6 +2193,11 @@ class Protocol(Generic, metaclass=_ProtocolMeta):
if not cls.__dict__.get('_is_protocol', False):
cls._is_protocol = any(b is Protocol for b in cls.__bases__)
# Mark inherited runtime checkability (deprecated). See GH-132604.
if cls._is_protocol and getattr(cls, '_is_runtime_protocol', False):
# This flag is set to False by @runtime_checkable.
cls.__typing_is_deprecated_inherited_runtime_protocol__ = True
# Set (or override) the protocol subclass hook.
if '__subclasshook__' not in cls.__dict__:
cls.__subclasshook__ = _proto_hook
@@ -2286,6 +2344,9 @@ def runtime_checkable(cls):
raise TypeError('@runtime_checkable can be only applied to protocol classes,'
' got %r' % cls)
cls._is_runtime_protocol = True
# See GH-132604.
if hasattr(cls, '__typing_is_deprecated_inherited_runtime_protocol__'):
cls.__typing_is_deprecated_inherited_runtime_protocol__ = False
# PEP 544 prohibits using issubclass()
# with protocols that have non-method members.
# See gh-113320 for why we compute this attribute here,
@@ -2366,14 +2427,14 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
"""
if getattr(obj, '__no_type_check__', None):
return {}
Format = _lazy_annotationlib.Format
Format = annotationlib.Format
if format is None:
format = Format.VALUE
# Classes require a special treatment.
if isinstance(obj, type):
hints = {}
for base in reversed(obj.__mro__):
ann = _lazy_annotationlib.get_annotations(base, format=format)
ann = annotationlib.get_annotations(base, format=format)
if format == Format.STRING:
hints.update(ann)
continue
@@ -2406,7 +2467,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
else:
return {k: _strip_annotations(t) for k, t in hints.items()}
hints = _lazy_annotationlib.get_annotations(obj, format=format)
hints = annotationlib.get_annotations(obj, format=format)
if (
not hints
and not isinstance(obj, types.ModuleType)
@@ -2482,7 +2543,7 @@ def _strip_annotations(t):
stripped_args = tuple(_strip_annotations(a) for a in t.__args__)
if stripped_args == t.__args__:
return t
return GenericAlias(t.__origin__, stripped_args)
return _rebuild_generic_alias(t, stripped_args)
if isinstance(t, Union):
stripped_args = tuple(_strip_annotations(a) for a in t.__args__)
if stripped_args == t.__args__:
@@ -2630,23 +2691,6 @@ def no_type_check(arg):
return arg
def no_type_check_decorator(decorator):
"""Decorator to give another decorator the @no_type_check effect.
This wraps the decorator with something that wraps the decorated
function in @no_type_check.
"""
import warnings
warnings._deprecated("typing.no_type_check_decorator", remove=(3, 15))
@functools.wraps(decorator)
def wrapped_decorator(*args, **kwds):
func = decorator(*args, **kwds)
func = no_type_check(func)
return func
return wrapped_decorator
def _overload_dummy(*args, **kwds):
"""Helper for @overload to raise when called."""
raise NotImplementedError(
@@ -2753,6 +2797,29 @@ def final(f):
return f
def disjoint_base(cls):
"""This decorator marks a class as a disjoint base.
Child classes of a disjoint base cannot inherit from other disjoint bases that are
not parent or child classes of the disjoint base.
For example:
@disjoint_base
class Disjoint1: pass
@disjoint_base
class Disjoint2: pass
class Disjoint3(Disjoint1, Disjoint2): pass # Type checker error
Type checkers can use knowledge of disjoint bases to detect unreachable code
and determine when two types can overlap.
"""
cls.__disjoint_base__ = True
return cls
# Some unconstrained type variables. These were initially used by the container types.
# They were never meant for export and are now unused, but we keep them around to
# avoid breaking compatibility with users who import them.
@@ -2808,9 +2875,6 @@ Mapping = _alias(collections.abc.Mapping, 2)
MutableMapping = _alias(collections.abc.MutableMapping, 2)
Sequence = _alias(collections.abc.Sequence, 1)
MutableSequence = _alias(collections.abc.MutableSequence, 1)
ByteString = _DeprecatedGenericAlias(
collections.abc.ByteString, 0, removal_version=(3, 17) # Not generic.
)
# Tuple accepts variable number of parameters.
Tuple = _TupleType(tuple, -1, inst=False, name='Tuple')
Tuple.__doc__ = \
@@ -2954,10 +3018,10 @@ def _make_eager_annotate(types):
for key, val in types.items()}
def annotate(format):
match format:
case _lazy_annotationlib.Format.VALUE | _lazy_annotationlib.Format.FORWARDREF:
case annotationlib.Format.VALUE | annotationlib.Format.FORWARDREF:
return checked_types
case _lazy_annotationlib.Format.STRING:
return _lazy_annotationlib.annotations_to_string(types)
case annotationlib.Format.STRING:
return annotationlib.annotations_to_string(types)
case _:
raise NotImplementedError(format)
return annotate
@@ -2987,9 +3051,9 @@ class NamedTupleMeta(type):
types = ns["__annotations__"]
field_names = list(types)
annotate = _make_eager_annotate(types)
elif (original_annotate := _lazy_annotationlib.get_annotate_from_class_namespace(ns)) is not None:
types = _lazy_annotationlib.call_annotate_function(
original_annotate, _lazy_annotationlib.Format.FORWARDREF)
elif (original_annotate := annotationlib.get_annotate_from_class_namespace(ns)) is not None:
types = annotationlib.call_annotate_function(
original_annotate, annotationlib.Format.FORWARDREF)
field_names = list(types)
# For backward compatibility, type-check all the types at creation time
@@ -2997,9 +3061,9 @@ class NamedTupleMeta(type):
_type_check(typ, "field annotation must be a type")
def annotate(format):
annos = _lazy_annotationlib.call_annotate_function(
annos = annotationlib.call_annotate_function(
original_annotate, format)
if format != _lazy_annotationlib.Format.STRING:
if format != annotationlib.Format.STRING:
return {key: _type_check(val, f"field {key} annotation must be a type")
for key, val in annos.items()}
return annos
@@ -3049,7 +3113,7 @@ class NamedTupleMeta(type):
return nm_tpl
def NamedTuple(typename, fields=_sentinel, /, **kwargs):
def NamedTuple(typename, fields, /):
"""Typed version of collections.namedtuple.
Usage::
@@ -3069,49 +3133,9 @@ def NamedTuple(typename, fields=_sentinel, /, **kwargs):
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
"""
if fields is _sentinel:
if kwargs:
deprecated_thing = "Creating NamedTuple classes using keyword arguments"
deprecation_msg = (
"{name} is deprecated and will be disallowed in Python {remove}. "
"Use the class-based or functional syntax instead."
)
else:
deprecated_thing = "Failing to pass a value for the 'fields' parameter"
example = f"`{typename} = NamedTuple({typename!r}, [])`"
deprecation_msg = (
"{name} is deprecated and will be disallowed in Python {remove}. "
"To create a NamedTuple class with 0 fields "
"using the functional syntax, "
"pass an empty list, e.g. "
) + example + "."
elif fields is None:
if kwargs:
raise TypeError(
"Cannot pass `None` as the 'fields' parameter "
"and also specify fields using keyword arguments"
)
else:
deprecated_thing = "Passing `None` as the 'fields' parameter"
example = f"`{typename} = NamedTuple({typename!r}, [])`"
deprecation_msg = (
"{name} is deprecated and will be disallowed in Python {remove}. "
"To create a NamedTuple class with 0 fields "
"using the functional syntax, "
"pass an empty list, e.g. "
) + example + "."
elif kwargs:
raise TypeError("Either list of fields or keywords"
" can be provided to NamedTuple, not both")
if fields is _sentinel or fields is None:
import warnings
warnings._deprecated(deprecated_thing, message=deprecation_msg, remove=(3, 15))
fields = kwargs.items()
types = {n: _type_check(t, f"field {n} annotation must be a type")
for n, t in fields}
field_names = [n for n, _ in fields]
nt = _make_nmtuple(typename, field_names, _make_eager_annotate(types), module=_caller())
nt = _make_nmtuple(typename, types, _make_eager_annotate(types), module=_caller())
nt.__orig_bases__ = (NamedTuple,)
return nt
@@ -3124,6 +3148,9 @@ def _namedtuple_mro_entries(bases):
NamedTuple.__mro_entries__ = _namedtuple_mro_entries
NoExtraItems = sentinel("NoExtraItems")
def _get_typeddict_qualifiers(annotation_type):
while True:
annotation_origin = get_origin(annotation_type)
@@ -3147,7 +3174,8 @@ def _get_typeddict_qualifiers(annotation_type):
class _TypedDictMeta(type):
def __new__(cls, name, bases, ns, total=True):
def __new__(cls, name, bases, ns, total=True, closed=None,
extra_items=NoExtraItems):
"""Create a new typed dict class object.
This method is called when TypedDict is subclassed,
@@ -3159,6 +3187,8 @@ class _TypedDictMeta(type):
if type(base) is not _TypedDictMeta and base is not Generic:
raise TypeError('cannot inherit from both a TypedDict type '
'and a non-TypedDict base class')
if closed is not None and extra_items is not NoExtraItems:
raise TypeError(f"Cannot combine closed={closed!r} and extra_items")
if any(issubclass(b, Generic) for b in bases):
generic_base = (Generic,)
@@ -3175,9 +3205,9 @@ class _TypedDictMeta(type):
if ns_annotations is not None:
own_annotate = None
own_annotations = ns_annotations
elif (own_annotate := _lazy_annotationlib.get_annotate_from_class_namespace(ns)) is not None:
own_annotations = _lazy_annotationlib.call_annotate_function(
own_annotate, _lazy_annotationlib.Format.FORWARDREF, owner=tp_dict
elif (own_annotate := annotationlib.get_annotate_from_class_namespace(ns)) is not None:
own_annotations = annotationlib.call_annotate_function(
own_annotate, annotationlib.Format.FORWARDREF, owner=tp_dict
)
else:
own_annotate = None
@@ -3244,20 +3274,20 @@ class _TypedDictMeta(type):
base_annotate = base.__annotate__
if base_annotate is None:
continue
base_annos = _lazy_annotationlib.call_annotate_function(
base_annos = annotationlib.call_annotate_function(
base_annotate, format, owner=base)
annos.update(base_annos)
if own_annotate is not None:
own = _lazy_annotationlib.call_annotate_function(
own = annotationlib.call_annotate_function(
own_annotate, format, owner=tp_dict)
if format != _lazy_annotationlib.Format.STRING:
if format != annotationlib.Format.STRING:
own = {
n: _type_check(tp, msg, module=tp_dict.__module__)
for n, tp in own.items()
}
elif format == _lazy_annotationlib.Format.STRING:
own = _lazy_annotationlib.annotations_to_string(own_annotations)
elif format in (_lazy_annotationlib.Format.FORWARDREF, _lazy_annotationlib.Format.VALUE):
elif format == annotationlib.Format.STRING:
own = annotationlib.annotations_to_string(own_annotations)
elif format in (annotationlib.Format.FORWARDREF, annotationlib.Format.VALUE):
own = own_checked_annotations
else:
raise NotImplementedError(format)
@@ -3270,6 +3300,8 @@ class _TypedDictMeta(type):
tp_dict.__readonly_keys__ = frozenset(readonly_keys)
tp_dict.__mutable_keys__ = frozenset(mutable_keys)
tp_dict.__total__ = total
tp_dict.__closed__ = closed
tp_dict.__extra_items__ = extra_items
return tp_dict
__call__ = dict # static method
@@ -3281,7 +3313,8 @@ class _TypedDictMeta(type):
__instancecheck__ = __subclasscheck__
def TypedDict(typename, fields=_sentinel, /, *, total=True):
def TypedDict(typename, fields, /, *, total=True, closed=None,
extra_items=NoExtraItems):
"""A simple typed namespace. At runtime it is equivalent to a plain dict.
TypedDict creates a dictionary type such that a type checker will expect all
@@ -3335,33 +3368,41 @@ def TypedDict(typename, fields=_sentinel, /, *, total=True):
id: ReadOnly[int] # the "id" key must not be modified
username: str # the "username" key can be changed
See PEPs 589, 655, and 705 for more information.
The closed argument controls whether the TypedDict allows additional
non-required items during inheritance and assignability checks.
If closed=True, the TypedDict does not allow additional items::
Point2D = TypedDict('Point2D', {'x': int, 'y': int}, closed=True)
class Point3D(Point2D):
z: int # Type checker error
Passing closed=False explicitly requests TypedDict's default open behavior.
If closed is not provided, the behavior is inherited from the superclass.
A type checker is only expected to support a literal False or True as the
value of the closed argument.
The extra_items argument can instead be used to specify the assignable type
of unknown non-required keys::
Point2D = TypedDict('Point2D', {'x': int, 'y': int}, extra_items=int)
class Point3D(Point2D):
z: int # OK
label: str # Type checker error
The extra_items argument is also inherited through subclassing. It is unset
by default, and it may not be used with the closed argument at the same
time.
See PEPs 589, 655, 705, and 728 for more information.
"""
if fields is _sentinel or fields is None:
import warnings
if fields is _sentinel:
deprecated_thing = "Failing to pass a value for the 'fields' parameter"
else:
deprecated_thing = "Passing `None` as the 'fields' parameter"
example = f"`{typename} = TypedDict({typename!r}, {{{{}}}})`"
deprecation_msg = (
"{name} is deprecated and will be disallowed in Python {remove}. "
"To create a TypedDict class with 0 fields "
"using the functional syntax, "
"pass an empty dictionary, e.g. "
) + example + "."
warnings._deprecated(deprecated_thing, message=deprecation_msg, remove=(3, 15))
fields = {}
ns = {'__annotations__': dict(fields)}
module = _caller()
if module is not None:
# Setting correct module is necessary to make typed dict classes pickleable.
ns['__module__'] = module
td = _TypedDictMeta(typename, (), ns, total=total)
td = _TypedDictMeta(typename, (), ns, total=total, closed=closed,
extra_items=extra_items)
td.__orig_bases__ = (TypedDict,)
return td
@@ -3549,7 +3590,7 @@ class IO(Generic[AnyStr]):
pass
@abstractmethod
def read(self, n: int = -1) -> AnyStr:
def read(self, n: int = -1, /) -> AnyStr:
pass
@abstractmethod
@@ -3557,15 +3598,15 @@ class IO(Generic[AnyStr]):
pass
@abstractmethod
def readline(self, limit: int = -1) -> AnyStr:
def readline(self, limit: int = -1, /) -> AnyStr:
pass
@abstractmethod
def readlines(self, hint: int = -1) -> list[AnyStr]:
def readlines(self, hint: int = -1, /) -> list[AnyStr]:
pass
@abstractmethod
def seek(self, offset: int, whence: int = 0) -> int:
def seek(self, offset: int, whence: int = 0, /) -> int:
pass
@abstractmethod
@@ -3577,7 +3618,7 @@ class IO(Generic[AnyStr]):
pass
@abstractmethod
def truncate(self, size: int | None = None) -> int:
def truncate(self, size: int | None = None, /) -> int:
pass
@abstractmethod
@@ -3585,11 +3626,11 @@ class IO(Generic[AnyStr]):
pass
@abstractmethod
def write(self, s: AnyStr) -> int:
def write(self, s: AnyStr, /) -> int:
pass
@abstractmethod
def writelines(self, lines: list[AnyStr]) -> None:
def writelines(self, lines: list[AnyStr], /) -> None:
pass
@abstractmethod
@@ -3597,7 +3638,7 @@ class IO(Generic[AnyStr]):
pass
@abstractmethod
def __exit__(self, type, value, traceback) -> None:
def __exit__(self, type, value, traceback, /) -> None:
pass
@@ -3607,7 +3648,7 @@ class BinaryIO(IO[bytes]):
__slots__ = ()
@abstractmethod
def write(self, s: bytes | bytearray) -> int:
def write(self, s: bytes | bytearray, /) -> int:
pass
@abstractmethod
@@ -3843,7 +3884,7 @@ def __getattr__(attr):
are only created on-demand here.
"""
if attr == "ForwardRef":
obj = _lazy_annotationlib.ForwardRef
obj = annotationlib.ForwardRef
elif attr in {"Pattern", "Match"}:
import re
obj = _alias(getattr(re, attr), 1)
@@ -3860,6 +3901,48 @@ def __getattr__(attr):
)
warnings.warn(depr_message, category=DeprecationWarning, stacklevel=2)
obj = _collect_type_parameters
elif attr == "ByteString":
import warnings
warnings._deprecated(
"typing.ByteString",
message=(
"{name!r} and 'collections.abc.ByteString' are deprecated "
"and slated for removal in Python {remove}"
),
remove=(3, 17)
)
class _DeprecatedGenericAlias(_SpecialGenericAlias, _root=True):
def __init__(
self, origin, nparams, *, removal_version, inst=True, name=None
):
super().__init__(origin, nparams, inst=inst, name=name)
self._removal_version = removal_version
def __instancecheck__(self, inst):
import warnings
warnings._deprecated(
f"{self.__module__}.{self._name}", remove=self._removal_version
)
return super().__instancecheck__(inst)
def __subclasscheck__(self, cls):
import warnings
warnings._deprecated(
f"{self.__module__}.{self._name}", remove=self._removal_version
)
return super().__subclasscheck__(cls)
with warnings.catch_warnings(
action="ignore", category=DeprecationWarning
):
# Not generic
ByteString = globals()["ByteString"] = _DeprecatedGenericAlias(
collections.abc.ByteString, 0, removal_version=(3, 17)
)
return ByteString
else:
raise AttributeError(f"module {__name__!r} has no attribute {attr!r}")
globals()[attr] = obj
+2
View File
@@ -543,6 +543,7 @@ mod _sre {
flags(HAS_WEAKREF)
)]
impl Py<Pattern> {
#[pymethod(name = "prefixmatch")]
#[pymethod(name = "match")]
fn py_match(
zelf: PyRef<Pattern>,
@@ -1161,6 +1162,7 @@ mod _sre {
self.pattern.clone()
}
#[pymethod(name = "prefixmatch")]
#[pymethod(name = "match")]
fn py_match(&self, vm: &VirtualMachine) -> PyResult<Option<PyRef<Match>>> {
with_sre_str!(self.pattern, &self.string.clone(), vm, |s| {