def cache_across_reloads[T](func: Callable[[], T]) -> Callable[[], T]:
file = getsourcefile(func)
assert file is not None
module = ReactiveModule.instances.get(path := Path(file).resolve())
if module is None:
from functools import cache
return cache(func)
source, col_offset = dedent(getsource(func))
key = (path, func.__qualname__)
proxy: NamespaceProxy = module._ReactiveModule__namespace_proxy # type: ignore
flags: int = module._ReactiveModule__flags # type: ignore
skip_annotations = ABOVE_3_14 or is_future_annotations_enabled(flags)
global _cache_decorator_phase
_cache_decorator_phase = not _cache_decorator_phase
if _cache_decorator_phase: # this function will be called twice: once transforming ast and once re-executing the patched source
on_dispose(lambda: functions.pop(key), file)
try:
exec(compile(fix_class_name_resolution(parse(source), func.__code__.co_firstlineno - 1, col_offset, skip_annotations), file, "exec", flags, dont_inherit=True), DictProxy(proxy))
except _Return as e:
# If this function is used as a decorator, it will raise an `_Return` exception in the second phase.
return e.value
else:
# Otherwise, it is used as a function, and we need to do the second phase ourselves.
func = proxy[func.__name__]
func = FunctionType(func.__code__, DictProxy(proxy), func.__name__, func.__defaults__, func.__closure__)
functions[key] = func
if result := memos.get(key):
memo, last_source = result
if source != last_source:
Derived.invalidate(memo) # type: ignore
memos[key] = memo, source
return _return(wraps(func)(memo))
@wraps(func)
def wrapper() -> T:
return functions[key]()
memo = Derived(wrapper, context=HMR_CONTEXT)
memo.reactivity_loss_strategy = "ignore" # Manually invalidated on source change, so reactivity loss is safe to ignore
memos[key] = memo, source
return _return(wraps(func)(memo))