Commits

jason kirtland  committed f7f51c8

Added hierarchical naming contrib example.

  • Participants
  • Parent commits 32c1e41

Comments (0)

Files changed (4)

File pegboard/base.py

     def __call__(self, environ, start_response):
         app = self.app_factory(*self.app_args, **self.app_kwargs)
         environ['pegboard.board'] = board = self.board_factory()
-        peg = self.peg_factory(name=self.name, app=app, environ=environ,
-                               board=board, parent=None)
+        environ['pegboard.peg'] = peg = \
+          self.peg_factory(name=self.name, app=app, environ=environ,
+                           board=board, parent=None)
+        board.pegs[peg.name] = peg
         return peg.invoke(start_response)
 
 

File pegboard/contrib/naming.py

+"""Alternate strategies for composed naming."""
+
+
+class HierarchicalNameMixin(object):
+    """Adds tree-structured naming to a Peg implementation."""
+
+    # override this with whatever you like: '.', '::', etc.
+    _name_separator = '/'
+
+    def add_child(self, app, name=None):
+        """Add a WSGI application as a child of this peg.
+
+        Adds a child with the local name *name*.  The
+        :attr:`full_name` of the child will be this Peg's
+        :attr:`full_name` joined with *name*.
+
+        """
+        if not name:
+            name = app.__name__
+        name = self._name_separator.join((self.full_name, name))
+        return super(HierarchicalNameMixin, self).add_child(app, name)
+
+    def set_name(self, full_name):
+        self._name = full_name.split(self._name_separator)[-1]
+    def name(self):
+        return self._name
+    name = property(name, set_name)
+    del set_name
+
+    @property
+    def full_name(self):
+        if not self.parent:
+            return self.name
+        names, peg = [self.name], self
+        while peg.parent:
+            names.append(peg.parent.name)
+            peg = peg.parent
+        return self._name_separator.join(reversed(names))
+
+    def resolve_name(self, name):
+        """Return the Peg for *name*.
+
+        >>> peg.resolve_name("A/B/C") # absolute
+        <Peg object at ...>
+
+        If name begins with :attr:`_name_separator`, *name* is
+        considered fully qualified and :meth:`resolve_name` will
+        return the Peg of that name or raise LookupError::
+
+        >>> peg.resolve_name("/A/B/C") # always look up from the root
+        <Peg object at ...>
+
+        If *name* is not fully qualified and a peg is not found with a
+        matching fully qualified name, *name* will be considered
+        relative to to the *name*.
+
+        >>> A_peg = peg.resolve_name("/A")      # absolute A
+        >>> C_peg = A_peg.resolve_name("B/C")   # relative to A
+
+        """
+        absolute = name.startswith(self._name_separator)
+        if absolute:
+            name = name[1:]
+        try:
+            return self.board.pegs[name]
+        except KeyError:
+            if not absolute:
+                as_child = self._name_separator.join((self.full_name, name))
+                try:
+                    return self.board.pegs[as_child]
+                except KeyError:
+                    pass
+            raise LookupError(name)

File tests/contrib/naming.py

+from pegboard import PegboardApplication, base
+from pegboard.contrib import naming
+from tests._util import (
+    assert_raises, wsgi_app, add_hook_on, call_as_wsgi)
+
+
+def test_hierarchical_naming_zero():
+    canary = []
+
+    def app(environ, start_response):
+        peg = environ['pegboard.peg']
+        canary.append((peg.full_name, peg.name))
+        assert peg.resolve_name('top') is peg
+        assert peg.resolve_name('/top') is peg
+        return ['abc']
+
+    call_as_wsgi(with_hier(app, name='top'))
+    assert canary == [('top', 'top')]
+
+def test_hierarchical_naming_tree():
+    canary = []
+
+    def app_a(environ, start_response):
+        peg = environ['pegboard.peg']
+        canary.append((peg.full_name, peg.name))
+        assert peg.resolve_name('a') is peg
+        assert peg.resolve_name('/a') is peg
+        return ['abc']
+
+    def app_b1(environ, start_response):
+        peg = environ['pegboard.peg']
+        canary.append((peg.full_name, peg.name))
+        assert peg.resolve_name('a/b1') is peg
+        assert peg.resolve_name('/a/b1') is peg
+        assert peg.parent.resolve_name('b1') is peg
+        assert_raises(LookupError, peg.parent.resolve_name, '/b1')
+
+    def app_b2(environ, start_response):
+        peg = environ['pegboard.peg']
+        canary.append((peg.full_name, peg.name))
+        assert peg.resolve_name('a/b2') is peg
+        assert peg.resolve_name('/a/b2') is peg
+        assert peg.parent.resolve_name('b2') is peg
+        assert_raises(LookupError, peg.parent.resolve_name, '/b2')
+
+    @add_hook_on(app_a, '__before__')
+    def before(environ, start_response):
+        peg = environ['pegboard.peg']
+        peg.add_child(app_b1, 'b1')
+        peg.add_child(app_b2, 'b2')
+
+    call_as_wsgi(with_hier(app_a, name='a'))
+    assert canary == [('a/b1', 'b1'), ('a/b2', 'b2'), ('a', 'a')]
+
+
+def with_hier(app, name='test'):
+    class App(base.PegboardApplication):
+        class peg_factory(naming.HierarchicalNameMixin, base.Peg):
+            pass
+
+    return App(name, lambda: app)

File tests/test_pegs.py

         PegboardApplication('x', factory)(*wsgi)
         assert 'pegboard.board' in environ
         assert isinstance(environ['pegboard.board'], base.Board)
-        assert 'pegboard.peg' not in environ
+        assert 'pegboard.peg' in environ
         environ.clear()
 
         class MyPegboard(PegboardApplication):