mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
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:
1 parent
7750dbcb86
commit
7137540b93
15 files changed
+1176
-494
No files matched your search
Vendored
+89
-80
@@ -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
|
||||
|
||||
Vendored
+34
-20
@@ -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}")
|
||||
Vendored
+1
-1
@@ -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
|
||||
|
||||
Vendored
-1
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
@@ -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
@@ -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.
|
||||
@@ -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.
|
||||
Vendored
+46
-33
@@ -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()
|
||||
Vendored
+57
-1
@@ -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]
|
||||
|
||||
Vendored
+475
-170
@@ -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__
|
||||
)
|
||||
}
|
||||
|
||||
Vendored
+262
-179
@@ -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
|
||||
|
||||
@@ -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| {
|
||||
|
||||
Reference in new issue
Block a user