[Repoze-checkins] r1382 - in repoze.lxmlgraph/trunk/docs: . step02 step02/myapp

Paul Everitt paul at agendaless.com
Fri Jul 18 16:38:59 EDT 2008


Author: Paul Everitt <paul at agendaless.com>
Date: Fri Jul 18 16:38:58 2008
New Revision: 1382

Log:
Some docs that actually introduce the XML graph

Added:
   repoze.lxmlgraph/trunk/docs/step02/myapp/
   repoze.lxmlgraph/trunk/docs/step02/myapp/__init__.py
   repoze.lxmlgraph/trunk/docs/step02/myapp/configure.zcml
   repoze.lxmlgraph/trunk/docs/step02/myapp/models.py
   repoze.lxmlgraph/trunk/docs/step02/myapp/samplemodel.xml
   repoze.lxmlgraph/trunk/docs/step02/myapp/views.py
   repoze.lxmlgraph/trunk/docs/step02/run.py
Modified:
   repoze.lxmlgraph/trunk/docs/index.rst
   repoze.lxmlgraph/trunk/docs/step02.rst

Modified: repoze.lxmlgraph/trunk/docs/index.rst
==============================================================================
--- repoze.lxmlgraph/trunk/docs/index.rst	(original)
+++ repoze.lxmlgraph/trunk/docs/index.rst	Fri Jul 18 16:38:58 2008
@@ -16,4 +16,6 @@
    background
    installation
    step01
+   step02
+
 

Modified: repoze.lxmlgraph/trunk/docs/step02.rst
==============================================================================
--- repoze.lxmlgraph/trunk/docs/step02.rst	(original)
+++ repoze.lxmlgraph/trunk/docs/step02.rst	Fri Jul 18 16:38:58 2008
@@ -0,0 +1,116 @@
+================================================
+Step 02: Hello World as XML
+================================================
+
+We now have a website with ``/a`` and ``/b`` URLs.  Each has a default
+view that returns a teensy weensy response.
+
+In this step we will do the exact some scope, but using an XML
+document as our model data.  We will leverage the same ``repoze.bfg``
+machinery:
+
+  - Model data with interfaces that define "types"
+
+  - ZCML configuration to provide type-specific views
+
+We do, however, need to do some things differently:
+
+  - Our model class needs to use lxml to inject itelf into the XML
+    nodes
+
+  - That model class needs to implement the "handshake"
+
+Let's look at what changed.
+
+File ``myapp/samplemodel.xml``
+--------------------------------
+
+Our hierarchy in Step 01 was very simple.  Mimicking it in XML is,
+thus, also very simple:
+
+.. literalinclude:: step02/myapp/samplemodel.xml
+   :linenos:
+   :language: xml
+
+1) Line 2 provides the root of the model as an XML ``<root>`` node.
+The element name doesn't have to be ``<root>``.
+
+2) In lines 3-4, the root contains 2 top-level children: a and b.
+These are provided as an element name ``<document>``.  This, also, is
+meaningfless as far as ``repoze.bfg`` is concerned.  However, this is
+where you compose th information model you are publishing.
+
+The only special constraint is that a node that wants to be "found" by
+``repoze.bfg`` in during traversal *must* have an ``id`` attribute.
+Each hop in the URL tries to grab a child with an attribute matching
+the next hop.  Also, the value of the ``@id`` should be unique in its
+containing node.
+
+
+Module ``myapp/models.py``
+------------------------------
+
+Here is the serious change: we have made an XML-aware model.  Or is it
+a model-aware XML document?  Such questions, harrumph.
+
+At a high level, we make write a class that "extends" lxml Element
+nodes, create an lxml parser, and register the custom class with the
+parser.
+
+.. literalinclude:: step02/myapp/models.py
+   :linenos:
+
+1) Line 4 imports lxml.
+
+2) Line 9 creates the custom class we are going to use to extend
+etree.ElementBase.  The lxml website has great documentation on the
+various ways to inject custom Python behavior into XML.
+
+3) Just as before, line 12 says that instances of this class support a
+certain content type (interface.)  In our case, instances will be XML
+nodes.
+
+4) ``repoze.bfg`` has a "protocol" where model data should have an
+``__name__`` attribute.  Lines 14-16 implement this by grabbing the
+``@id`` attribute of the current node.
+
+5) URL traversal in ``repoze.bfg`` works via the ``__getitem__``
+protocol.  Thus, we need a method that implements this.  Lines 18-26
+use XPath to look for a direct child that has an ``@id`` matching the
+item id that's being traversed to.  If it finds it, return it.  If
+not, or if more than one is found, raise an error.
+
+6) As before, ``get_root`` is the function that is expected to return
+the top of the model.  In lines 30-32 we do the lxml magic to get the
+custom Python class registered.  We then load some XML and return the
+top of the tree.
+
+
+Module `myapp/views.py``
+--------------------------
+
+We only made two changes here.
+
+.. literalinclude:: step02/myapp/views.py
+   :linenos:
+
+1) Line 5 grabs the element name (tag name) of the ``context``, which
+is the current XML node that we're traversing through.
+
+2) Line 6 uses the special property we defined in our custom Python
+class to get the ``__name__`` of the context.
+
+
+Browsing the Model
+------------------------
+
+We can use the same URLs from Step 01 to browser the model and see
+results::
+
+  http://localhost:5432/a
+  http://localhost:5432/b
+  http://localhost:5432/c (Not Found)
+
+In this case, each request grabs a node in the XML and uses it as the
+data for the view.  ``repoze.bfg`` doesn't really know that, unlike
+Step 01, we no longer have "real" Python data.
\ No newline at end of file

Added: repoze.lxmlgraph/trunk/docs/step02/myapp/__init__.py
==============================================================================
--- (empty file)
+++ repoze.lxmlgraph/trunk/docs/step02/myapp/__init__.py	Fri Jul 18 16:38:58 2008
@@ -0,0 +1 @@
+#

Added: repoze.lxmlgraph/trunk/docs/step02/myapp/configure.zcml
==============================================================================
--- (empty file)
+++ repoze.lxmlgraph/trunk/docs/step02/myapp/configure.zcml	Fri Jul 18 16:38:58 2008
@@ -0,0 +1,13 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+	   xmlns:bfg="http://namespaces.repoze.org/bfg">
+
+  <!-- this must be included for the view declarations to work -->
+  <include package="repoze.bfg" />
+
+  <!-- the default view for a MyModel -->
+  <bfg:view
+     for=".models.IMyModel"
+     factory=".views.my_hello_view"
+     />
+
+</configure>

Added: repoze.lxmlgraph/trunk/docs/step02/myapp/models.py
==============================================================================
--- (empty file)
+++ repoze.lxmlgraph/trunk/docs/step02/myapp/models.py	Fri Jul 18 16:38:58 2008
@@ -0,0 +1,41 @@
+from zope.interface import implements
+from zope.interface import Attribute
+from zope.interface import Interface
+from lxml import etree
+
+class IMyModel(Interface):
+    __name__ = Attribute('Name of the model instance')
+
+class BfgElement(etree.ElementBase):
+    """Handle access control and getitem behavior"""
+
+    implements(IMyModel)
+
+    @property
+    def __name__(self):
+        return self.xpath("@id")[0]
+
+    def __getitem__(self, item_id):
+        xp = "*[@id='%s']" % item_id
+        matches = self.xpath(xp)
+        if len(matches) == 0:
+            raise KeyError('No child found for %s' % item_id)
+        elif len(matches) > 1:
+            raise KeyError('More than one child for %s' % item_id)
+        else:
+            return matches[0]
+
+def get_root(environ):
+    # Setup the custom parser with our BfgElement behavior
+    parser_lookup = etree.ElementDefaultClassLookup(element=BfgElement)
+    parser = etree.XMLParser()
+    parser.set_element_class_lookup(parser_lookup)
+
+    # Now load the XML file
+    xmlstring = open("myapp/samplemodel.xml").read()
+    root = etree.XML(xmlstring, parser)
+
+    return root
+
+
+

Added: repoze.lxmlgraph/trunk/docs/step02/myapp/samplemodel.xml
==============================================================================
--- (empty file)
+++ repoze.lxmlgraph/trunk/docs/step02/myapp/samplemodel.xml	Fri Jul 18 16:38:58 2008
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+    <document id="a"/>
+    <document id="b"/>
+</root>

Added: repoze.lxmlgraph/trunk/docs/step02/myapp/views.py
==============================================================================
--- (empty file)
+++ repoze.lxmlgraph/trunk/docs/step02/myapp/views.py	Fri Jul 18 16:38:58 2008
@@ -0,0 +1,8 @@
+from webob import Response
+
+def my_hello_view(context, request):
+    response = Response('Hello to %s from %s @ %s' % (
+            context.tag, 
+            context.__name__, 
+            request.environ['PATH_INFO']))
+    return response

Added: repoze.lxmlgraph/trunk/docs/step02/run.py
==============================================================================
--- (empty file)
+++ repoze.lxmlgraph/trunk/docs/step02/run.py	Fri Jul 18 16:38:58 2008
@@ -0,0 +1,8 @@
+from paste import httpserver
+
+from repoze.bfg import make_app
+from myapp.models import get_root
+import myapp
+
+app = make_app(get_root, myapp)
+httpserver.serve(app, host='0.0.0.0', port='5432')


More information about the Repoze-checkins mailing list