Skip to content

Commit 6738161

Browse files
dzherbtaminomara
authored andcommitted
fix: string annotations ClassVar bug
1 parent 618b726 commit 6738161

5 files changed

Lines changed: 104 additions & 18 deletions

File tree

Lib/dataclasses.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -811,21 +811,33 @@ def _is_type(annotation, cls, a_module, a_type, is_type_predicate):
811811
_MODULE_IDENTIFIER_RE = re.compile(r'(?:\s*(\w+)\s*\.)?\s*(\w+)')
812812

813813
match = _MODULE_IDENTIFIER_RE.prefixmatch(annotation)
814-
if match:
815-
ns = None
816-
module_name = match[1]
817-
if not module_name:
818-
# No module name, assume the class's module did
819-
# "from dataclasses import InitVar".
820-
ns = sys.modules.get(cls.__module__).__dict__
821-
else:
822-
# Look up module_name in the class's module.
823-
module = sys.modules.get(cls.__module__)
824-
if module and module.__dict__.get(module_name) is a_module:
825-
ns = sys.modules.get(a_type.__module__).__dict__
826-
if ns and is_type_predicate(ns.get(match[2]), a_module):
827-
return True
828-
return False
814+
if not match:
815+
return False
816+
817+
ns = None
818+
module_name = match.group(1)
819+
type_name = match.group(2)
820+
821+
if not module_name:
822+
# No module name, assume the class's module did
823+
# "from dataclasses import InitVar".
824+
ns = sys.modules.get(cls.__module__).__dict__
825+
else:
826+
# Look up module_name in the class's module.
827+
cls_module = sys.modules.get(cls.__module__)
828+
if not cls_module:
829+
return False
830+
831+
a_type_module = cls_module.__dict__.get(module_name)
832+
if (
833+
isinstance(a_type_module, types.ModuleType)
834+
# Consider the case when a_type does not belong
835+
# to the namespace, e.g. 'dataclasses.ClassVar[int]'
836+
and a_type_module.__dict__.get(type_name) is a_type
837+
):
838+
ns = sys.modules.get(a_type.__module__).__dict__
839+
840+
return ns and is_type_predicate(ns.get(type_name), a_module)
829841

830842

831843
def _get_field(cls, a_name, a_type, default_kw_only):

Lib/test/test_dataclasses/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4329,10 +4329,14 @@ def test_classvar_module_level_import(self):
43294329
from test.test_dataclasses import dataclass_module_1_str
43304330
from test.test_dataclasses import dataclass_module_2
43314331
from test.test_dataclasses import dataclass_module_2_str
4332+
from test.test_dataclasses import dataclass_module_3
4333+
from test.test_dataclasses import dataclass_module_3_str
43324334

4333-
for m in (dataclass_module_1, dataclass_module_1_str,
4334-
dataclass_module_2, dataclass_module_2_str,
4335-
):
4335+
for m in (
4336+
dataclass_module_1, dataclass_module_1_str,
4337+
dataclass_module_2, dataclass_module_2_str,
4338+
dataclass_module_3, dataclass_module_3_str,
4339+
):
43364340
with self.subTest(m=m):
43374341
# There's a difference in how the ClassVars are
43384342
# interpreted when using string annotations or
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# We need this to test a case when a type
2+
# is imported via some other package,
3+
# like ClassVar from typing_extensions instead of typing.
4+
# https://github.com/python/cpython/issues/133956
5+
from typing import ClassVar
6+
from dataclasses import InitVar
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#from __future__ import annotations
2+
USING_STRINGS = False
3+
4+
# dataclass_module_3.py and dataclass_module_3_str.py are identical
5+
# except only the latter uses string annotations.
6+
7+
from dataclasses import dataclass
8+
import test.test_dataclasses._types_proxy as tp
9+
10+
T_CV2 = tp.ClassVar[int]
11+
T_CV3 = tp.ClassVar
12+
13+
T_IV2 = tp.InitVar[int]
14+
T_IV3 = tp.InitVar
15+
16+
@dataclass
17+
class CV:
18+
T_CV4 = tp.ClassVar
19+
cv0: tp.ClassVar[int] = 20
20+
cv1: tp.ClassVar = 30
21+
cv2: T_CV2
22+
cv3: T_CV3
23+
not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar.
24+
25+
@dataclass
26+
class IV:
27+
T_IV4 = tp.InitVar
28+
iv0: tp.InitVar[int]
29+
iv1: tp.InitVar
30+
iv2: T_IV2
31+
iv3: T_IV3
32+
not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from __future__ import annotations
2+
USING_STRINGS = True
3+
4+
# dataclass_module_3.py and dataclass_module_2_str.py are identical
5+
# except only the latter uses string annotations.
6+
7+
from dataclasses import dataclass
8+
import test.test_dataclasses._types_proxy as tp
9+
10+
T_CV2 = tp.ClassVar[int]
11+
T_CV3 = tp.ClassVar
12+
13+
T_IV2 = tp.InitVar[int]
14+
T_IV3 = tp.InitVar
15+
16+
@dataclass
17+
class CV:
18+
T_CV4 = tp.ClassVar
19+
cv0: tp.ClassVar[int] = 20
20+
cv1: tp.ClassVar = 30
21+
cv2: T_CV2
22+
cv3: T_CV3
23+
not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar.
24+
25+
@dataclass
26+
class IV:
27+
T_IV4 = tp.InitVar
28+
iv0: tp.InitVar[int]
29+
iv1: tp.InitVar
30+
iv2: T_IV2
31+
iv3: T_IV3
32+
not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar.

0 commit comments

Comments
 (0)