From d7d23360df2d66308ae0c34ad7f7f2d624663fcd Mon Sep 17 00:00:00 2001 From: William Barnhart Date: Fri, 29 Mar 2024 18:27:23 -0400 Subject: [PATCH 1/4] Fix evaluation of types for typing classes --- mode/utils/objects.py | 58 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/mode/utils/objects.py b/mode/utils/objects.py index 8a51580..62d0e4f 100644 --- a/mode/utils/objects.py +++ b/mode/utils/objects.py @@ -29,10 +29,25 @@ Tuple, Type, TypeVar, - _eval_type, cast, ) +try: + from typing import _eval_type # type: ignore +except ImportError: + + def _eval_type(t, globalns, localns, recursive_guard=frozenset()): # type: ignore + return t + + +try: + from typing import _type_check # type: ignore +except ImportError: + + def _type_check(arg, msg, is_argument=True, module=None): # type: ignore + return arg + + try: from typing import _ClassVar # type: ignore except ImportError: # pragma: no cover @@ -47,6 +62,25 @@ def _is_class_var(x: Any) -> bool: def _is_class_var(x: Any) -> bool: return type(x) is _ClassVar +if typing.TYPE_CHECKING: + + class ForwardRef: # noqa + __forward_arg__: str + __forward_evaluated__: bool + __forward_value__: Type + __forward_code__: Any + + def __init__(self, arg: str, is_argument: bool = True) -> None: ... + +else: + try: + # CPython 3.7 + from typing import ForwardRef + except ImportError: # pragma: no cover + # CPython 3.6 + from typing import _ForwardRef as ForwardRef + + __all__ = [ "FieldMapping", @@ -374,7 +408,9 @@ def eval_type( if isinstance(typ, str): typ = ForwardRef(typ) if isinstance(typ, ForwardRef): - if sys.version_info < (3, 9): + if not typ.__forward_evaluated__: + typ = _ForwardRef_safe_eval(typ, globalns, localns) + elif sys.version_info < (3, 9): typ = typ._evaluate(globalns, localns) else: typ = typ._evaluate(globalns, localns, frozenset()) @@ -383,6 +419,24 @@ def eval_type( raise InvalidAnnotation(typ) return alias_types.get(typ, typ) +def _ForwardRef_safe_eval( + ref: ForwardRef, globalns: Dict[str, Any] = None, localns: Dict[str, Any] = None +) -> Type: + # On 3.6/3.7 ForwardRef._evaluate crashes if str references ClassVar + if not ref.__forward_evaluated__: + if globalns is None and localns is None: + globalns = localns = {} + elif globalns is None: + globalns = localns + elif localns is None: + localns = globalns + val = eval(ref.__forward_code__, globalns, localns) # noqa: S307 + if not _is_class_var(val): + val = _type_check(val, "Forward references must evaluate to types.") + ref.__forward_value__ = val + ref.__forward_evaluated__ = True + return ref.__forward_value__ + def _get_globalns(typ: Type) -> Dict[str, Any]: return sys.modules[typ.__module__].__dict__ From 06c66c355bce6edf84b99dc01b6174d60b692483 Mon Sep 17 00:00:00 2001 From: William Barnhart Date: Fri, 29 Mar 2024 18:28:32 -0400 Subject: [PATCH 2/4] Discard future after awaiting --- mode/services.py | 2 +- mode/threads.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mode/services.py b/mode/services.py index e8850f1..8e01a17 100644 --- a/mode/services.py +++ b/mode/services.py @@ -695,7 +695,7 @@ def human_tracebacks(self) -> str: ) def _on_future_done(self, fut: asyncio.Future) -> None: - self._futures.discard(fut) + return self._futures.discard(fut) def __post_init__(self) -> None: """Additional user initialization.""" diff --git a/mode/threads.py b/mode/threads.py index 2dd0aeb..a11cbbd 100644 --- a/mode/threads.py +++ b/mode/threads.py @@ -169,7 +169,7 @@ async def start(self) -> None: assert not self._thread_started.is_set() self._thread_started.set() self._thread_running = asyncio.Future(loop=self.parent_loop) - self.add_future(self._keepalive2()) + fut = self.add_future(self._keepalive2()) try: self._thread = self.Worker(self) self._thread.start() @@ -183,6 +183,7 @@ async def start(self) -> None: # wait for thread to be fully started await self._thread_running + await fut finally: self._thread_running = None From cb3c112ec8a5533b610d68a9931835d212fb5383 Mon Sep 17 00:00:00 2001 From: William Barnhart Date: Fri, 29 Mar 2024 18:30:01 -0400 Subject: [PATCH 3/4] properly type and lint --- mode/utils/objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mode/utils/objects.py b/mode/utils/objects.py index 62d0e4f..4fa58ca 100644 --- a/mode/utils/objects.py +++ b/mode/utils/objects.py @@ -64,7 +64,7 @@ def _is_class_var(x: Any) -> bool: if typing.TYPE_CHECKING: - class ForwardRef: # noqa + class ForwardRef: __forward_arg__: str __forward_evaluated__: bool __forward_value__: Type @@ -420,7 +420,7 @@ def eval_type( return alias_types.get(typ, typ) def _ForwardRef_safe_eval( - ref: ForwardRef, globalns: Dict[str, Any] = None, localns: Dict[str, Any] = None + ref: ForwardRef, globalns: Optional[Dict[str, Any]] = None, localns: Optional[Dict[str, Any]] = None ) -> Type: # On 3.6/3.7 ForwardRef._evaluate crashes if str references ClassVar if not ref.__forward_evaluated__: From ddb8b91827cc86828d804e5b223abf3b8bec8e9b Mon Sep 17 00:00:00 2001 From: William Barnhart Date: Fri, 29 Mar 2024 18:32:12 -0400 Subject: [PATCH 4/4] fix formatting --- mode/utils/objects.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/mode/utils/objects.py b/mode/utils/objects.py index 4fa58ca..3745d88 100644 --- a/mode/utils/objects.py +++ b/mode/utils/objects.py @@ -62,6 +62,7 @@ def _is_class_var(x: Any) -> bool: def _is_class_var(x: Any) -> bool: return type(x) is _ClassVar + if typing.TYPE_CHECKING: class ForwardRef: @@ -81,7 +82,6 @@ def __init__(self, arg: str, is_argument: bool = True) -> None: ... from typing import _ForwardRef as ForwardRef - __all__ = [ "FieldMapping", "DefaultsMapping", @@ -419,8 +419,11 @@ def eval_type( raise InvalidAnnotation(typ) return alias_types.get(typ, typ) + def _ForwardRef_safe_eval( - ref: ForwardRef, globalns: Optional[Dict[str, Any]] = None, localns: Optional[Dict[str, Any]] = None + ref: ForwardRef, + globalns: Optional[Dict[str, Any]] = None, + localns: Optional[Dict[str, Any]] = None, ) -> Type: # On 3.6/3.7 ForwardRef._evaluate crashes if str references ClassVar if not ref.__forward_evaluated__: @@ -432,7 +435,9 @@ def _ForwardRef_safe_eval( localns = globalns val = eval(ref.__forward_code__, globalns, localns) # noqa: S307 if not _is_class_var(val): - val = _type_check(val, "Forward references must evaluate to types.") + val = _type_check( + val, "Forward references must evaluate to types." + ) ref.__forward_value__ = val ref.__forward_evaluated__ = True return ref.__forward_value__