Skip to content

Commit a60d850

Browse files
committed
Ever more wild globals manipulation
1 parent 35015d0 commit a60d850

File tree

3 files changed

+89
-12
lines changed

3 files changed

+89
-12
lines changed

Lib/dataclasses.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,8 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
906906
# we're iterating over them, see if any are frozen.
907907
any_frozen_base = False
908908
has_dataclass_bases = False
909+
init_globals = dict(globals)
910+
909911
for b in cls.__mro__[-1:0:-1]:
910912
# Only process classes that have been processed by our
911913
# decorator. That is, they have a _FIELDS attribute.
@@ -916,6 +918,17 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
916918
fields[f.name] = f
917919
if getattr(b, _PARAMS).frozen:
918920
any_frozen_base = True
921+
if has_dataclass_bases:
922+
# If dataclass has other dataclass as a base type,
923+
# it might have existing `__init__` method.
924+
# We need to change its `globals`, because otherwise
925+
# we might end up with unsolvable annotations. For example:
926+
# `def __init__(self, d: collections.OrderedDict) -> None:`
927+
# We won't be able to resolve `collections.OrderedDict`
928+
# with wrong `globals`, when placed in a different module. #45524
929+
super_init = getattr(b, '__init__', None)
930+
if super_init is not None:
931+
init_globals.update(getattr(super_init, '__globals__', {}))
919932

920933
# Annotations that are defined in this class (not in base
921934
# classes). If __annotations__ isn't present, then this class
@@ -1020,18 +1033,6 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
10201033
if init:
10211034
# Does this class have a post-init function?
10221035
has_post_init = hasattr(cls, _POST_INIT_NAME)
1023-
init_globals = dict(globals)
1024-
if has_dataclass_bases:
1025-
# If dataclass has other dataclass as a base type,
1026-
# it might have existing `__init__` method.
1027-
# We need to change its `globals`, because otherwise
1028-
# we might end up with unsolvable annotations. For example:
1029-
# `def __init__(self, d: collections.OrderedDict) -> None:`
1030-
# won't be able to resolve `collections.OrderedDict`
1031-
# with wrong `globals`, when placed in different modules. #45524
1032-
super_init = getattr(cls, '__init__', None)
1033-
if super_init is not None:
1034-
init_globals = getattr(super_init, '__globals__', init_globals )
10351036

10361037
_set_new_attribute(cls, '__init__',
10371038
_init_fn(all_init_fields,

Lib/test/dataclass_textanno2.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from __future__ import annotations
2+
3+
import dataclasses
4+
from test import dataclass_textanno # We need to be sure that `Foo` is not in scope
5+
6+
7+
class Custom:
8+
pass
9+
10+
11+
@dataclasses.dataclass
12+
class Child(dataclass_textanno.Bar):
13+
custom: Custom
14+
15+
16+
@dataclasses.dataclass(init=False)
17+
class WithFutureInit(Child):
18+
def __init__(self, foo: dataclass_textanno.Foo, custom: Custom) -> None:
19+
pass

Lib/test/test_typing.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3318,11 +3318,16 @@ class CustomInit(dataclass_textanno.Bar):
33183318
def __init__(self, foo: dataclass_textanno.Foo) -> None:
33193319
pass
33203320

3321+
@dataclass
3322+
class FutureInitChild(dataclass_textanno.WithFutureInit):
3323+
pass
3324+
33213325
classes = [
33223326
Default,
33233327
WithInitFalse,
33243328
CustomInit,
33253329
dataclass_textanno.WithFutureInit,
3330+
FutureInitChild,
33263331
]
33273332
for klass in classes:
33283333
with self.subTest(klass=klass):
@@ -3336,6 +3341,58 @@ def __init__(self, foo: dataclass_textanno.Foo) -> None:
33363341
{'foo': dataclass_textanno.Foo, 'return': type(None)},
33373342
)
33383343

3344+
def test_dataclass_from_proxy_module(self):
3345+
# see bpo-45524
3346+
from test import dataclass_textanno, dataclass_textanno2
3347+
from dataclasses import dataclass
3348+
3349+
@dataclass
3350+
class Default(dataclass_textanno2.Child):
3351+
pass
3352+
3353+
@dataclass(init=False)
3354+
class WithInitFalse(dataclass_textanno2.Child):
3355+
pass
3356+
3357+
@dataclass(init=False)
3358+
class CustomInit(dataclass_textanno2.Child):
3359+
def __init__(
3360+
self,
3361+
foo: dataclass_textanno.Foo,
3362+
custom: dataclass_textanno2.Custom,
3363+
) -> None:
3364+
pass
3365+
3366+
@dataclass
3367+
class FutureInitChild(dataclass_textanno2.WithFutureInit):
3368+
pass
3369+
3370+
classes = [
3371+
# Default,
3372+
# WithInitFalse,
3373+
# CustomInit,
3374+
# dataclass_textanno2.WithFutureInit,
3375+
FutureInitChild,
3376+
]
3377+
for klass in classes:
3378+
with self.subTest(klass=klass):
3379+
self.assertEqual(
3380+
get_type_hints(klass),
3381+
{
3382+
'foo': dataclass_textanno.Foo,
3383+
'custom': dataclass_textanno2.Custom,
3384+
},
3385+
)
3386+
self.assertEqual(get_type_hints(klass.__new__), {})
3387+
self.assertEqual(
3388+
get_type_hints(klass.__init__),
3389+
{
3390+
'foo': dataclass_textanno.Foo,
3391+
'custom': dataclass_textanno2.Custom,
3392+
'return': type(None),
3393+
},
3394+
)
3395+
33393396

33403397
class GetUtilitiesTestCase(TestCase):
33413398
def test_get_origin(self):

0 commit comments

Comments
 (0)