diff options
| author | Steve Piercy <web@stevepiercy.com> | 2015-10-26 03:02:43 -0700 |
|---|---|---|
| committer | Steve Piercy <web@stevepiercy.com> | 2015-10-26 03:02:43 -0700 |
| commit | c32c8a4375543b563b2e7eb4bffe917b89a881ea (patch) | |
| tree | 496e974a036cbf091e86da23f4b2cf49400db33f | |
| parent | 8eea79cad1659f8b9a2c7715b7b5161ea13c56a2 (diff) | |
| parent | 9dbba32a0895a4a56611a6db55b59e9e461a8c1c (diff) | |
| download | pyramid-c32c8a4375543b563b2e7eb4bffe917b89a881ea.tar.gz pyramid-c32c8a4375543b563b2e7eb4bffe917b89a881ea.tar.bz2 pyramid-c32c8a4375543b563b2e7eb4bffe917b89a881ea.zip | |
Merge pull request #2037 from stevepiercy/master
minor grammar, rewrap 79 columns, some .rst syntax fixes
| -rw-r--r-- | docs/narr/muchadoabouttraversal.rst | 341 |
1 files changed, 168 insertions, 173 deletions
diff --git a/docs/narr/muchadoabouttraversal.rst b/docs/narr/muchadoabouttraversal.rst index 483b1bb16..3e00a295a 100644 --- a/docs/narr/muchadoabouttraversal.rst +++ b/docs/narr/muchadoabouttraversal.rst @@ -4,44 +4,44 @@ Much Ado About Traversal ======================== -(Or, why you should care about it) +(Or, why you should care about it.) .. note:: - This chapter was adapted, with permission, from a blog post by `Rob - Miller <http://blog.nonsequitarian.org/>`_, originally published at - http://blog.nonsequitarian.org/2010/much-ado-about-traversal/ . + This chapter was adapted, with permission, from a blog post by `Rob Miller + <http://blog.nonsequitarian.org/>`_, originally published at + http://blog.nonsequitarian.org/2010/much-ado-about-traversal/. -Traversal is an alternative to :term:`URL dispatch` which allows -:app:`Pyramid` applications to map URLs to code. +Traversal is an alternative to :term:`URL dispatch` which allows :app:`Pyramid` +applications to map URLs to code. .. note:: Ex-Zope users who are already familiar with traversal and view lookup conceptually may want to skip directly to the :ref:`traversal_chapter` - chapter, which discusses technical details. This chapter is mostly aimed - at people who have previous :term:`Pylons` experience or experience in - another framework which does not provide traversal, and need an - introduction to the "why" of traversal. + chapter, which discusses technical details. This chapter is mostly aimed at + people who have previous :term:`Pylons` experience or experience in another + framework which does not provide traversal, and need an introduction to the + "why" of traversal. Some folks who have been using Pylons and its Routes-based URL matching for a long time are being exposed for the first time, via :app:`Pyramid`, to new ideas such as ":term:`traversal`" and ":term:`view lookup`" as a way to route incoming HTTP requests to callable code. Some of the same folks believe that -traversal is hard to understand. Others question its usefulness; URL -matching has worked for them so far, why should they even consider dealing -with another approach, one which doesn't fit their brain and which doesn't -provide any immediately obvious value? +traversal is hard to understand. Others question its usefulness; URL matching +has worked for them so far, so why should they even consider dealing with +another approach, one which doesn't fit their brain and which doesn't provide +any immediately obvious value? You can be assured that if you don't want to understand traversal, you don't have to. You can happily build :app:`Pyramid` applications with only -:term:`URL dispatch`. However, there are some straightforward, real-world -use cases that are much more easily served by a traversal-based approach than -by a pattern-matching mechanism. Even if you haven't yet hit one of these -use cases yourself, understanding these new ideas is worth the effort for any -web developer so you know when you might want to use them. :term:`Traversal` -is actually a straightforward metaphor easily comprehended by anyone who's -ever used a run-of-the-mill file system with folders and files. +:term:`URL dispatch`. However, there are some straightforward, real-world use +cases that are much more easily served by a traversal-based approach than by a +pattern-matching mechanism. Even if you haven't yet hit one of these use cases +yourself, understanding these new ideas is worth the effort for any web +developer so you know when you might want to use them. :term:`Traversal` is +actually a straightforward metaphor easily comprehended by anyone who's ever +used a run-of-the-mill file system with folders and files. .. index:: single: URL dispatch @@ -49,34 +49,33 @@ ever used a run-of-the-mill file system with folders and files. URL Dispatch ------------ -Let's step back and consider the problem we're trying to solve. An -HTTP request for a particular path has been routed to our web -application. The requested path will possibly invoke a specific -:term:`view callable` function defined somewhere in our app. We're -trying to determine *which* callable function, if any, should be -invoked for a given requested URL. +Let's step back and consider the problem we're trying to solve. An HTTP +request for a particular path has been routed to our web application. The +requested path will possibly invoke a specific :term:`view callable` function +defined somewhere in our app. We're trying to determine *which* callable +function, if any, should be invoked for a given requested URL. Many systems, including Pyramid, offer a simple solution. They offer the -concept of "URL matching". URL matching approaches this problem by parsing -the URL path and comparing the results to a set of registered "patterns", -defined by a set of regular expressions, or some other URL path templating -syntax. Each pattern is mapped to a callable function somewhere; if the -request path matches a specific pattern, the associated function is called. -If the request path matches more than one pattern, some conflict resolution -scheme is used, usually a simple order precedence so that the first match -will take priority over any subsequent matches. If a request path doesn't -match any of the defined patterns, a "404 Not Found" response is returned. - -In Pyramid, we offer an implementation of URL matching which we call -:term:`URL dispatch`. Using :app:`Pyramid` syntax, we might have a match -pattern such as ``/{userid}/photos/{photoid}``, mapped to a ``photo_view()`` -function defined somewhere in our code. Then a request for a path such as +concept of "URL matching". URL matching approaches this problem by parsing the +URL path and comparing the results to a set of registered "patterns", defined +by a set of regular expressions or some other URL path templating syntax. Each +pattern is mapped to a callable function somewhere; if the request path matches +a specific pattern, the associated function is called. If the request path +matches more than one pattern, some conflict resolution scheme is used, usually +a simple order precedence so that the first match will take priority over any +subsequent matches. If a request path doesn't match any of the defined +patterns, a "404 Not Found" response is returned. + +In Pyramid, we offer an implementation of URL matching which we call :term:`URL +dispatch`. Using :app:`Pyramid` syntax, we might have a match pattern such as +``/{userid}/photos/{photoid}``, mapped to a ``photo_view()`` function defined +somewhere in our code. Then a request for a path such as ``/joeschmoe/photos/photo1`` would be a match, and the ``photo_view()`` function would be invoked to handle the request. Similarly, -``/{userid}/blog/{year}/{month}/{postid}`` might map to a -``blog_post_view()`` function, so ``/joeschmoe/blog/2010/12/urlmatching`` -would trigger the function, which presumably would know how to find and -render the ``urlmatching`` blog post. +``/{userid}/blog/{year}/{month}/{postid}`` might map to a ``blog_post_view()`` +function, so ``/joeschmoe/blog/2010/12/urlmatching`` would trigger the +function, which presumably would know how to find and render the +``urlmatching`` blog post. Historical Refresher -------------------- @@ -88,65 +87,64 @@ time when we didn't have fancy web frameworks like :term:`Pylons` and :app:`Pyramid`. Instead, we had general purpose HTTP servers that primarily served files off of a file system. The "root" of a given site mapped to a particular folder somewhere on the file system. Each segment of the request -URL path represented a subdirectory. The final path segment would be either -a directory or a file, and once the server found the right file it would -package it up in an HTTP response and send it back to the client. So serving -up a request for ``/joeschmoe/photos/photo1`` literally meant that there was -a ``joeschmoe`` folder somewhere, which contained a ``photos`` folder, which -in turn contained a ``photo1`` file. If at any point along the way we find -that there is not a folder or file matching the requested path, we return a -404 response. +URL path represented a subdirectory. The final path segment would be either a +directory or a file, and once the server found the right file it would package +it up in an HTTP response and send it back to the client. So serving up a +request for ``/joeschmoe/photos/photo1`` literally meant that there was a +``joeschmoe`` folder somewhere, which contained a ``photos`` folder, which in +turn contained a ``photo1`` file. If at any point along the way we find that +there is not a folder or file matching the requested path, we return a 404 +response. As the web grew more dynamic, however, a little bit of extra complexity was -added. Technologies such as CGI and HTTP server modules were developed. -Files were still looked up on the file system, but if the file ended with -(for example) ``.cgi`` or ``.php``, or if it lived in a special folder, -instead of simply sending the file to the client the server would read the -file, execute it using an interpreter of some sort, and then send the output -from this process to the client as the final result. The server -configuration specified which files would trigger some dynamic code, with the -default case being to just serve the static file. +added. Technologies such as CGI and HTTP server modules were developed. Files +were still looked up on the file system, but if the file ended with (for +example) ``.cgi`` or ``.php``, or if it lived in a special folder, instead of +simply sending the file to the client the server would read the file, execute +it using an interpreter of some sort, and then send the output from this +process to the client as the final result. The server configuration specified +which files would trigger some dynamic code, with the default case being to +just serve the static file. .. index:: single: traversal -Traversal (aka Resource Location) ---------------------------------- +Traversal (a.k.a., Resource Location) +------------------------------------- Believe it or not, if you understand how serving files from a file system works, you understand traversal. And if you understand that a server might do -something different based on what type of file a given request specifies, -then you understand view lookup. +something different based on what type of file a given request specifies, then +you understand view lookup. The major difference between file system lookup and traversal is that a file -system lookup steps through nested directories and files in a file system -tree, while traversal steps through nested dictionary-type objects in a -:term:`resource tree`. Let's take a detailed look at one of our example -paths, so we can see what I mean: - -The path ``/joeschmoe/photos/photo1``, has four segments: ``/``, -``joeschmoe``, ``photos`` and ``photo1``. With file system lookup we might -have a root folder (``/``) containing a nested folder (``joeschmoe``), which -contains another nested folder (``photos``), which finally contains a JPG -file (``photo1``). With traversal, we instead have a dictionary-like root -object. Asking for the ``joeschmoe`` key gives us another dictionary-like -object. Asking this in turn for the ``photos`` key gives us yet another -mapping object, which finally (hopefully) contains the resource that we're -looking for within its values, referenced by the ``photo1`` key. - -In pure Python terms, then, the traversal or "resource location" -portion of satisfying the ``/joeschmoe/photos/photo1`` request -will look something like this pseudocode:: +system lookup steps through nested directories and files in a file system tree, +while traversal steps through nested dictionary-type objects in a +:term:`resource tree`. Let's take a detailed look at one of our example paths, +so we can see what I mean. + +The path ``/joeschmoe/photos/photo1``, has four segments: ``/``, ``joeschmoe``, +``photos`` and ``photo1``. With file system lookup we might have a root folder +(``/``) containing a nested folder (``joeschmoe``), which contains another +nested folder (``photos``), which finally contains a JPG file (``photo1``). +With traversal, we instead have a dictionary-like root object. Asking for the +``joeschmoe`` key gives us another dictionary-like object. Asking in turn for +the ``photos`` key gives us yet another mapping object, which finally +(hopefully) contains the resource that we're looking for within its values, +referenced by the ``photo1`` key. + +In pure Python terms, then, the traversal or "resource location" portion of +satisfying the ``/joeschmoe/photos/photo1`` request will look something like +this pseudocode:: get_root()['joeschmoe']['photos']['photo1'] -``get_root()`` is some function that returns a root traversal -:term:`resource`. If all of the specified keys exist, then the returned -object will be the resource that is being requested, analogous to the JPG -file that was retrieved in the file system example. If a :exc:`KeyError` is -generated anywhere along the way, :app:`Pyramid` will return 404. (This -isn't precisely true, as you'll see when we learn about view lookup below, -but the basic idea holds.) +``get_root()`` is some function that returns a root traversal :term:`resource`. +If all of the specified keys exist, then the returned object will be the +resource that is being requested, analogous to the JPG file that was retrieved +in the file system example. If a :exc:`KeyError` is generated anywhere along +the way, :app:`Pyramid` will return 404. (This isn't precisely true, as you'll +see when we learn about view lookup below, but the basic idea holds.) .. index:: single: resource @@ -159,10 +157,10 @@ nested dictionary things? Where do these objects, these 'resources', live? What *are* they?" Since :app:`Pyramid` is not a highly opinionated framework, it makes no -restriction on how a :term:`resource` is implemented; a developer can -implement them as he wishes. One common pattern used is to persist all of -the resources, including the root, in a database as a graph. The root object -is a dictionary-like object. Dictionary-like objects in Python supply a +restriction on how a :term:`resource` is implemented; a developer can implement +them as they wish. One common pattern used is to persist all of the resources, +including the root, in a database as a graph. The root object is a +dictionary-like object. Dictionary-like objects in Python supply a ``__getitem__`` method which is called when key lookup is done. Under the hood, when ``adict`` is a dictionary-like object, Python translates ``adict['a']`` to ``adict.__getitem__('a')``. Try doing this in a Python @@ -175,25 +173,24 @@ interpreter prompt if you don't believe us: >>> adict.__getitem__('a') 1 - The dictionary-like root object stores the ids of all of its subresources as keys, and provides a ``__getitem__`` implementation that fetches them. So ``get_root()`` fetches the unique root object, while ``get_root()['joeschmoe']`` returns a different object, also stored in the database, which in turn has its own subresources and ``__getitem__`` -implementation, etc. These resources might be persisted in a relational +implementation, and so on. These resources might be persisted in a relational database, one of the many "NoSQL" solutions that are becoming popular these -days, or anywhere else, it doesn't matter. As long as the returned objects -provide the dictionary-like API (i.e. as long as they have an appropriately -implemented ``__getitem__`` method) then traversal will work. - -In fact, you don't need a "database" at all. You could use plain -dictionaries, with your site's URL structure hard-coded directly in -the Python source. Or you could trivially implement a set of objects -with ``__getitem__`` methods that search for files in specific -directories, and thus precisely recreate the traditional mechanism of -having the URL path mapped directly to a folder structure on the file -system. Traversal is in fact a superset of file system lookup. +days, or anywhere else; it doesn't matter. As long as the returned objects +provide the dictionary-like API (i.e., as long as they have an appropriately +implemented ``__getitem__`` method), then traversal will work. + +In fact, you don't need a "database" at all. You could use plain dictionaries, +with your site's URL structure hard-coded directly in the Python source. Or +you could trivially implement a set of objects with ``__getitem__`` methods +that search for files in specific directories, and thus precisely recreate the +traditional mechanism of having the URL path mapped directly to a folder +structure on the file system. Traversal is in fact a superset of file system +lookup. .. note:: See the chapter entitled :ref:`resources_chapter` for a more technical overview of resources. @@ -208,34 +205,33 @@ At this point we're nearly there. We've covered traversal, which is the process by which a specific resource is retrieved according to a specific URL path. But what is "view lookup"? -The need for view lookup is simple: there is more than one possible action -that you might want to take after finding a :term:`resource`. With our photo +The need for view lookup is simple: there is more than one possible action that +you might want to take after finding a :term:`resource`. With our photo example, for instance, you might want to view the photo in a page, but you might also want to provide a way for the user to edit the photo and any associated metadata. We'll call the former the ``view`` view, and the latter will be the ``edit`` view. (Original, I know.) :app:`Pyramid` has a centralized view :term:`application registry` where named views can be -associated with specific resource types. So in our example, we'll assume -that we've registered ``view`` and ``edit`` views for photo objects, and that -we've specified the ``view`` view as the default, so that +associated with specific resource types. So in our example, we'll assume that +we've registered ``view`` and ``edit`` views for photo objects, and that we've +specified the ``view`` view as the default, so that ``/joeschmoe/photos/photo1/view`` and ``/joeschmoe/photos/photo1`` are equivalent. The edit view would sensibly be provided by a request for ``/joeschmoe/photos/photo1/edit``. Hopefully it's clear that the first portion of the edit view's URL path is -going to resolve to the same resource as the non-edit version, specifically -the resource returned by ``get_root()['joeschmoe']['photos']['photo1']``. -But traveral ends there; the ``photo1`` resource doesn't have an ``edit`` -key. In fact, it might not even be a dictionary-like object, in which case +going to resolve to the same resource as the non-edit version, specifically the +resource returned by ``get_root()['joeschmoe']['photos']['photo1']``. But +traversal ends there; the ``photo1`` resource doesn't have an ``edit`` key. In +fact, it might not even be a dictionary-like object, in which case ``photo1['edit']`` would be meaningless. When the :app:`Pyramid` resource location has been resolved to a *leaf* resource, but the entire request path has not yet been expended, the *very next* path segment is treated as a -:term:`view name`. The registry is then checked to see if a view of the -given name has been specified for a resource of the given type. If so, the -view callable is invoked, with the resource passed in as the related -``context`` object (also available as ``request.context``). If a view -callable could not be found, :app:`Pyramid` will return a "404 Not Found" -response. +:term:`view name`. The registry is then checked to see if a view of the given +name has been specified for a resource of the given type. If so, the view +callable is invoked, with the resource passed in as the related ``context`` +object (also available as ``request.context``). If a view callable could not +be found, :app:`Pyramid` will return a "404 Not Found" response. You might conceptualize a request for ``/joeschmoe/photos/photo1/edit`` as ultimately converted into the following piece of Pythonic pseudocode:: @@ -246,8 +242,8 @@ ultimately converted into the following piece of Pythonic pseudocode:: view_callable(request) The ``get_root`` and ``get_view`` functions don't really exist. Internally, -:app:`Pyramid` does something more complicated. But the example above -is a reasonable approximation of the view lookup algorithm in pseudocode. +:app:`Pyramid` does something more complicated. But the example above is a +reasonable approximation of the view lookup algorithm in pseudocode. Use Cases --------- @@ -261,58 +257,57 @@ like this:: /{userid}/{typename}/{objectid}[/{view_name}] -In all of the examples thus far, we've hard coded the typename value, -assuming that we'd know at development time what names were going to be used -("photos", "blog", etc.). But what if we don't know what these names will -be? Or, worse yet, what if we don't know *anything* about the structure of -the URLs inside a user's folder? We could be writing a CMS where we want the -end user to be able to arbitrarily add content and other folders inside his -folder. He might decide to nest folders dozens of layers deep. How will you -construct matching patterns that could account for every possible combination -of paths that might develop? - -It might be possible, but it certainly won't be easy. The matching -patterns are going to become complex quickly as you try to handle all -of the edge cases. - -With traversal, however, it's straightforward. Twenty layers of nesting -would be no problem. :app:`Pyramid` will happily call ``__getitem__`` as -many times as it needs to, until it runs out of path segments or until a -resource raises a :exc:`KeyError`. Each resource only needs to know how to -fetch its immediate children, the traversal algorithm takes care of the rest. -Also, since the structure of the resource tree can live in the database and -not in the code, it's simple to let users modify the tree at runtime to set -up their own personalized "directory" structures. - -Another use case in which traversal shines is when there is a need to support -a context-dependent security policy. One example might be a document -management infrastructure for a large corporation, where members of different -departments have varying access levels to the various other departments' -files. Reasonably, even specific files might need to be made available to -specific individuals. Traversal does well here if your resources actually -represent the data objects related to your documents, because the idea of a -resource authorization is baked right into the code resolution and calling -process. Resource objects can store ACLs, which can be inherited and/or -overridden by the subresources. - -If each resource can thus generate a context-based ACL, then whenever view -code is attempting to perform a sensitive action, it can check against that -ACL to see whether the current user should be allowed to perform the action. -In this way you achieve so called "instance based" or "row level" security -which is considerably harder to model using a traditional tabular approach. +In all of the examples thus far, we've hard coded the typename value, assuming +that we'd know at development time what names were going to be used ("photos", +"blog", etc.). But what if we don't know what these names will be? Or, worse +yet, what if we don't know *anything* about the structure of the URLs inside a +user's folder? We could be writing a CMS where we want the end user to be able +to arbitrarily add content and other folders inside his folder. He might +decide to nest folders dozens of layers deep. How will you construct matching +patterns that could account for every possible combination of paths that might +develop? + +It might be possible, but it certainly won't be easy. The matching patterns +are going to become complex quickly as you try to handle all of the edge cases. + +With traversal, however, it's straightforward. Twenty layers of nesting would +be no problem. :app:`Pyramid` will happily call ``__getitem__`` as many times +as it needs to, until it runs out of path segments or until a resource raises a +:exc:`KeyError`. Each resource only needs to know how to fetch its immediate +children, and the traversal algorithm takes care of the rest. Also, since the +structure of the resource tree can live in the database and not in the code, +it's simple to let users modify the tree at runtime to set up their own +personalized "directory" structures. + +Another use case in which traversal shines is when there is a need to support a +context-dependent security policy. One example might be a document management +infrastructure for a large corporation, where members of different departments +have varying access levels to the various other departments' files. +Reasonably, even specific files might need to be made available to specific +individuals. Traversal does well here if your resources actually represent the +data objects related to your documents, because the idea of a resource +authorization is baked right into the code resolution and calling process. +Resource objects can store ACLs, which can be inherited and/or overridden by +the subresources. + +If each resource can thus generate a context-based ACL, then whenever view code +is attempting to perform a sensitive action, it can check against that ACL to +see whether the current user should be allowed to perform the action. In this +way you achieve so called "instance based" or "row level" security which is +considerably harder to model using a traditional tabular approach. :app:`Pyramid` actively supports such a scheme, and in fact if you register -your views with guard permissions and use an authorization policy, -:app:`Pyramid` can check against a resource's ACL when deciding whether or -not the view itself is available to the current user. +your views with guarded permissions and use an authorization policy, +:app:`Pyramid` can check against a resource's ACL when deciding whether or not +the view itself is available to the current user. -In summary, there are entire classes of problems that are more easily served -by traversal and view lookup than by :term:`URL dispatch`. If your problems -don't require it, great: stick with :term:`URL dispatch`. But if you're -using :app:`Pyramid` and you ever find that you *do* need to support one of -these use cases, you'll be glad you have traversal in your toolkit. +In summary, there are entire classes of problems that are more easily served by +traversal and view lookup than by :term:`URL dispatch`. If your problems don't +require it, great, stick with :term:`URL dispatch`. But if you're using +:app:`Pyramid` and you ever find that you *do* need to support one of these use +cases, you'll be glad you have traversal in your toolkit. .. note:: - It is even possible to mix and match :term:`traversal` with - :term:`URL dispatch` in the same :app:`Pyramid` application. See the + It is even possible to mix and match :term:`traversal` with :term:`URL + dispatch` in the same :app:`Pyramid` application. See the :ref:`hybrid_chapter` chapter for details. |
