Pyramid traversal and TurboGears object dispatch work very differently.
In Pyramid, the graph of objects traversed isnt consulted for view
logic. Instead, the graph is a graph of "model" objects. "Traversal"
is the act of finding a "context". When a context is found, traversal
has done its job. Subsequently a view is looked up based on that
context and data in the request.
An analogue of your above might look like so in Pyramid:
#--> The main root factory (myapp/views/root.py): <--
from pyramid.view import view_config
from pyramid.configuration import Configurator
from paste.httpserver import serve
class AdminModel(object):
pass
class RootView(object):
def __init__(self, request):
self.request = request
@view_config(renderer='string')
def __call__(self):
return 'index'
#--> The admin root (myapp/views/admin/root.py): <--
class AdminRootView(object):
def __init__(self, request):
self.request = request
@view_config(renderer='string', context=AdminModel)
def __call__(self):
return 'admin index'
@view_config(name='hello', renderer='string', context=AdminModel)
def hello(self):
return 'admin hello'
root = {'admin':AdminModel()}
if __name__ == '__main__':
config = Configurator(root_factory = lambda x: root)
config.scan()
app = config.make_wsgi_app()
serve(app)
The root object represents the root of the graph being traversed. The
AdminModel object is present in the graph under the root as the key
"admin".
When traversal traverses "/", the context is the root. Since every
other view is registered for a more specific context, the Root View is
used.
http://localhost:8080/ -> 'index'
When traversal traverses "/admin", the context is the "admin"
AdminModel. Only the views registered for this context will be
considered viable. Since the "view name" is "", the view registered as
"__call__" (no name= argument) on the AdminRootView object will be used:
http://localhost:8080/admin -> 'admin index'
When traversal traverses "/admin/hello", the context is the "admin"
AdminModel. Since the view name is "hello", the view registered as
name="hello" (the hello method of the AdminRootView) will be used.
http://localhost:8080/admin/hello -> 'admin hello'
For more info, see the Traversal chapter in the Pyramid docs:
http://docs.pylonshq.com/pyramid/dev/narr/traversal.html
- C
URL Dispatch has "view handlers", which are classes containing view
methods, so the equivalent of Pylons1 controllers. You would normally
use handlers.py and models.py in small applications, and handlers/ and
models/ in large applications. The application templates are still in
flux but will probably default to handlers.py and models.py.
You can use function-based view callables, in which case views.py
would be appropriate, but this is more of a BFG pattern than a
Pylonsish pattern.
> 2. Are the Pyramid developers intentionally moving toward a less
> "object dispatch" setup, and a more declarative setup where routes and
> views are explicitly defined (no magic or ponies)?
I think Pyramid is moving toward URL Dispatch for everyday use, and
reserving Traversal for the specialized use cases that Chris explains
better than I can.
This move is natural based on BFG's history, and the new situation of
future Pylons newbies, which is forcing Pyramid to be more accessible
and simpler out of the box, rather than just being a "hackers'
framework" as BFG was. BFG was descended from Zope and its ZODB object
database. Traversal is ideal in this situation, especially if your
models are in the database, because the URL hierarchy is
unpredictable. (You can add an object or container to the database,
and it changes the possible URL hierarchy.) But "normal WSGI
applications" have a fixed URL hierarchy going to a fixed set of
views, so URL Dispatch can describe that more simply. I don't know
BFG's evolution intimately but I think URL Dispatch was added as an
option at some point, and as Chris began using it in his own
applications he found it more and more useful for the basic URL
structure, and limiting Traversal to hierarchies that really are
dynamic. That's the use case for the "Hybrid" approach in the manual.
I originally thought Traversal was the same as TurboGears dispatch and
wrote that in some documentation; I'll change those. The truth may be
that TurboGears has a dispatching model that is not in Pyramid, but I
don't see why it can't be fitted on top of URL Dispatch the same way
it was over Routes, if TurboGears users want to stick to their
familiar way of dispatching.
> 3. With the movement away from controllers to a view-based setup, does
> this make a Pylons 1 style "map.connect('/{controller}/{action}/
> {id}')" route setup totally foreign?
"/{action}/{id}" definitely works. "/{handler}" is problematic because
that variable is the fully-qualified dotted name of the handler; e.g.,
config.add_handler('hello', '/hello/{action}',
handler='mypackage.handlers:Hello')
-OR-
from mypackage.handlers import Hello
config.add_handler('hello', '/hello/{action}', handler=Hello)
You don't want to expose the fully-qualified handler name in the URL,
for both security and aesthetic reasons.
> 4. Would a typical "large" Pyramid app then have lines and lines of
> route/view definitions in the __init__.py (or other config file)?
You'd need one line for each handler class. An app would have to get
very large before it would have more than a dozen handlers. If you're
using "{id}", you'd need one line with it and one without it; e.g.,
"/hello/{action}" and "/hello/{action}/{id}", because some actions
take an ID and others don't. This is identical to the Pylons1 default
by the way, because minimization (which allowed optional components on
the right side of the URL in the same route) is no longer recommended.
You'd also need a line for any custom URLs that don't fit these
patterns, e.g., "/".
Regarding the relationship of the 'action' variable to view methods,
in Pylons1 they are always the same. In Pyramid they normally are too,
but it's possible to define multiple actions that map to the same view
method (presumably each with a different template). I doubt this will
be used except in specialized cases, but it's worth understanding the
difference, which is described in the "View Handlers" chapter of the
manual.
--
Mike Orr <slugg...@gmail.com>
Yep, makes sense.
>
> - TG's object dispatch (and other web frameworks I'm used to) make
> the class/module hierarchy vs. url/path hierarchy pretty
> straightforward, so it's more natural for me to think of a url as
> traversing "controller objects" rather than having "model contexts."
Pyramid just doesn't use the same paradigm, so it's understandable that
you're having some difficulty.
>
> - All the Pyramid example apps I've seen seem to have a single
> view.py containing all the "view" methods. In a larger app setting,
> wouldn't you want to split these up into separate view modules?
> (forgive me, I have not gone googling for examples of large repoze.bfg
> apps)
Sure. It's "just Python", so you can structure it as you see fit. If
you use .scan(), you don't need to do anything except break the views.py
module into separate modules.
>
> - So the issue that I'm wrestling with, of course, is how to make
> the various @view_configs load the right view method from the
> appropriate view module & method via a config.scan() process.
If you use .scan(), there's nothing you really need to do to "make this
work". Just break the views.py apart into separate modules, or create a
separate "views" subpackage (a directory with an __init__.py and
other .py files in it), or several subpackages, as necessary.
.scan() doesn't care, it will find all the views you've defined in your
entire package no matter where they live.
> So maybe I want traversal, maybe I don't. Maybe I just need a "light-
> bulb moment" about contexts. I think Pyramid's "different" way of
> doing things just has me confused, coming from a background of using
> frameworks that have a more "object dispatch" approach to urls/paths.
>
>
>
> Which brings me to my new set of questions, which are:
>
> 1. What would a larger Pyramid app look like directory/module wise?
> Wouldn't this look more like a default Pylons 1/TG 2 setup?
> (controllers-*ahem*-views in a "views" dir, models in a "models" dir,
> etc)
If you have lots of views, my suggestion would be to turn the "views.py"
*module* into a "views" package ala:
http://docs.pylonshq.com/pyramid/dev/narr/project.html#modifying-package-structure . Likewise for models.
We're interested in sharing basic layout between Pyramid paster
templates, and we're going to encourage people to not stray very far
from the layouts they suggest. But FWIW, as far as the framework is
concerned, the layout can be entirely arbitrary. It doesn't care how
you structure your code. There are no "magic" directories or files.
> 2. Are the Pyramid developers intentionally moving toward a less
> "object dispatch" setup, and a more declarative setup where routes and
> views are explicitly defined (no magic or ponies)?
The Pyramid developers never had an object dispatch to "move from".
Object dispatch in the terms you've definined via example so far, is a
TurboGears and/or CherryPy concept. Other frameworks may do it
similarly (very-early Zope2 used a similar concept) but these days it's
mostly a TurboGears thing.
Pyramid traversal is similar to object dispatch, inasmuch as code is
found as the result of traversing some graph. But the way the code is
found, and the way the graph is traversed is different.
I think you're trying to draw a distinction between the two styles of
mapping URLs to code by describing the Pyramid one as "explicit". To
the extent that Pyramid traversal never implicitly uses a method of the
objects being traversed to find a "view", I guess you could think of it
this way. However, in reality, both are equally explicit, I think.
TurboGears style object dispatch makes you define a method of the graph
object as a "view"; Pyramid view lookup makes you register a callable
using the graph object *type* as a view.
If it helps at all, you might think of Pyramid style view lookup as
extending how Python finds "a method of the traversed object". Instead
of just doing getattr(object, method_name) to find a method which acts
as the thing that returns a response, it does something more
complicated. It looks up a view callable from its registry based on the
object type. In a sense this view callable is a lot *like* a method..
it's a callable which accepts the context object as its first argument
("context"). If you mentally replace "context" with "self" here, you
can see that it starts to smell a bit like a method, albeit a method
defined external to the class definition itself.
> 3. With the movement away from controllers to a view-based setup, does
> this make a Pylons 1 style "map.connect('/{controller}/{action}/
> {id}')" route setup totally foreign?
Not totally foreign. Inasmuch as there's no dedicated "controllers"
directory in a Pyramid app, one level of implicitness is removed. But
for people who like Pylons-style dispatch, there are "handlers", which
are effectively Pylons "controllers", except the framework won't help to
locate a controller. It will however, help to locate an *action* on a
handler, so you can do:
config.add_handler('myhandler', '/blog/{action}',
handler='myproject.views.BlogHandler')
This pattern, however, is not useful with traversal, FWIW. It's a whole
separate topic.
> 4. Would a typical "large" Pyramid app then have lines and lines of
> route/view definitions in the __init__.py (or other config file)?
Could. Or might just have a single "scan" after setting up some number
of routes. Or might have no routes at all and use traversal entirely,
in which case, there'd only be a single call to "scan" in there.
>
>
> Please forgive me if all of this seems elementary to you bfg/Pyramid
> experts. I've been a web developer for years but for some reason
> Pyramid is making me feel like a n00b. Again, I think I need a
> revelation about contexts.
Not sure how to provide the revelation. I wish I could speak the magic
words, apologies.
- C
I think the idea that Pyramid is moving to URL Dispatch for "every day"
use is maybe a bit overgeneral, because it really depends whose day
you're talking about.
Pylons 1 and other frameworks like it are very much geared towards
single-purpose non-extensible bespoke applications. If you write these
sorts of apps every day, and you don't often deal with hierarchical
data, then, yes, you may want to use URL dispatch exclusively, which
means you're going to be more attracted to "add_route", and
"add_handler" than someone else might be.
However, if your day consists of maintaining a large system that is
either very extensible (it may itself be a framework of some kind,
extensible by your customers or third party integrators), or your data
set is extremely hierarchical, you'll probably be attracted to using
traversal.
You can make generalizations about which group is "larger" or "more
representative", but in reality, both groups have a good number of
constituents, and Pyramid-the-core-framework isn't likely to favor one
over the other. Individual paster templates might, however.
> This move is natural based on BFG's history, and the new situation of
> future Pylons newbies, which is forcing Pyramid to be more accessible
> and simpler out of the box, rather than just being a "hackers'
> framework" as BFG was.
(An aside, unrelated to this discussion: this statement puzzles me.
It's not like BFG has really changed that much since becoming Pyramid.
It's not "just a hackers framework" now that its name was changed?
Really?)
> BFG was descended from Zope and its ZODB object
> database. Traversal is ideal in this situation, especially if your
> models are in the database, because the URL hierarchy is
> unpredictable. (You can add an object or container to the database,
> and it changes the possible URL hierarchy.) But "normal WSGI
> applications" have a fixed URL hierarchy going to a fixed set of
> views,
Your apps may have a fixed URL hierarchy going to a fixed set of views,
and many other apps may too. But there is no such thing as a "normal
WSGI app". Every bare WSGI app invents its own dispatch mechanism.
WSGI is sort of besides the point here I think.
I think you personally want it to be a flat, ordered set of lookups, and
that's fine. But I don't think it's "abnormal" for view lookup to not
work this way.
> so URL Dispatch can describe that more simply. I don't know
> BFG's evolution intimately but I think URL Dispatch was added as an
> option at some point, and as Chris began using it in his own
> applications he found it more and more useful for the basic URL
> structure, and limiting Traversal to hierarchies that really are
> dynamic. That's the use case for the "Hybrid" approach in the manual.
I personally still often use traversal for applications that have a
fixed data hierarchy, or sometimes even *no* data hierarchy. I just
find URL dispatch easier to *explain* to people. I think, however, that
traversal is just as valid a viewfinding mechanism as URL dispatch is,
and has some distinct advantages. It just takes longer to explain.
> I originally thought Traversal was the same as TurboGears dispatch and
> wrote that in some documentation; I'll change those. The truth may be
> that TurboGears has a dispatching model that is not in Pyramid, but I
> don't see why it can't be fitted on top of URL Dispatch the same way
> it was over Routes, if TurboGears users want to stick to their
> familiar way of dispatching.
Indeed, some TG folks I believe are inventing such a thing.
BFG had a steep learning curve due ZCML and Traversal. The difference
may be more in emphasis and documentation than in software. By
emphasizing imperative configuration, moving the ZCML examples to the
back of the manual rather than as the primary way to configure
Pyramid, talking up URL Dispatch, and adding view handlers, makes
Pyramid a lot more accessible to developers who don't come from a Zope
background, and to new developers who might otherwise choose
TurboGears/Pylons/Django/Werkzeug.
--
Mike Orr <slugg...@gmail.com>
On 12/6/10 10:43 AM, Seth wrote:
> Okay, so I think I'm just having a hard time grokking the idea of a
> "context" (even after beating myself to death with the documentation).
>
> With that in mind, let me rephrase my initial question with a more
> high-level question:
>
> How is a large web application in Pyramid supposed to look?
>
>
> Yes, that's a bit vague so let me clarify my thought process here:
>
> - If I had a webapp with, say, 70+ available "routes" it seems
> silly to me to specify every single view call in the config, so I
> think I'd be a real fan of the config.scan() callable and the
> corresponding @view_config decorator.
yup, this is what it's there for. the only time i'd use an external
config is if i was explicitly supporting extensibility, if i wanted to
make room for other packages to override my view registrations with ones
of their own.
> - TG's object dispatch (and other web frameworks I'm used to) make
> the class/module hierarchy vs. url/path hierarchy pretty
> straightforward, so it's more natural for me to think of a url as
> traversing "controller objects" rather than having "model contexts."
not sure what to say about this. neither is particularly "natural".
really, traversal and view lookup are not that hard to grok. i suspect
it's only because you're trying to map it onto what you're familiar
with, which isn't really the same thing at all, that you're having
problems. let me take a stab at describing the process, maybe this will
help it click for you. i'll do it with an example, rather than in the
abstract, which also might help.
in my fictitious example app we'll have three types of objects: folders,
web pages, and calendar events. a folder can contain zero or more other
objects, of any type, including other folders. each of these objects
are 'model' objects, in both the pyramid traversal sense AND the
sqlalchemy or other ORM sense... i.e. they are instances of a model
class that are persisted in a database somewhere.
in our example, the root object is a folder; the app has a custom
get_root function that returns the root folder. the root folder, like
all folders, has a __getitem__ method that returns the contents of that
folder. the root folder contains "user folders". each user folder is a
workspace for a given user, and inside of each user folder that user is
allowed to add and manage content of any of the three defined types.
okay, so now we've set the model / traversal stage. but what about
views? each content type will have a display view and an edit view,
with the display view as the default. the display view will be named
"view", and the edit view will be named "edit". these will have the
same name and function for each model type, but the specific
implementation of each view will obviously be different; displaying
and/or editing a folder is not the same thing as displaying and/or
editing a plain web page.
so now we're ready to look at the whole process. imagine a web request
with the following path:
/mike/special-project/meeting-2010-12-12/edit
as always, traversal starts w/ the root object, '/'. then the
root.__getitem__('mike') will be called, to see if a user folder named
'mike' exists. if not, 404. (the system will actually first look to
see if a view named 'mike' is registered for folder objects, but this
isn't the case, so 404 it is.)
but mike does exist, and he does have a user folder. so traversal now
has this folder, upon which is called __getitem__('special-project').
there are now three possibilities:
- there is no 'special-project' item in the mike folder, so we get a
404 as before.
- there is a 'special-project' web page or calendar event object in
the mike folder. this object will be returned, but a) it doesn't have a
__getitem__ method and b) there is no 'meeting-2010-12-12' view
registered, so again we end up w/ a 404.
- there is a 'special-project' folder in the mike folder. this has a
__getitem__ method, of course, so traversal continues.
let's assume the latter of these is what we have. yet again we call
__getitem__ on the retrieved folder, this time looking for a
'meeting-2010-12-12' object. if it does not exist... yup, you guessed
it: 404. but let's assume it does exist, and that it's a calendar event
object. this object will be retrieved and, since there's no __getitem__
method, we'll know that traversal has terminated, and the event object
is our final traversal context.
that means that we've finally arrived at view lookup. we've got an
event object, and the next path element is 'edit'. has an edit view
been registered for event objects? yes; as stated originally, an event
object has been registered for EVERY object type. but since we have an
event object context, the event object's edit view is what is returned.
presumably this view would render an edit form template in the simple
case, or, if we are making a POST request, this view would validate and
(if valid) persist the submitted form values, ultimately redirecting to
the display view with an 'event saved' message.
okay, that was all quite a bit longer than i intended, but along the way
i think i managed to cover most of the possible results of a single
traversal / view lookup attempt. hopefully that will help to give you a
fresh look at how this all fits together.
> - All the Pyramid example apps I've seen seem to have a single
> view.py containing all the "view" methods. In a larger app setting,
> wouldn't you want to split these up into separate view modules?
> (forgive me, I have not gone googling for examples of large repoze.bfg
> apps)
yes, it would absolutely be a good idea to use 'view' package folder
with separate modules if you have many views.
> - So the issue that I'm wrestling with, of course, is how to make
> the various @view_configs load the right view method from the
> appropriate view module& method via a config.scan() process.
when you decorate a function with @view_config, you're registering it as
a view with a specific name for a specific context type (or types). the
view registration happens when config.scan() is called against the
module containing the decorated view methods, typically at pyramid
startup. whenever traversal has resolved itself to a specific object,
the registry will be checked to see if that object type has a registered
view with the specified name. does that make sense now?
> So maybe I want traversal, maybe I don't. Maybe I just need a "light-
> bulb moment" about contexts. I think Pyramid's "different" way of
> doing things just has me confused, coming from a background of using
> frameworks that have a more "object dispatch" approach to urls/paths.
hopefully the baby-steps example above at least nudges you in the
direction of such a "light-bulb" moment.
> Which brings me to my new set of questions, which are:
>
> 1. What would a larger Pyramid app look like directory/module wise?
> Wouldn't this look more like a default Pylons 1/TG 2 setup?
> (controllers-*ahem*-views in a "views" dir, models in a "models" dir,
> etc)
yes, this would be a fine way to organize a larger pyramid app.
although, for pylons developers, there will also be "handlers".
handlers are essentially classes that aggregate a set of related views
as methods on that class, with some shortcuts provided for registering
these views. a handler is roughly analogous to the pylons 1.0 idea of a
controller.
> 2. Are the Pyramid developers intentionally moving toward a less
> "object dispatch" setup, and a more declarative setup where routes and
> views are explicitly defined (no magic or ponies)?
pyramid developers aren't "moving toward" a less object dispatch
setup... they've never HAD an object dispatch approach, that's a TG
thing, so there's no need to move away from one.
but yes, routes and views should be explicitly defined. again, handlers
will provide some shortcuts for mapping related routes to a related set
of views, similar to how pylons controllers work, which will make the
view mapping a tiny bit less explicit, in a
"convention-over-configuration" way. but handlers are optional; use
them if they fit your brain, don't use them if they don't.
> 3. With the movement away from controllers to a view-based setup, does
> this make a Pylons 1 style "map.connect('/{controller}/{action}/
> {id}')" route setup totally foreign?
nope, handlers are the concept that keep that style of thinking relevant.
> 4. Would a typical "large" Pyramid app then have lines and lines of
> route/view definitions in the __init__.py (or other config file)?
if you were building an extensive and extensible large pyramid app, then
you might conceivably have many view registrations in zcml
configuration. if extensibility was not a concern, probably view
decorators would be used; in that case the view registration lives right
with the view function itself. also, handlers could be used, which
would then allow multiple views to be registered all at once as part of
the handler registration.
> Please forgive me if all of this seems elementary to you bfg/Pyramid
> experts. I've been a web developer for years but for some reason
> Pyramid is making me feel like a n00b. Again, I think I need a
> revelation about contexts.
no worries, those of us who've been doing this for years often forget
how mind-bending this can be to someone who's never used it before,
especially someone who is used to something that is close enough to be
confusing, but which is still fundamentally different. the good news is
that i can assure you that once it clicks it's really quite easy to work
with... if you decide to use it, it only takes a very short while before
you'll be thinking in terms of traversal and view lookups, and then
_you'll_ be the one forgetting how mind-bending this can be to someone
else...
-r
Traversal has been optional since the framework was released in July of
1998 (it has always had "url dispatch").
ZCML has not been required since BFG 1.2, released ten months ago.
> The difference
> may be more in emphasis and documentation than in software. By
> emphasizing imperative configuration, moving the ZCML examples to the
> back of the manual rather than as the primary way to configure
> Pyramid, talking up URL Dispatch, and adding view handlers, makes
> Pyramid a lot more accessible to developers who don't come from a Zope
> background, and to new developers who might otherwise choose
> TurboGears/Pylons/Django/Werkzeug.
Fair enough, yes, the documentation has been updated.
That said, I think our job is to provide a set of features that can
accomodate a good number of use cases easily. Handlers, and imperative
configuration are ways to allow for some of those use cases. But I
reject the notion that these combined are somehow "the one, true, right
way" for all prospective new users. Perhaps just simpler to explain.
- C
Whoa, time machine set too far back. *2008*. ;-)
- C
[...]
>> I originally thought Traversal was the same as TurboGears dispatch and
>> wrote that in some documentation; I'll change those. The truth may be
>> that TurboGears has a dispatching model that is not in Pyramid, but I
>> don't see why it can't be fitted on top of URL Dispatch the same way
>> it was over Routes, if TurboGears users want to stick to their
>> familiar way of dispatching.
>
> Indeed, some TG folks I believe are inventing such a thing.
>
and Chris is certainly speaking about this:
http://bitbucket.org/pyramid_tg/pyramid_expose
warning: this is a work in progress and may still have problems and we
are thinking about the internals to comply as much as possible with
the pyramid way of doing things but the way of using should not change
that much... any constructive feed-back is welcome.
Florent.