def decorator(fn): cache = LRUCache(maxsize) argspec = inspect2.getfullargspec(get_original_fn(fn)) arg_names = argspec.args[1:] + argspec.kwonlyargs # remove self async_fun = fn.asynq kwargs_defaults = get_kwargs_defaults(argspec) cache_key = key_fn if cache_key is None: def cache_key(args, kwargs): return get_args_tuple(args, kwargs, arg_names, kwargs_defaults) @asynq() @functools.wraps(fn) def wrapper(*args, **kwargs): key = cache_key(args, kwargs) try: result(cache[key]) return except KeyError: value = yield async_fun(*args, **kwargs) cache[key] = value result(value) return return wrapper
def test_retry_preserves_argspec(self): def fn(foo, bar, baz=None, **kwargs): pass decorated = aretry(Exception)(fn) assert_eq(inspect.getargspec(fn), inspect.getargspec(get_original_fn(decorated)))
def decorator(fun): _keygetter = keygetter if _keygetter is None: original_fn = get_original_fn(fun) argspec = inspect2.getfullargspec(original_fn) arg_names = argspec.args + argspec.kwonlyargs kwargs_defaults = get_kwargs_defaults(argspec) _keygetter = lambda args, kwargs: get_args_tuple( args, kwargs, arg_names, kwargs_defaults) return decorate(DeduplicateDecorator, fun.task_cls, _keygetter)(fun)
def cache_fun(fun): argspec = inspect2.getfullargspec(get_original_fn(fun)) arg_names = argspec.args[1:] + argspec.kwonlyargs # remove self async_fun = fun.asynq kwargs_defaults = get_kwargs_defaults(argspec) cache = {} def cache_key(args, kwargs): return get_args_tuple(args, kwargs, arg_names, kwargs_defaults) def clear_cache(instance_key, ref): del cache[instance_key] @async_proxy() @functools.wraps(fun) def new_fun(self, *args, **kwargs): instance_key = id(self) if instance_key not in cache: ref = weakref.ref(self, functools.partial(clear_cache, instance_key)) cache[instance_key] = (ref, {}) instance_cache = cache[instance_key][1] k = cache_key(args, kwargs) try: return ConstFuture(instance_cache[k]) except KeyError: def callback(task): instance_cache[k] = task.value() task = async_fun(self, *args, **kwargs) task.on_computed.subscribe(callback) return task # just so unit tests can check that this is cleaned up correctly new_fun.__acached_per_instance_cache__ = cache return new_fun
def cache_fun(fun): argspec = inspect2.getfullargspec(get_original_fn(fun)) arg_names = argspec.args[1:] + argspec.kwonlyargs # remove self async_fun = fun.asynq kwargs_defaults = get_kwargs_defaults(argspec) cache = {} def cache_key(args, kwargs): return get_args_tuple(args, kwargs, arg_names, kwargs_defaults) def clear_cache(instance_key, ref): del cache[instance_key] @asynq() @functools.wraps(fun) def new_fun(self, *args, **kwargs): instance_key = id(self) if instance_key not in cache: ref = weakref.ref(self, functools.partial(clear_cache, instance_key)) cache[instance_key] = (ref, {}) instance_cache = cache[instance_key][1] k = cache_key(args, kwargs) try: result(instance_cache[k]) return except KeyError: value = yield async_fun(self, *args, **kwargs) instance_cache[k] = value result(value) return # just so unit tests can check that this is cleaned up correctly new_fun.__acached_per_instance_cache__ = cache return new_fun
def _uncached_get_argspec(self, obj: Any, impl: Optional[Impl], is_asynq: bool) -> MaybeSignature: if isinstance(obj, tuple) or hasattr(obj, "__getattr__"): return None # lost cause # Cythonized methods, e.g. fn.asynq if is_dot_asynq_function(obj): try: return self._cached_get_argspec(obj.__self__, impl, is_asynq) except TypeError: # some cythonized methods have __self__ but it is not a function pass # for bound methods, see if we have an argspec for the unbound method if inspect.ismethod(obj) and obj.__self__ is not None: argspec = self._cached_get_argspec(obj.__func__, impl, is_asynq) return make_bound_method(argspec, KnownValue(obj.__self__)) if hasattr(obj, "fn") or hasattr(obj, "original_fn"): is_asynq = is_asynq or hasattr(obj, "asynq") # many decorators put the original function in the .fn attribute try: original_fn = qcore.get_original_fn(obj) except (TypeError, AttributeError): # fails when executed on an object that doesn't allow setting attributes, # e.g. certain extension classes pass else: return self._cached_get_argspec(original_fn, impl, is_asynq) argspec = self.ts_finder.get_argspec(obj) if argspec is not None: return argspec if inspect.isfunction(obj): if hasattr(obj, "inner"): # @qclient.task_queue.exec_after_request() puts the original function in .inner return self._cached_get_argspec(obj.inner, impl, is_asynq) # NewTypes, but we don't currently know how to handle NewTypes over more # complicated types. if hasattr(obj, "__supertype__") and isinstance( obj.__supertype__, type): # NewType return Signature.make( [ SigParameter( "x", SigParameter.POSITIONAL_ONLY, annotation=type_from_runtime( obj.__supertype__, ctx=self.default_context), ) ], NewTypeValue(obj), callable=obj, ) inspect_sig = self._safe_get_signature(obj) if inspect_sig is None: return self._make_any_sig(obj) return self.from_signature( inspect_sig, function_object=obj, is_async=asyncio.iscoroutinefunction(obj), impl=impl, is_asynq=is_asynq, ) # decorator binders if _is_qcore_decorator(obj): argspec = self._cached_get_argspec(obj.decorator, impl, is_asynq) # wrap if it's a bound method if obj.instance is not None and argspec is not None: return make_bound_method(argspec, KnownValue(obj.instance)) return argspec if inspect.isclass(obj): obj = self.config.unwrap_cls(obj) override = self.config.get_constructor(obj) if isinstance(override, Signature): signature = override else: should_ignore = safe_in(obj, self.config.IGNORED_CALLEES) return_type = UNRESOLVED_VALUE if should_ignore else TypedValue( obj) allow_call = safe_issubclass( obj, self.config.CLASSES_SAFE_TO_INSTANTIATE) if isinstance(override, inspect.Signature): inspect_sig = override else: if override is not None: constructor = override elif hasattr(obj, "__init__"): constructor = obj.__init__ else: # old-style class return Signature.make( [], return_type, is_ellipsis_args=True, callable=obj, allow_call=allow_call, ) inspect_sig = self._safe_get_signature(constructor) if inspect_sig is None: return Signature.make( [], return_type, is_ellipsis_args=True, callable=obj, allow_call=allow_call, ) signature = self.from_signature( inspect_sig, function_object=obj, impl=impl, returns=return_type, allow_call=allow_call, ) bound_sig = make_bound_method(signature, TypedValue(obj)) if bound_sig is None: return None sig = bound_sig.get_signature(preserve_impl=True) if sig is not None: return sig return bound_sig if inspect.isbuiltin(obj): if hasattr(obj, "__self__") and not isinstance( obj.__self__, ModuleType): cls = type(obj.__self__) try: method = getattr(cls, obj.__name__) except AttributeError: return self._make_any_sig(obj) if method == obj: return self._make_any_sig(obj) argspec = self._cached_get_argspec(method, impl, is_asynq) return make_bound_method(argspec, KnownValue(obj.__self__)) inspect_sig = self._safe_get_signature(obj) if inspect_sig is not None: return self.from_signature(inspect_sig, function_object=obj) return self._make_any_sig(obj) if hasattr(obj, "__call__"): # we could get an argspec here in some cases, but it's impossible to figure out # the argspec for some builtin methods (e.g., dict.__init__), and no way to detect # these with inspect, so just give up. return self._make_any_sig(obj) if isinstance(obj, property): # If we know the getter, inherit its return value. if obj.fget: fget_argspec = self._cached_get_argspec( obj.fget, impl, is_asynq) if fget_argspec is not None and fget_argspec.has_return_value( ): return PropertyArgSpec( obj, return_value=fget_argspec.return_value) return PropertyArgSpec(obj) return None