Skip to content

Commit 68192a8

Browse files
committed
Fix the tests, rebased on UpdateClass
This fixes a couple actual bugs: * need to eval types in UpdateClass * evaluating strings in _eval_type_type shouldn't be needed, we'll do it when we have to with _apply_generic
1 parent 0a5fef3 commit 68192a8

4 files changed

Lines changed: 58 additions & 80 deletions

File tree

tests/test_fastapilike_1.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# We should have at least *one* test with this...
2+
from __future__ import annotations
3+
14
import dataclasses
25
import enum
36
import textwrap
@@ -164,7 +167,7 @@ class Hero:
164167
HasDefault[int | None, None]
165168
] # = Field(default=None, primary_key=True)
166169

167-
name: str
170+
name: "str"
168171
age: HasDefault[int | None, None] # = Field(default=None, index=True)
169172

170173
secret_name: Hidden[str]

tests/test_type_eval.py

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1827,12 +1827,13 @@ def g(self) -> int: ... # omitted
18271827
assert m == Never
18281828

18291829

1830+
type AttrsAsSets[T] = UpdateClass[
1831+
*[Member[GetName[m], set[GetType[m]]] for m in Iter[Attrs[T]]]
1832+
]
1833+
1834+
18301835
def test_update_class_members_03():
18311836
# Generic UpdateClass, uses T
1832-
type AttrsAsSets[T] = UpdateClass[
1833-
*[Member[GetName[m], set[GetType[m]]] for m in Iter[Attrs[T]]]
1834-
]
1835-
18361837
class A:
18371838
a: int
18381839

@@ -1906,9 +1907,8 @@ def g(self) -> int: ...
19061907

19071908
# Attrs
19081909
attrs = eval_typing(Attrs[B])
1909-
assert (
1910-
attrs
1911-
== tuple[
1910+
assert str(attrs) == str(
1911+
tuple[
19121912
Member[Literal["a"], int, Never, Never, A],
19131913
Member[Literal["b"], int, Never, Never, B],
19141914
]
@@ -1963,10 +1963,6 @@ def g(self) -> int: ...
19631963

19641964
def test_update_class_inheritance_01():
19651965
# current class init subclass is not applied
1966-
type AttrsAsSets[T] = UpdateClass[
1967-
*[Member[GetName[m], set[GetType[m]]] for m in Iter[Attrs[T]]]
1968-
]
1969-
19701966
class A:
19711967
a: int
19721968

@@ -1993,18 +1989,16 @@ def __init_subclass__[T](
19931989
)
19941990

19951991

1992+
type AttrsAsList[T] = UpdateClass[
1993+
*[Member[GetName[m], list[GetType[m]]] for m in Iter[Attrs[T]]]
1994+
]
1995+
type AttrsAsTuple[T] = UpdateClass[
1996+
*[Member[GetName[m], tuple[GetType[m]]] for m in Iter[Attrs[T]]]
1997+
]
1998+
1999+
19962000
def test_update_class_inheritance_02():
19972001
# __init_subclass__ calls follow normal MRO
1998-
type AttrsAsSets[T] = UpdateClass[
1999-
*[Member[GetName[m], set[GetType[m]]] for m in Iter[Attrs[T]]]
2000-
]
2001-
type AttrsAsList[T] = UpdateClass[
2002-
*[Member[GetName[m], list[GetType[m]]] for m in Iter[Attrs[T]]]
2003-
]
2004-
type AttrsAsTuple[T] = UpdateClass[
2005-
*[Member[GetName[m], tuple[GetType[m]]] for m in Iter[Attrs[T]]]
2006-
]
2007-
20082002
class A:
20092003
a: int
20102004

typemap/type_eval/_eval_operators.py

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
import re
88
import types
99
import typing
10+
import sys
11+
1012
from typing_extensions import _AnnotatedAlias as typing_AnnotatedAlias
1113

1214
from typemap import type_eval
1315
from typemap.type_eval import _apply_generic, _typing_inspect
1416
from typemap.type_eval._eval_typing import (
1517
_child_context,
1618
_eval_types,
17-
_get_class_type_hint_namespaces,
1819
)
1920
from typemap.typing import (
2021
Attrs,
@@ -180,11 +181,10 @@ def _get_update_class_members(
180181
) -> list[Member] | None:
181182
if (
182183
(init_subclass := base.__dict__.get("__init_subclass__"))
183-
and (
184-
init_subclass_annos := getattr(
185-
init_subclass, "__annotations__", None
186-
)
187-
)
184+
# XXX: We're using get_type_hints now to evaluate hints but
185+
# we should have our own generic infrastructure instead.
186+
# (I'm working on it -sully)
187+
and (init_subclass_annos := typing.get_type_hints(init_subclass))
188188
and (ret_annotation := init_subclass_annos.get("return"))
189189
):
190190
# Substitute the cls type var with the current class
@@ -751,6 +751,38 @@ def _resolved_function_signature(func, receiver_type=None):
751751
return sig
752752

753753

754+
def _get_class_type_hint_namespaces(
755+
obj: type,
756+
) -> tuple[dict[str, typing.Any], dict[str, typing.Any]]:
757+
globalns: dict[str, typing.Any] = {}
758+
localns: dict[str, typing.Any] = {}
759+
760+
# Get module globals
761+
if obj.__module__ and (module := sys.modules.get(obj.__module__)):
762+
globalns.update(module.__dict__)
763+
764+
# Annotations may use typevars defined in the class
765+
localns.update(obj.__dict__)
766+
767+
if _typing_inspect.is_generic_alias(obj):
768+
# We need the origin's type vars
769+
localns.update(obj.__origin__.__dict__)
770+
771+
# Extract type parameters from the class
772+
args = typing.get_args(obj)
773+
origin = typing.get_origin(obj)
774+
tps = getattr(obj, '__type_params__', ()) or getattr(
775+
origin, '__parameters__', ()
776+
)
777+
for tp, arg in zip(tps, args, strict=False):
778+
localns[tp.__name__] = arg
779+
780+
# Add the class itself for self-references
781+
localns[obj.__name__] = obj
782+
783+
return globalns, localns
784+
785+
754786
def _get_function_hint_namespaces(func, receiver_type=None):
755787
globalns = {}
756788
localns = {}

typemap/type_eval/_eval_typing.py

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
if typing.TYPE_CHECKING:
2323
from typing import Any, Sequence
2424

25-
from . import _apply_generic, _typing_inspect
25+
from . import _apply_generic
2626

2727

2828
__all__ = ("eval_typing",)
@@ -282,59 +282,8 @@ def _eval_func(
282282
return _apply_generic.make_func(func, annos)
283283

284284

285-
def _get_class_type_hint_namespaces(
286-
obj: type,
287-
) -> tuple[dict[str, typing.Any], dict[str, typing.Any]]:
288-
globalns: dict[str, typing.Any] = {}
289-
localns: dict[str, typing.Any] = {}
290-
291-
# Get module globals
292-
if obj.__module__ and (module := sys.modules.get(obj.__module__)):
293-
globalns.update(module.__dict__)
294-
295-
# Annotations may use typevars defined in the class
296-
localns.update(obj.__dict__)
297-
298-
if _typing_inspect.is_generic_alias(obj):
299-
# We need the origin's type vars
300-
localns.update(obj.__origin__.__dict__)
301-
302-
# Extract type parameters from the class
303-
args = typing.get_args(obj)
304-
origin = typing.get_origin(obj)
305-
tps = getattr(obj, '__type_params__', ()) or getattr(
306-
origin, '__parameters__', ()
307-
)
308-
for tp, arg in zip(tps, args, strict=False):
309-
localns[tp.__name__] = arg
310-
311-
# Add the class itself for self-references
312-
localns[obj.__name__] = obj
313-
314-
return globalns, localns
315-
316-
317285
@_eval_types_impl.register
318286
def _eval_type_type(obj: type, ctx: EvalContext):
319-
# Ensure that any string annotations are resolved
320-
if (
321-
hasattr(obj, '__annotations__')
322-
and obj.__annotations__
323-
and any(isinstance(v, str) for v in obj.__annotations__.values())
324-
):
325-
# Ensure we don't recurse infinitely
326-
ctx.seen[obj] = obj
327-
328-
# Replace string annotations with resolved types
329-
globalns, localns = _get_class_type_hint_namespaces(obj)
330-
hints = {
331-
k: _eval_types(v, ctx)
332-
for k, v in typing.get_type_hints(
333-
obj, globalns=globalns, localns=localns, include_extras=True
334-
).items()
335-
}
336-
obj.__annotations__.update(hints)
337-
338287
return obj
339288

340289

0 commit comments

Comments
 (0)