Source code for meerkat.interactive.graph.magic

from functools import wraps
from typing import Any, Callable, List, TypeVar, cast

from meerkat.interactive.graph.marking import unmarked
from meerkat.interactive.graph.reactivity import reactive

# Used for annotating decorator usage of 'react'.
# Adapted from PyTorch:
# https://mypy.readthedocs.io/en/latest/generics.html#declaring-decorators
FuncType = Callable[..., Any]
F = TypeVar("F", bound=FuncType)
T = TypeVar("T")


_IS_MAGIC_CONTEXT: List[bool] = []
_MAGIC_FN = "magic"


def _magic(fn: Callable) -> Callable:
    """Internal decorator that is used to mark a function with a wand. Wand
    functions can be activated when the magic context is active (`with
    magic:`).

    When the magic context is active, the function will be wrapped in
    `reactive` and will be executed as a reactive function.

    When the magic context is not active, the function will be effectively run
    with unmarked inputs (i.e. `with unmarked():`). This means that the function
    will not be added to the graph. The return value will be marked and returned.
    This allows the return value to be used as a marked element in the future.

    This is only meant for internal use.

    with mk.unmarked():
        with mk.magic():
            s = s + 3   # s.__add__ is a wand
    """

    def __wand(fn: Callable):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            """Wrapper function that wraps the function in `reactive` if the
            magic context is active, and just wraps with `mark` otherwise."""

            # Check if magic context is active.
            if is_magic_context():
                # If active, we wrap with `reactive` and return.
                return reactive(fn, nested_return=False)(*args, **kwargs)
            else:
                # Just wrap with `mark` and return.
                with unmarked():
                    out = reactive(fn, nested_return=False)(*args, **kwargs)
                    # out = mark(out)
                return out

        # setattr(wrapper, "__wrapper__", _MAGIC_FN)
        return wrapper

    return __wand(fn)


[docs]class magic: """A context manager and decorator that changes the behavior of Store objects inside it. All methods, properties and public attributes of Store objects will be wrapped in @reactive decorators. Examples: """ def __init__(self, magic: bool = True) -> None: self._magic = magic def __call__(self, func): @wraps(func) def decorate_context(*args, **kwargs): with self.clone(): return func(*args, **kwargs) setattr(decorate_context, "__wrapper__", _MAGIC_FN) return cast(F, decorate_context) def __enter__(self): _IS_MAGIC_CONTEXT.append(self._magic) return self def __exit__(self, type, value, traceback): _IS_MAGIC_CONTEXT.pop(-1) def clone(self): return self.__class__(self._magic)
def is_magic_context() -> bool: """Whether the code is in a magic context. Returns: bool: True if the code is in a magic context. """ # By default, we should not assume we are in a magic context. # This will mean that Store objects will default to not decorating # their methods and properties with @reactive. if len(_IS_MAGIC_CONTEXT) == 0: return False # Otherwise, we check if the user has explicitly used the # `magic` context manager or decorator. return len(_IS_MAGIC_CONTEXT) > 0 and bool(_IS_MAGIC_CONTEXT[-1])