summaryrefslogtreecommitdiff
path: root/README.txt
blob: e3f1f6ea58f0e3ae3df08f2f06e18efeaeb6150b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
repoze.bfg
==========

``repoze.bfg`` is a system for routing web requests to applications
based on graph traversal.  It is inspired by Zope's publisher, and
uses Zope libraries to do much of its work.  However, it is less
ambitious and less featureful than any released version of Zope's
publisher.

``repoze.bfg`` uses the WSGI protocol to handle requests and
responses, and integrates Zope, Paste, and WebOb libraries to form the
basis for a simple web object publishing framework.

Graph Traversal
---------------

In many popular web frameworks, a "URL dispatcher" is used to
associate a particular URL with a bit of code (known somewhat
ambiguously as a "controller" or "view" depending upon the particular
vocabulary religion to which you subscribe).  These systems allow the
developer to create "urlconfs" or "routes" to controller/view Python
code using pattern matching against URL components.  Examples:
`Django's URL dispatcher
<http://www.djangoproject.com/documentation/url_dispatch/>`_ and the
`Routes URL mapping system <http://routes.groovie.org/>`_ .

It is however possible to map URLs to code slightly differently, using
object graph traversal. The venerable Zope and CherryPy web frameworks
offer traversal-based URL dispatch.  ``repoze.bfg`` also provides
graph-traversal-based dispatch of URLs to code.  Graph-traversal based
dispatching is useful if you like the URL to be representative of an
arbitrary hierarchy of potentially heterogeneous items.

Non-graph traversal based URL dispatch can easily handle URLs such as
``http://example.com/members/Chris``, where it's assumed that each
item "below" ``members`` in the URL represents a member in the system.
You just match everything "below" ``members`` to a particular view.
They are not very good, however, at inferring the difference between
sets of URLs such as ``http://example.com/members/Chris/document`` vs.
``http://example.com/members/Chris/stuff/page`` wherein you'd like the
``document`` in the first URL to represent, e.g. a PDF document, and
``/stuff/page`` in the second to represent, e.g. an OpenOffice
document in a "stuff" folder.  It takes more pattern matching
assertions to be able to make URLs like these work in URL-dispatch
based systems.  Over time, the more assertions you make, the more
fragile the URL dispatch lookup logic becomes.  URL-dispatch based
systems don't deal very well with URLs that represent arbitrary-depth
hierarchies.

Graph traversal works well if you need to divine meaning out of these
types of "ambiguous" URLs and URLs that represent arbitrary-depth
hierarchies.  Each URL segment represents a single traversal through
an edge of the graph.  So a URL like ``http://example.com/a/b/c`` can
be thought of as a graph traversal on the example.com site through the
edges "a", "b", and "c".

Graph traversal is materially more complex than URL-based dispatch,
however, if only because it requires the construction and maintenance
of a graph, and it requires the developer to think about mapping URLs
to code in terms of traversing the graph.  (How's *that* for
self-referential! ;-) That said, for developers comfortable with Zope,
in particular, and comfortable with hierarchical data stores like
ZODB, mapping a URL to a graph traversal it's a natural way to think
about creating a web application.  In essence, the choice to use graph
traversal vs. URL dispatch is largely "religious" in some sense and
often doesn't make sense for completely "square" data, but old habits
die hard for folks used to graph-traversal-based lookup.
``repoze.bfg`` is for those folks.

How ``repoze.bfg`` is Configured
--------------------------------

Users interact with your ``repoze.bfg``-based application via a
"router", which is itself a WSGI application.  At system startup time,
the router must be configured with a root object from which all
traversal will begin.  The root object is a mapping object, such as a
Python dictionary.  In fact, all items contained in the graph are
either leaf nodes (these have no __getitem__) or container nodes
(these do have a __getitem__).

Items contained within the graph are analogous to the concept of
``model`` objects used by many other frameworks.  They are typically
instances of classes.  Each containerish instance is willing to return
a child or raise a KeyError based on a name passed to its __getitem__.
No leaf-level instance is required to have a __getitem__.

Similarities with Other Frameworks
----------------------------------

The Django docs state that Django is an "MTV" framework in their `FAQ
<http://www.djangoproject.com/documentation/faq/>`_.  This also
happens to be true for ``repoze.bfg``::

  Django appears to be a MVC framework, but you call the Controller
  the "view", and the View the "template". How come you don’t use the
  standard names?

  Well, the standard names are debatable.

  In our interpretation of MVC, the "view" describes the data that
  gets presented to the user. It’s not necessarily how the data looks,
  but which data is presented. The view describes which data you see,
  not how you see it. It’s a subtle distinction.

  So, in our case, a "view" is the Python callback function for a
  particular URL, because that callback function describes which data
  is presented.

  Furthermore, it’s sensible to separate content from presentation —
  which is where templates come in. In Django, a "view" describes
  which data is presented, but a view normally delegates to a
  template, which describes how the data is presented.

  Where does the "controller" fit in, then? In Django’s case, it’s
  probably the framework itself: the machinery that sends a request to
  the appropriate view, according to the Django URL configuration.

  If you’re hungry for acronyms, you might say that Django is a "MTV"
  framework — that is, "model", "template", and "view." That breakdown
  makes much more sense.

Jargon
------

The following jargon is used casually in descriptions of
``repoze.bfg`` operations.

mapply

  code which dynamically ("magically") determines which arguments to
  pass to a view based on environment and request parameters.

request

  A ``WebOb`` request object.

response

  An object that has three attributes: app_iter (representing an
  iterable body), headerlist (representing the http headers sent
  upstream), and status (representing the http status string).  This
  is the interface defined for ``WebOb`` response objects.

view

  A callable that accepts arbitrary values (mapped into it by
  "mapply") and which returns a response object.

view constructor

  A callable which returns a view object.  It should accept two
  values: context and request.

model

  An object representing data in the system.  A model is part of the
  object graph traversed by the system.  Models are traversed to
  determine a context.

context

  A model in the system that is the subject of a view.

view registry

  A registry which maps a context and view name to a view constructor.

template

  A file that is capable of representing some text when rendered.

interface

  An attribute of a model object that determines its type.

How ``repoze.bfg`` Processes a Request
--------------------------------------

When a user requests a page from your ``repoze.bfg`` -powered
application, the system uses this algorithm to determine which Python
code to execute:

 1.  The request for the page is presented to ``repoze.bfg``'s
     "router" in terms of a standard WSGI request, which is
     represented by a WSGI environment and a start_response callable.

 2.  The router creates a `WebOb <http://pythonpaste.org/webob/>`_
     request object based on the WSGI environment.

 3.  The router uses the WSGI environment's ``PATH_INFO`` variable to
     determine the path segments to traverse.  The leading slash is
     stripped off `PATH_INFO``, and the remaining path segments are
     split on the slash character to form a traversal sequence, so a
     request with a ``PATH_INFO`` variable of ``/a/b/c`` maps to the
     traversal sequence ``['a', 'b', 'c']``.

 4.  Traversal begins at the root object.  For the traversal sequence
     ``['a', 'b', 'c']``, the root object's __getitem__ is called with
     the name ``a``.  Traversal continues through the sequence.  In
     our example, if the root object's __getitem__ called with the
     name ``a`` returns an object (aka "object A"), that object's
     __getitem__ is called with the name ``b``.  If object A returns
     an object when asked for ``b``, object B's __getitem__ is then
     asked for the name ``c``, and may return object C.

 5.  Traversal ends when a) the entire path is exhausted or b) when
     any graph element raises a KeyError from its __getitem__ or c)
     when any non-final path element traversal does not have a
     __getitem__ method (resulting in a NameError) or d) when any path
     element is prefixed with the set of characters ``@@`` (indicating
     that the characters following the ``@@`` token should be treated
     as a "view name").

 6.  When traversal ends for any of the reasons in the previous step,
     the the last object found during traversal is deemed to be the
     "context".  If the path has been exhausted when traversal ends,
     the "view name" is deemed to be the empty string (``''``).
     However, if the path was not exhausted before traversal
     terminated, the first remaining path element is treated as the
     view name.  Any subseqent path elements after the view name are
     deemed the "subpath".  For instance, if ``PATH_INFO`` was
     ``/a/b`` and the root returned an "A" object, and the "A" object
     returned a "B" object, the router deems that the context is
     "object B", the view name is the empty string, and the subpath is
     the empty sequence.  On the other hand, if ``PATH_INFO`` was
     ``/a/b/c`` and "object A" was found but raised a KeyError for the
     name ``b``, the router deems that the context is object A, the
     view name is ``b`` and the subpath is ``['c']``.

 7.  Armed with the context, the view name, and the subpath, the
     router performs a view lookup.  It attemtps to look up a view
     constructor from the ``repoze.bfg`` view registry using the view
     name and the context.  If a view constructor is found, it is
     converted into a WSGI application: it is "wrapped in" ( aka
     "adapted to") a WSGI application using mapply.  The WSGI adapter
     uses mapply to map request and environment variables into the
     view when it is called.  If a view constructor is not found, a
     generic WSGI ``NotFound`` application is constructed. 

In either case, the resulting WSGI application is called.  The WSGI
application's return value is an iterable.  This is returned upstream
to the WSGI server.  The WSGI application also calls start_response
with a status code and a header list.

A Sample Application
--------------------

A typical simple ``repoze.bfg`` application consists of four things:

  1. A ``views.py`` module, which contains view code.

  2. A ``models.py`` module, which contains model code.

  3. A ``configure.zcml`` file which maps view names to model types.
     This is also known as the "view registry", although it also
     often contains non-view-related declarations.

  4. A "templates" directory, which is full of zc3.pt templates.

An application must be a Python package (meaning it must have an
__init__.py and it must be findable on the PYTHONPATH).

views.py
~~~~~~~~

A views.py module might look like so::

  from webob import Response
  from repoze.bfg.view import TemplateView

  def my_hello_view(context, request):
      response = Response('Hello from %s @ %s' % (
                          context.__name__, 
                          request.environ['PATH_INFO']))
      return response

   def my_template_view(context, request):
       return render_template('templates/my.pt', name=context.__name__)

models.py
~~~~~~~~~

A models.py might look like so::

  from UserDict import UserDict

  from zope.interface import implements
  from zope.interface import Attribute
  from zope.interface import Interface

  class IMyModel(Interface):
      __name__ = Attribute('Name of the model instance')

  class MyModel(UserDict):
      implements(IMyModel)
      def __init__(self, name):
          self.__name__ = name
          UserDict.__init__(self, {})

  # model instance info would typically be stored in a database of some
  # kind; here we put it at module scope for demonstration purposes.

  root = Model('root')
  root['a'] = Model('a')
  root['b'] = Model('b')

  def get_root(environ):
      return root
    
configure.zcml
~~~~~~~~~~~~~~

A view registry might look like so::

  <configure xmlns="http://namespaces.zope.org/zope"
      xmlns:bfg="http://namespaces.repoze.org/bfg"
      i18n_domain="repoze.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.MyHelloView"
        permission="repoze.view"
        />

    <!-- the templated.html view for a MyModel -->
    <bfg:view
        for=".models.IMyModel"
        factory=".views.MyTemplateView"
        name="templated.html"
        permission="repoze.view"
        />

  </configure>

templates/my.pt
~~~~~~~~~~~~~~~

A template that is used by a view might look like so::

  <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:tal="http://xml.zope.org/namespaces/tal">
  <head></head>
  <body>
    <h1>My template viewing ${name}</h1>
  </body>
  </html>

Running the Application
-----------------------

To run the application above, the simplest method is to run it
directly from a starter script (although you might also use Paste to
perform this task)::

  from paste import httpserver

  from repoze.bfg import make_app
  from myapp.models import get_root
  import myapp

  app = make_app(myapp.get_root, myapp)
  httpserver.serve(app, host='0.0.0.0', port='5432')
  
Viewing the Application
-----------------------

Visit http://localhost:5432/ in your browser.  You will see::

  Hello from root @ /

Visit http://localhost:5432/a in your browser.  You will see::

  Hello from a @ /a

Visit http://localhost:5432/b in your browser.  You will see::

  Hello from b @ /b

Visit http://localhost:5432/templated.html in your browser.  You will
see::

  My template viewing root


Visit http://localhost:5432/a/templated.html in your browser.  You
will see::

  My template viewing a

Visit http://localhost:5432/b/templated.html in your browser.  You
will see::

  My template viewing b