diff --git a/a_sync/_meta.py b/a_sync/_meta.py index 9a770755..4607f388 100644 --- a/a_sync/_meta.py +++ b/a_sync/_meta.py @@ -5,6 +5,7 @@ from typing import Any, Dict, Tuple from a_sync import ENVIRONMENT_VARIABLES, _bound, modifiers +from a_sync.future import _ASyncFutureWrappedFn from a_sync.modified import ASyncFunction, Modified from a_sync.property import PropertyDescriptor @@ -14,20 +15,23 @@ class ASyncMeta(ABCMeta): """Any class with metaclass ASyncMeta will have its functions wrapped with a_sync upon class instantiation.""" def __new__(cls, new_class_name, bases, attrs): _update_logger(new_class_name) - logger.debug(f"woah, you're defining a new ASync class `{new_class_name}`! let's walk thru it together") - logger.debug(f"first, I check whether you've defined any modifiers on `{new_class_name}`") + logger.debug(f"woah, you're defining a new ASync class `%s`! let's walk thru it together", new_class_name) + logger.debug(f"first, I check whether you've defined any modifiers on `%s`", new_class_name) # NOTE: Open uesion: what do we do when a parent class and subclass define the same modifier differently? # Currently the parent value is used for functions defined on the parent, # and the subclass value is used for functions defined on the subclass. class_defined_modifiers = modifiers.get_modifiers_from(attrs) - logger.debug(f'found modifiers: {class_defined_modifiers}') + logger.debug(f'found modifiers: %s', class_defined_modifiers) logger.debug("now I inspect the class definition to figure out which attributes need to be wrapped") for attr_name, attr_value in list(attrs.items()): if attr_name.startswith("_"): - logger.debug(f"`{new_class_name}.{attr_name}` starts with an underscore, skipping") + logger.debug(f"`%s.%s` starts with an underscore, skipping", new_class_name, attr_name) continue elif "__" in attr_name: - logger.debug(f"`{new_class_name}.{attr_name}` incluldes a double-underscore, skipping") + logger.debug(f"`%s.%s` incluldes a double-underscore, skipping", new_class_name, attr_name) + continue + elif isinstance(attr_value, _ASyncFutureWrappedFn): + logger.debug(f"`%s.%s` is a %s, skipping", new_class_name, attr_name, attr_value.__class__.__name__) continue logger.debug(f"inspecting `{new_class_name}.{attr_name}` of type {attr_value.__class__.__name__}") fn_modifiers = dict(class_defined_modifiers) diff --git a/a_sync/future.py b/a_sync/future.py index 5ee37cec..2edce943 100644 --- a/a_sync/future.py +++ b/a_sync/future.py @@ -2,20 +2,20 @@ import asyncio from decimal import Decimal -from functools import wraps +from functools import partial, wraps from typing import (Any, Awaitable, Callable, List, Set, TypeVar, Union, overload) from typing_extensions import ParamSpec, Unpack -from a_sync import a_sync +from a_sync._typing import ModifierKwargs T = TypeVar('T') P = ParamSpec('P') MaybeMeta = Union[T, "ASyncFuture[T]"] -def future(callable: Union[Callable[P, Awaitable[T]], Callable[P, T]], **kwargs) -> Callable[P, "ASyncFuture[T]"]: - return ASyncFuture.wrap_callable(callable, **kwargs) +def future(callable: Union[Callable[P, Awaitable[T]], Callable[P, T]] = None, **kwargs: Unpack[ModifierKwargs]) -> Callable[P, "ASyncFuture[T]"]: + return _ASyncFutureWrappedFn(callable, **kwargs) async def _gather_check_and_materialize(*things: Unpack[MaybeMeta[T]]) -> List[T]: return await asyncio.gather(*[_check_and_materialize(thing) for thing in things]) @@ -33,8 +33,6 @@ def _materialize(meta: "ASyncFuture[T]") -> T: retval = asyncio.get_event_loop().run_until_complete(meta._awaitable) meta.set_result(retval) meta._done.set() - print(f'dependencies: {meta.dependencies}') - print(f'dependants: {meta.dependants}') return retval except RuntimeError as e: raise RuntimeError(f"{meta} result is not set and the event loop is running, you will need to await it first") from e @@ -43,7 +41,6 @@ def _materialize(meta: "ASyncFuture[T]") -> T: class ASyncFuture(asyncio.Future, Awaitable[T]): def __init__(self, awaitable: Awaitable[T], dependencies: List["ASyncFuture"] = []) -> None: - #print(awaitable) self._awaitable = awaitable self._dependencies = dependencies for dependency in dependencies: @@ -87,13 +84,6 @@ def result(self) -> Union[Callable[[], T], Any]: return r.result # the result should be callable like an asyncio.Future return super().result - @classmethod - def wrap_callable(cls, callable: Union[Callable[P, Awaitable[T]], Callable[P, T]], **kwargs) -> Callable[P, "ASyncFuture[T]"]: - callable = a_sync(callable, **kwargs) - @wraps(callable) - def future_wrap(*args: P.args, **kwargs: P.kwargs) -> "ASyncFuture[T]": - return cls(callable(*args, **kwargs, sync=False)) - return future_wrap def __getattr__(self, attr: str) -> Any: return getattr(_materialize(self), attr) def __getitem__(self, key) -> Any: @@ -121,9 +111,6 @@ async def __await(self) -> T: self._started = True self.set_result(await self._awaitable) self._done.set() - print(f'self {self.__repr__()}') - print(f'dependencies: {self.dependencies}') - print(f'dependants: {self.dependants}') return self._result def __iter__(self): return _materialize(self).__iter__() @@ -496,3 +483,21 @@ def __int__(self) -> int: return int(_materialize(self)) def __float__(self) -> float: return float(_materialize(self)) + +class _ASyncFutureWrappedFn(Callable[P, ASyncFuture[T]]): + __slots__ = "callable", "wrapped" + def __init__(self, callable: Union[Callable[P, Awaitable[T]], Callable[P, T]] = None, **kwargs: Unpack[ModifierKwargs]): + from a_sync import a_sync + if callable: + self.callable = callable + a_sync_callable = a_sync(callable, default="async", **kwargs) + @wraps(callable) + def future_wrap(*args: P.args, **kwargs: P.kwargs) -> "ASyncFuture[T]": + return ASyncFuture(a_sync_callable(*args, **kwargs, sync=False)) + self.wrapped = future_wrap + else: + self.wrapped = partial(_ASyncFutureWrappedFn, **kwargs) + def __call__(self, *args: P.args, **kwargs: P.kwargs) -> ASyncFuture[T]: + return self.wrapped(*args, **kwargs) + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.callable}>"