Commits

Georg Bauer  committed 567a45e

first implementation of the basics, unoptimized

  • Participants
  • Parent commits 5091d94

Comments (0)

Files changed (2)

File dynscope/__init__.py

 __version__ = '0.1'
+
+from fluids import DynamicScope
+
+fluid, flet = DynamicScope._construct()

File 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, **kw):
+        """
+        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 kw.iterkeys() if not k.startswith('_')]
+        if len(keys) != len(kw):
+            raise ValueError("dynamic variables must not start with an underscore")
+        self.__dict__.update((k,kw[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
+