diff --git a/asynq/batching.py b/asynq/batching.py index 25d76fe..1bb448a 100644 --- a/asynq/batching.py +++ b/asynq/batching.py @@ -20,7 +20,6 @@ from . import _debug from . import profiler - __traceback_hide__ = True _debug_options = _debug.options diff --git a/asynq/decorators.py b/asynq/decorators.py index 5961f0d..5adaf9e 100644 --- a/asynq/decorators.py +++ b/asynq/decorators.py @@ -15,7 +15,9 @@ import asyncio import inspect -from typing import Any, Coroutine +import logging +from collections.abc import Coroutine +from typing import Any import qcore.decorators import qcore.helpers as core_helpers @@ -25,6 +27,7 @@ from .asynq_to_async import AsyncioMode, is_asyncio_mode, resolve_awaitables __traceback_hide__ = True +logger = logging.getLogger("asynq") def lazy(fn): @@ -147,12 +150,13 @@ def is_pure_async_fn(self): class PureAsyncDecorator(qcore.decorators.DecoratorBase): binder_cls = PureAsyncDecoratorBinder - def __init__(self, fn, task_cls, kwargs={}, asyncio_fn=None): + def __init__(self, fn, task_cls, kwargs={}, asyncio_fn=None, allow_sync_call=False): qcore.decorators.DecoratorBase.__init__(self, fn) self.task_cls = task_cls self.needs_wrapper = core_inspection.is_cython_or_generator(fn) self.kwargs = kwargs self.asyncio_fn = asyncio_fn + self.allow_sync_call = allow_sync_call def name(self): return "@asynq(pure=True)" @@ -202,8 +206,8 @@ def asyncio(self, *args, **kwargs) -> Coroutine[Any, Any, Any]: class AsyncDecorator(PureAsyncDecorator): binder_cls = AsyncDecoratorBinder - def __init__(self, fn, cls, kwargs={}, asyncio_fn=None): - super().__init__(fn, cls, kwargs, asyncio_fn) + def __init__(self, fn, cls, kwargs={}, asyncio_fn=None, allow_sync_call=False): + super().__init__(fn, cls, kwargs, asyncio_fn, allow_sync_call) def is_pure_async_fn(self): return False @@ -216,9 +220,16 @@ def name(self): def __call__(self, *args, **kwargs): if is_asyncio_mode(): - raise RuntimeError("asyncio mode does not support synchronous calls") - - return self._call_pure(args, kwargs).value() + if self.allow_sync_call: + logger.warning( + f"asyncio mode does not support synchronous calls: {self.fn.__name__} at {inspect.getsourcefile(self.fn)}" + ) + else: + raise RuntimeError( + f"asyncio mode does not support synchronous calls: {self.fn.__name__} at {inspect.getsourcefile(self.fn)}" + ) + else: + return self._call_pure(args, kwargs).value() class AsyncAndSyncPairDecoratorBinder(AsyncDecoratorBinder): @@ -232,14 +243,24 @@ def __call__(self, *args, **kwargs): class AsyncAndSyncPairDecorator(AsyncDecorator): binder_cls = AsyncAndSyncPairDecoratorBinder - def __init__(self, fn, cls, sync_fn, kwargs={}, asyncio_fn=None): - AsyncDecorator.__init__(self, fn, cls, kwargs, asyncio_fn) + def __init__( + self, fn, cls, sync_fn, kwargs={}, asyncio_fn=None, allow_sync_call=False + ): + AsyncDecorator.__init__(self, fn, cls, kwargs, asyncio_fn, allow_sync_call) self.sync_fn = sync_fn def __call__(self, *args, **kwargs): if is_asyncio_mode(): - raise RuntimeError("asyncio mode does not support synchronous calls") - return self.sync_fn(*args, **kwargs) + if self.allow_sync_call: + logger.warning( + f"asyncio mode does not support synchronous calls: {self.fn.__name__} at {inspect.getsourcefile(self.fn)}" + ) + else: + raise RuntimeError( + f"asyncio mode does not support synchronous calls: {self.fn.__name__} at {inspect.getsourcefile(self.fn)}" + ) + else: + return self.sync_fn(*args, **kwargs) def __get__(self, owner, cls): # This is needed so that we can use objects with __get__ as the sync_fn. If we just rely on @@ -262,9 +283,11 @@ def __get__(self, owner, cls): class AsyncProxyDecorator(AsyncDecorator): - def __init__(self, fn, asyncio_fn=None): + def __init__(self, fn, asyncio_fn=None, allow_sync_call=False): # we don't need the task class but still need to pass it to the superclass - AsyncDecorator.__init__(self, fn, None, asyncio_fn=asyncio_fn) + AsyncDecorator.__init__( + self, fn, None, asyncio_fn=asyncio_fn, allow_sync_call=allow_sync_call + ) def asyncio(self, *args, **kwargs) -> Coroutine[Any, Any, Any]: if self.asyncio_fn is None: @@ -288,8 +311,10 @@ def _call_pure(self, args, kwargs): class AsyncAndSyncPairProxyDecorator(AsyncProxyDecorator): - def __init__(self, fn, sync_fn, asyncio_fn=None): - AsyncProxyDecorator.__init__(self, fn, asyncio_fn=asyncio_fn) + def __init__(self, fn, sync_fn, asyncio_fn=None, allow_sync_call=False): + AsyncProxyDecorator.__init__( + self, fn, asyncio_fn=asyncio_fn, allow_sync_call=allow_sync_call + ) self.sync_fn = sync_fn def __call__(self, *args, **kwargs): @@ -297,7 +322,12 @@ def __call__(self, *args, **kwargs): def asynq( - pure=False, sync_fn=None, cls=async_task.AsyncTask, asyncio_fn=None, **kwargs + pure=False, + sync_fn=None, + cls=async_task.AsyncTask, + asyncio_fn=None, + allow_sync_call=False, + **kwargs, ): """Async task decorator. Converts a method returning generator object to @@ -317,18 +347,23 @@ def decorate(fn): return qcore.decorators.decorate(PureAsyncDecorator, cls, kwargs)(fn) elif sync_fn is None: decorated = qcore.decorators.decorate( - AsyncDecorator, cls, kwargs, asyncio_fn + AsyncDecorator, cls, kwargs, asyncio_fn, allow_sync_call )(fn) return decorated else: return qcore.decorators.decorate( - AsyncAndSyncPairDecorator, cls, sync_fn, kwargs, asyncio_fn + AsyncAndSyncPairDecorator, + cls, + sync_fn, + kwargs, + asyncio_fn, + allow_sync_call, )(fn) return decorate -def async_proxy(pure=False, sync_fn=None, asyncio_fn=None): +def async_proxy(pure=False, sync_fn=None, asyncio_fn=None, allow_sync_call=False): if sync_fn is not None: assert pure is False, "sync_fn=? cannot be used together with pure=True" @@ -339,7 +374,7 @@ def decorate(fn): return qcore.decorators.decorate(AsyncProxyDecorator, asyncio_fn)(fn) else: return qcore.decorators.decorate( - AsyncAndSyncPairProxyDecorator, sync_fn, asyncio_fn + AsyncAndSyncPairProxyDecorator, sync_fn, asyncio_fn, allow_sync_call )(fn) return decorate diff --git a/asynq/decorators.pyi b/asynq/decorators.pyi index bbf7dfb..c9d9ecd 100644 --- a/asynq/decorators.pyi +++ b/asynq/decorators.pyi @@ -46,6 +46,7 @@ class PureAsyncDecorator(qcore.decorators.DecoratorBase, Generic[_T, _P]): task_cls: Optional[type[futures.FutureBase]], kwargs: Mapping[str, Any] = ..., asyncio_fn: Optional[Callable[_P, Coroutine[Any, Any, _T]]] = ..., + allow_sync_call: bool = ..., ) -> None: ... @overload def __init__( @@ -54,6 +55,7 @@ class PureAsyncDecorator(qcore.decorators.DecoratorBase, Generic[_T, _P]): task_cls: Optional[type[futures.FutureBase]], kwargs: Mapping[str, Any] = ..., asyncio_fn: Optional[Callable[_P, Coroutine[Any, Any, _T]]] = ..., + allow_sync_call: bool = ..., ) -> None: ... def name(self) -> str: ... def is_pure_async_fn(self) -> bool: ... @@ -84,6 +86,7 @@ class AsyncDecorator(PureAsyncDecorator[_T, _P]): cls: Optional[type[futures.FutureBase]], kwargs: Mapping[str, Any] = ..., asyncio_fn: Optional[Callable[_P, Coroutine[Any, Any, _T]]] = ..., + allow_sync_call: bool = ..., ): ... @overload def __init__( @@ -92,6 +95,7 @@ class AsyncDecorator(PureAsyncDecorator[_T, _P]): cls: Optional[type[futures.FutureBase]], kwargs: Mapping[str, Any] = ..., asyncio_fn: Optional[Callable[_P, Coroutine[Any, Any, _T]]] = ..., + allow_sync_call: bool = ..., ): ... def is_pure_async_fn(self) -> bool: ... def asynq(self, *args: Any, **kwargs: Any) -> async_task.AsyncTask[_T]: ... @@ -117,6 +121,7 @@ class AsyncProxyDecorator(AsyncDecorator[_T, _P]): self, fn: Callable[..., futures.FutureBase[_T]], asyncio_fn: Optional[Callable[..., Coroutine[Any, Any, _T]]] = ..., + allow_sync_call: bool = ..., ) -> None: ... class AsyncAndSyncPairProxyDecorator(AsyncProxyDecorator[_T, _P]): @@ -125,6 +130,7 @@ class AsyncAndSyncPairProxyDecorator(AsyncProxyDecorator[_T, _P]): fn: Callable[..., futures.FutureBase[_T]], sync_fn: Callable[..., _T], asyncio_fn: Optional[Callable[..., Coroutine[Any, Any, _T]]] = ..., + allow_sync_call: bool = ..., ) -> None: ... def __call__(self, *args: Any, **kwargs: Any) -> _T: ... @@ -142,6 +148,7 @@ def asynq( sync_fn: Optional[Callable[_P, _T]] = ..., cls: type[futures.FutureBase] = ..., asyncio_fn: Optional[Callable[_P, Coroutine[Any, Any, _T]]] = ..., + allow_sync_call: bool = ..., **kwargs: Any, ) -> _MkAsyncDecorator: ... @overload @@ -151,6 +158,7 @@ def asynq( sync_fn: Optional[Callable[_P, _T]] = ..., cls: type[futures.FutureBase] = ..., asyncio_fn: Optional[Callable[_P, Coroutine[Any, Any, _T]]] = ..., + allow_sync_call: bool = ..., **kwargs: Any, ) -> _MkPureAsyncDecorator: ... @overload diff --git a/asynq/futures.py b/asynq/futures.py index 7b5cccd..a45454f 100644 --- a/asynq/futures.py +++ b/asynq/futures.py @@ -21,7 +21,6 @@ from . import debug from . import _debug - __traceback_hide__ = True diff --git a/asynq/scoped_value.py b/asynq/scoped_value.py index f8eb332..bac7e25 100644 --- a/asynq/scoped_value.py +++ b/asynq/scoped_value.py @@ -28,7 +28,6 @@ from . import contexts - _empty_context = qcore.empty_context diff --git a/asynq/tests/debug_cache.py b/asynq/tests/debug_cache.py index c6a32c7..9da4ab6 100644 --- a/asynq/tests/debug_cache.py +++ b/asynq/tests/debug_cache.py @@ -16,7 +16,6 @@ from asynq import scheduler from .caching import ExternalCacheBase, LocalCache - # Caches diff --git a/asynq/tests/test_channels.py b/asynq/tests/test_channels.py index 7512a88..303f33a 100644 --- a/asynq/tests/test_channels.py +++ b/asynq/tests/test_channels.py @@ -16,7 +16,6 @@ from asynq import asynq, async_proxy, ConstFuture from collections import deque - # TODO(alex): finish w/this test diff --git a/asynq/tests/test_contexts.py b/asynq/tests/test_contexts.py index ad4a43f..f3bb0a6 100644 --- a/asynq/tests/test_contexts.py +++ b/asynq/tests/test_contexts.py @@ -19,7 +19,6 @@ from .debug_cache import mc from .caching import ExternalCacheBatchItem - current_context = None change_amount = 0 diff --git a/asynq/tests/test_multiple_inheritance.py b/asynq/tests/test_multiple_inheritance.py index 3f46149..d2930bc 100644 --- a/asynq/tests/test_multiple_inheritance.py +++ b/asynq/tests/test_multiple_inheritance.py @@ -22,7 +22,6 @@ from asynq import asynq - called = {} diff --git a/asynq/utils.py b/asynq/utils.py index 8ec36fa..3f94da2 100644 --- a/asynq/utils.py +++ b/asynq/utils.py @@ -16,7 +16,6 @@ from . import async_task from . import _debug - _debug_options = _debug.options