Source

dynscope / dynscope / fluids.py

import threading

class DynamicEnvironment(threading.local):
    """
    This class implements the dynamic environment on top
    of the threadlocal class. The functionality that
    is added by this class is efficient linking of
    environments so you can produce sub-environments where
    outside variables can be shadowed and assignments and
    lookups still go to the right environment.

    This means that if you want to shadow a global variable,
    you have to explicitely localize it in the new subenvironment.
    """

    def __init__(self, parent=None):
        """
        Initialize a new environment that can be linked to a
        parent environment.
        """
        self._parent = parent

    def _update(self, items):
        """
        Load a whole dict of variables into the environment.
        Variables starting with _ are not allowed to prevent
        overloading internal controls.
        """
        keys = [k for k in items.iterkeys() if not k.startswith('_')]
        if len(keys) != len(items):
            raise ValueError("dynamic variables must not start with an underscore")
        self.__dict__.update((k,items[k]) for k in keys)

    def _find_env(self, attr):
        """
        Returns the environment that carries the given attribute
        or raises AttributeError if it can't be found.
        """
        if attr in self.__dict__:
            return self
        if self._parent:
            return self._parent._find_env(attr)
        raise AttributeError(attr)

    def __getattr__(self, attr):
        """
        Handle attributes starting with _ locally, everything else
        is sent into the dynamic environment.
        """
        if attr.startswith('_'):
            return super(DynamicEnvironment, self).__getattr__(attr)
        else:
            env = self._find_env(attr)
            return env.__dict__[attr]

    def __setattr__(self, attr, value):
        """
        Handle attributes starting with _ locally, everything else
        is sent into the dynamic environment.
        """
        if attr.startswith('_'):
            return super(DynamicEnvironment, self).__setattr__(attr, value)
        else:
            try:
                env = self._find_env(attr)
            except AttributeError:
                env = self
            env.__dict__[attr] = value

class DynamicScope(object):
    """
    This class encapsulates the dynamic scope. The scope can
    be stacked to have dynamically scoped variables, when stacked
    the actual scope instance doesn't change, but the included
    environment does.

    Dynamic scopes are context managers, scope stacking is handled
    by using the with statement.

    Attribute access is delegated to the current environment for attributes
    that don't start with an underscore.
    """
    @classmethod
    def _construct(cls):
        """
        Constructor that returns the fluid and flet pair for
        a dynamic scope manager.
        """
        o = cls()
        return o, o._flet

    def __init__(self):
        self._env = DynamicEnvironment()

    def __getattr__(self, attr):
        if attr.startswith('_'):
            return super(DynamicScope, self).__getattr__(attr)
        else:
            return getattr(self._env, attr)

    def __setattr__(self, attr, value):
        if attr.startswith('_'):
            return super(DynamicScope, self).__setattr__(attr, value)
        else:
            setattr(self._env, attr, value)

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self._env = self._env._parent
        return False

    def _flet(self, **kw):
        """
        This function handles the actual stacking of scopes. It
        returns the dynamic scope itself, as that is the context
        manager itself and will handle the unwinding.
        """
        self._env = DynamicEnvironment(self._env)
        self._env._update(kw)
        return self