Source code for reapy.tools._inside_reaper

import contextlib
import functools
import importlib

import reapy
import reapy.config
from reapy.errors import DisabledDistAPIError, DisabledDistAPIWarning
from .network import machines
# if not reapy.is_inside_reaper():
#     try:
#         from .network import Client, WebInterface
#         _WEB_INTERFACE = WebInterface(reapy.config.WEB_INTERFACE_PORT)
#         _CLIENT = Client(_WEB_INTERFACE.get_reapy_server_port())
#     except DisabledDistAPIError:
#         import warnings
#         warnings.warn(DisabledDistAPIWarning())
#         _CLIENT = None


def dist_api_is_enabled():
    """Return whether reapy can reach REAPER from the outside."""
    return machines.get_selected_client() is not None


class inside_reaper(contextlib.ContextDecorator):

    """
    Context manager for efficient calls from outside REAPER.

    It can also be used as a function decorator.

    Examples
    --------
    Instead of running:

    >>> project = reapy.Project()
    >>> l = [project.bpm for i in range(1000)

    which takes around 30 seconds, run:

    >>> project = reapy.Project()
    >>> with reapy.inside_reaper():
    ...     l = [project.bpm for i in range(1000)
    ...

    which takes 0.1 seconds!

    Example usage as decorator:

    >>> @reapy.inside_reaper()
    ... def add_n_tracks(n):
    ...     for x in range(n):
    ...         reapy.Project().add_track()

    """

    def __call__(self, func, encoded_func=None):
        if reapy.is_inside_reaper():
            return func
        if isinstance(func, property):
            return DistProperty.from_property(func)
        # Check if the decorated function is from reapy
        module_name = func.__module__
        if module_name == 'reapy' or module_name.startswith('reapy.'):
            @functools.wraps(func)
            def wrap(*args, **kwargs):
                f = func if encoded_func is None else encoded_func
                client = machines.get_selected_client()
                return client.request(f, {"args": args, "kwargs": kwargs})
            return wrap
        # Otherwise, use the context manager
        return super().__call__(func)

    def __enter__(self):
        if not reapy.is_inside_reaper():
            machines.get_selected_client().request("HOLD")

    def __exit__(self, exc_type, exc_val, exc_tb):
        if not reapy.is_inside_reaper():
            machines.get_selected_client().request("RELEASE")
        return False


class DistProperty(property):

    _inside_reaper = inside_reaper()

    @classmethod
    def from_property(cls, p):
        return cls().getter(p.fget).setter(p.fset).deleter(p.fdel)

    @staticmethod
    def _encode(f, method_name):
        return {
            "__callable__": True,
            "module_name": f.__module__,
            "name": "{}.f{}".format(f.__qualname__, method_name)
        }

    def getter(self, fget):
        if fget is not None:
            fget = self._inside_reaper(fget, self._encode(fget, "get"))
        return super().getter(fget)

    def setter(self, fset):
        if fset is not None:
            fset = self._inside_reaper(fset, self._encode(fset, "set"))
        return super().setter(fset)

    def deleter(self, fdel):
        if fdel is not None:
            fdel = self._inside_reaper(fdel, self._encode(fdel, "del"))
        return super().deleter(fdel)