[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