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 diff --git a/mode/utils/objects.py b/mode/utils/objects.py index 8a51580..3745d88 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 @@ -48,6 +63,25 @@ def _is_class_var(x: Any) -> bool: return type(x) is _ClassVar +if typing.TYPE_CHECKING: + + class ForwardRef: + __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", "DefaultsMapping", @@ -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()) @@ -384,6 +420,29 @@ def eval_type( return alias_types.get(typ, typ) +def _ForwardRef_safe_eval( + 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__: + 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__