diff options
Diffstat (limited to 'docs')
67 files changed, 3300 insertions, 3477 deletions
diff --git a/docs/api.rst b/docs/api.rst index 4808a08b3..b650c8ded 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -15,7 +15,6 @@ documentation is organized alphabetically by module name. api/config api/events api/exceptions - api/flash api/httpexceptions api/i18n api/interfaces diff --git a/docs/api/flash.rst b/docs/api/flash.rst deleted file mode 100644 index 94907958d..000000000 --- a/docs/api/flash.rst +++ /dev/null @@ -1,36 +0,0 @@ -.. _flash_module: - -:mod:`pyramid.flash` --------------------- - -Flash Category Constants -~~~~~~~~~~~~~~~~~~~~~~~~ - -The following attributes represent constants for use as flash messaging -category values (see :ref:`flash_chapter`). - -.. attribute:: DEBUG - - An alternate spelling for the string ``debug``. Represents development - debug messages. - -.. attribute:: INFO - - An alternate spelling for the string ``info``. Represents messages that - are informational for user consumption. - -.. attribute:: SUCCESS - - An alternate spelling for the string ``success``. Represents messages that - tell the user about a successful action. - -.. attribute:: WARNING - - An alternate spelling for the string ``warning``. Represents messages - that tell the user about a condition that is not a success, but is neither - an error. - -.. attribute:: ERROR - - An alternate spelling for the string ``success``. Represents messages - that tell the user about an unsuccessful action. diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index dab64ba15..b3c14e5f7 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -35,4 +35,3 @@ Other Interfaces .. autointerface:: ITemplateRenderer - .. autointerface:: IFlashMessages diff --git a/docs/api/view.rst b/docs/api/view.rst index 0057cca4a..4dddea25f 100644 --- a/docs/api/view.rst +++ b/docs/api/view.rst @@ -18,6 +18,7 @@ .. autoclass:: static :members: + :inherited-members: .. autofunction:: append_slash_notfound_view(context, request) diff --git a/docs/conf.py b/docs/conf.py index 7bcdf3a07..8c238cecd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -76,7 +76,7 @@ copyright = '%s, Agendaless Consulting' % datetime.datetime.now().year # other places throughout the built documents. # # The short X.Y version. -version = '1.0a7' +version = '1.0a8' # The full version, including alpha/beta/rc tags. release = version diff --git a/docs/designdefense.rst b/docs/designdefense.rst index 5aa149824..53b95b9d0 100644 --- a/docs/designdefense.rst +++ b/docs/designdefense.rst @@ -3,133 +3,121 @@ Defending Pyramid's Design ========================== -From time to time, challenges to various aspects of :app:`Pyramid` -design are lodged. To give context to discussions that follow, we -detail some of the design decisions and trade-offs here. In some -cases, we acknowledge that the framework can be made better and we -describe future steps which will be taken to improve it; in some cases -we just file the challenge as "noted", as obviously you can't please -everyone all of the time. - -Pyramid Has Zope Things In It, So It's Too Complex --------------------------------------------------- - -On occasion, someone will feel compelled to post a mailing -list message that reads something like this: - -.. code-block:: text - - had a quick look at pyramid ... too complex to me and not really - understand for which benefits.. I feel should consider whether it's time - for me to step back to django .. I always hated zope (useless ?) - complexity and I love simple way of thinking - -(Paraphrased from a real email, actually.) - -Let's take this criticism point-by point. - -Too Complex -+++++++++++ - -- If you can understand this hello world program, you can use Pyramid: - -.. code-block:: python - :linenos: - - from paste.httpserver import serve - from pyramid.config import Configurator - from pyramid.response import Response - - def hello_world(request): - return Response('Hello world!') - - if __name__ == '__main__': - config = Configurator() - config.add_view(hello_world) - app = config.make_wsgi_app() - serve(app) - -- Pyramid is 5,000 lines of runtime code. Pylons 1.0 has about 3,000 lines - of runtime code. Django has about 60,000 lines of runtime code. You'd - practically need to bend the laws of space and time for Django to be - simpler than Pyramid. - -- It has 600 or more pages of documentation (printed), covering topics from - the very basic to the most advanced. *Nothing* is left undocumented, quite - literally. - -- It has an *awesome*, very helpful community. Visit the #repoze and/or - #pylons IRC channels on freenode.net and see. - -Hate Zope -+++++++++ - -I'm sorry you feel that way. The Zope brand has certainly taken its share of -lumps over the years, and has a reputation for being insular and mysterious. -But the word "Zope" is literally quite meaningless without qualification. -What *part* of Zope do you hate? "Zope" is a brand, not a technology. - -If it's Zope2-the-web-framework, Pyramid is not that. The primary designers -and developers of Pyramid, if anyone, should know. We wrote Pyramid's -predecessor (:mod:`repoze.bfg`), in part, because *we* knew that Zope 2 had -usability issues and limitations. :mod:`repoze.bfg` (and now :app:`Pyramid`) -was written to address these issues. - -If it's Zope3-the-web-framework, Pyramid is *definitely* not that. Making -use of lots of Zope 3 technologies is territory already staked out by the -:term:`Grok` project. Save for the obvious fact that they're both web -frameworks, :mod:`Pyramid` is very, very different than Grok. Grok exposes -lots of Zope technologies to end users. On the other hand, if you need to -understand a Zope-only concept while using Pyramid, then we've failed on some -very basic axis. - -If it's just the word Zope: it's, charitably, only guilt by association. You -need to understand that just because a piece of software internally uses some -package named ``zope.foo``, it doesn't turn the piece of software that uses -it into "Zope". There is a lot of *great* software written that has the word -Zope in its name. Zope is not some sort of monolithic thing, and a lot of -its software is usable externally. - -Zope Is Useless -+++++++++++++++ - -It's not really the job of this document to defend Zope. But Zope has been -around for over 10 years and has an incredibly large, active community. If -you don't believe this, http://taichino.appspot.com/pypi_ranking/authors is -an eye-opening reality check. - -Love Simplicity -+++++++++++++++ +From time to time, challenges to various aspects of :app:`Pyramid` design are +lodged. To give context to discussions that follow, we detail some of the +design decisions and trade-offs here. In some cases, we acknowledge that the +framework can be made better and we describe future steps which will be taken +to improve it; in some cases we just file the challenge as "noted", as +obviously you can't please everyone all of the time. + +Pyramid Provides More Than One Way to Do It +------------------------------------------- + +A canon of Python popular culture is "TIOOTDI" ("there is only one way to do +it", a slighting, tongue-in-cheek reference to Perl's "TIMTOWTDI", which is +an acronym for "there is more than one way to do it"). + +:app:`Pyramid` is, for better or worse, a "TIMTOWTDI" system. For example, +it includes more than one way to resolve a URL to a :term:`view callable`: +via :term:`url dispatch` or :term:`traversal`. Multiple methods of +configuration exist: :term:`imperative configuration`, :term:`configuration +decoration`, and :term:`ZCML`. It works with multiple different kinds of +persistence and templating systems. And so on. However, the existence of +most of these overlapping ways to do things are not without reason and +purpose: we have a number of audiences to serve, and we believe that TIMTOWTI +at the web framework level actually *prevents* a much more insidious and +harmful set of duplication at higher levels in the Python web community. + +:app:`Pyramid` began its life as :mod:`repoze.bfg`, written by a team of +people with many years of prior :ref:`Zope` experience. The idea of +:term:`traversal`, the usage of :term:`ZCML` and the way :term:`view lookup` +works was stolen entirely from Zope. The authorization subsystem provided by +:app:`Pyramid` is a derivative of Zope's. The idea that an application can +be *extended* without forking is also a Zope derivative. + +Implementations of these features were *required* to allow the :app:`Pyramid` +authors to build the bread-and-butter CMS-type systems for customers in the +way they were accustomed to building them. No other system save Zope itself +had such features. And Zope itself was beginning to show signs of its age. +We were becoming hampered by consequences of its early design mistakes. +Zope's lack of documentation was also difficult to work around: it was hard +to hire smart people to work on Zope applications, because there was no +comprehensive documentation set to point them at which explained "it all" in +one consumble place, and it was too large and self-inconsistent to document +properly. Before :mod:`repoze.bfg` went under development, its authors +obviously looked around for other frameworks that fit the bill. But no +non-Zope framework did. So we embarked on building :mod:`repoze.bfg`. + +As the result of our research, however, it became apparent that, despite the +fact that no *one* framework had all the features we required, lots of +existing frameworks had good, and sometimes very compelling ideas. In +particular, :term:`URL dispatch` is a more direct mechanism to map URLs to +code. -Years of effort have gone into honing this package and its documentation to -make it as simple as humanly possible for developers to use. Everything is a -tradeoff, of course, and people have their own ideas about what "simple" is. -You may have a style difference if you believe Pyramid is complex. Its -developers obviously disagree. +So although we couldn't find a framework save for Zope that fit our needs, +and while we incorporated a lot of Zope ideas into BFG, we also emulated the +features we found compelling in other frameworks (such as :term:`url +dispatch`). After the initial public release of BFG, as time went on, +features were added to support people allergic to various Zope-isms in the +system, such as the ability to configure the application using +:term:`imperative configuration` rather than solely using :term:`ZCML`, and +the elimination of the required use of :term:`interface` objects. It soon +became clear that we had a system that was very generic, and was beginning to +appeal to non-Zope users as well as ex-Zope users. + +As the result of this generalization, it became obvious BFG shared 90% of its +featureset with the featureset of Pylons 1, and thus had a very similar +target market. Because they were so similar, choosing between the two +systems was an exercise in frustration for an otherwise non-partisan +developer. It was also strange for the Pylons and BFG development +communities to be in competition for the same set of users, given how similar +the two frameworks were. So the Pylons and BFG teams began to work together +to form a plan to "merge". The features missing from BFG (notably +:term:`view handler` classes, flash messaging, and other minor missing bits), +were added, to provide familiarity to ex-Pylons users. The result is +:app:`Pyramid`. + +The Python web framework space is currently notoriously balkanized. We're +truly hoping that the amalgamation of components in :app:`Pyramid` will +appeal to at least two currently very distinct sets of users: Pylons and BFG +users. By unifying the best concepts from Pylons and BFG into a single +codebase and leaving the bad concepts from their ancestors behind, we'll be +able to consolidate our efforts better, share more code, and promote our +efforts as a unit rather than competing pointlessly. We hope to be able to +shortcut the pack mentality which results in a *much larger* duplication of +effort, represented by competing but incredibly similar applications and +libraries, each built upon a specific low level stack that is incompatible +with the other. We'll also shrink the choice of credible Python web +frameworks down by at least one. We're also hoping to attract users from +other communities (such as Zope's and TurboGears') by providing the features +they require, while allowing enough flexibility to do things in a familiar +fashion. Some overlap of functionality to achieve these goals is expected +and unavoidable, at least if we aim to prevent pointless duplication at +higher levels. If we've done our job well enough, the various audiences will +be able to coexist and cooperate rather than firing at each other across some +imaginary web framework "DMZ". Pyramid Uses A Zope Component Architecture ("ZCA") Registry ----------------------------------------------------------- -:app:`Pyramid` uses a :term:`Zope Component Architecture` (ZCA) -"component registry" as its :term:`application registry` under the -hood. This is a point of some contention. :app:`Pyramid` is of a -:term:`Zope` pedigree, so it was natural for its developers to use a -ZCA registry at its inception. However, we understand that using a -ZCA registry has issues and consequences, which we've attempted to -address as best we can. Here's an introspection about -:app:`Pyramid` use of a ZCA registry, and the trade-offs its usage +:app:`Pyramid` uses a :term:`Zope Component Architecture` (ZCA) "component +registry" as its :term:`application registry` under the hood. This is a +point of some contention. :app:`Pyramid` is of a :term:`Zope` pedigree, so +it was natural for its developers to use a ZCA registry at its inception. +However, we understand that using a ZCA registry has issues and consequences, +which we've attempted to address as best we can. Here's an introspection +about :app:`Pyramid` use of a ZCA registry, and the trade-offs its usage involves. Problems ++++++++ The "global" API that may be used to access data in a ZCA "component -registry" is not particularly pretty or intuitive, and sometimes it's -just plain obtuse. Likewise, the conceptual load on a casual source -code reader of code that uses the ZCA global API is somewhat high. -Consider a ZCA neophyte reading the code that performs a typical -"unnamed utility" lookup using the :func:`zope.component.getUtility` -global API: +registry" is not particularly pretty or intuitive, and sometimes it's just +plain obtuse. Likewise, the conceptual load on a casual source code reader +of code that uses the ZCA global API is somewhat high. Consider a ZCA +neophyte reading the code that performs a typical "unnamed utility" lookup +using the :func:`zope.component.getUtility` global API: .. ignore-next-block .. code-block:: python @@ -139,74 +127,68 @@ global API: from zope.component import getUtility settings = getUtility(ISettings) -After this code runs, ``settings`` will be a Python dictionary. But -it's unlikely that any "civilian" would know that just by reading the -code. There are a number of comprehension issues with the bit of code -above that are obvious. - -First, what's a "utility"? Well, for the purposes of this discussion, -and for the purpose of the code above, it's just not very important. -If you really want to know, you can read `this -<http://www.muthukadan.net/docs/zca.html#utility>`_. However, still, -readers of such code need to understand the concept in order to parse -it. This is problem number one. - -Second, what's this ``ISettings`` thing? It's an :term:`interface`. -Is that important here? Not really, we're just using it as a "key" -for some lookup based on its identity as a marker: it represents an -object that has the dictionary API, but that's not very important in -this context. That's problem number two. - -Third of all, what does the ``getUtility`` function do? It's -performing a lookup for the ``ISettings`` "utility" that should -return.. well, a utility. Note how we've already built up a -dependency on the understanding of an :term:`interface` and the -concept of "utility" to answer this question: a bad sign so far. Note -also that the answer is circular, a *really* bad sign. - -Fourth, where does ``getUtility`` look to get the data? Well, the -"component registry" of course. What's a component registry? Problem -number four. - -Fifth, assuming you buy that there's some magical registry hanging -around, where *is* this registry? *Homina homina*... "around"? -That's sort of the best answer in this context (a more specific answer -would require knowledge of internals). Can there be more than one -registry? Yes. So *which* registry does it find the registration in? -Well, the "current" registry of course. In terms of -:app:`Pyramid`, the current registry is a thread local variable. -Using an API that consults a thread local makes understanding how it -works non-local. +After this code runs, ``settings`` will be a Python dictionary. But it's +unlikely that any "civilian" would know that just by reading the code. There +are a number of comprehension issues with the bit of code above that are +obvious. + +First, what's a "utility"? Well, for the purposes of this discussion, and +for the purpose of the code above, it's just not very important. If you +really want to know, you can read `this +<http://www.muthukadan.net/docs/zca.html#utility>`_. However, still, readers +of such code need to understand the concept in order to parse it. This is +problem number one. + +Second, what's this ``ISettings`` thing? It's an :term:`interface`. Is that +important here? Not really, we're just using it as a "key" for some lookup +based on its identity as a marker: it represents an object that has the +dictionary API, but that's not very important in this context. That's +problem number two. + +Third of all, what does the ``getUtility`` function do? It's performing a +lookup for the ``ISettings`` "utility" that should return.. well, a utility. +Note how we've already built up a dependency on the understanding of an +:term:`interface` and the concept of "utility" to answer this question: a bad +sign so far. Note also that the answer is circular, a *really* bad sign. + +Fourth, where does ``getUtility`` look to get the data? Well, the "component +registry" of course. What's a component registry? Problem number four. + +Fifth, assuming you buy that there's some magical registry hanging around, +where *is* this registry? *Homina homina*... "around"? That's sort of the +best answer in this context (a more specific answer would require knowledge +of internals). Can there be more than one registry? Yes. So *which* +registry does it find the registration in? Well, the "current" registry of +course. In terms of :app:`Pyramid`, the current registry is a thread local +variable. Using an API that consults a thread local makes understanding how +it works non-local. You've now bought in to the fact that there's a registry that is just "hanging around". But how does the registry get populated? Why, :term:`ZCML` of course. Sometimes. Or via imperative code. In this -particular case, however, the registration of ``ISettings`` is made by -the framework itself "under the hood": it's not present in any ZCML -nor was it performed imperatively. This is extremely hard to -comprehend. Problem number six. +particular case, however, the registration of ``ISettings`` is made by the +framework itself "under the hood": it's not present in any ZCML nor was it +performed imperatively. This is extremely hard to comprehend. Problem +number six. -Clearly there's some amount of cognitive load here that needs to be -borne by a reader of code that extends the :app:`Pyramid` framework -due to its use of the ZCA, even if he or she is already an expert -Python programmer and whom is an expert in the domain of web -applications. This is suboptimal. +Clearly there's some amount of cognitive load here that needs to be borne by +a reader of code that extends the :app:`Pyramid` framework due to its use of +the ZCA, even if he or she is already an expert Python programmer and whom is +an expert in the domain of web applications. This is suboptimal. Ameliorations +++++++++++++ -First, the primary amelioration: :app:`Pyramid` *does not expect -application developers to understand ZCA concepts or any of its APIs*. -If an *application* developer needs to understand a ZCA concept or API -during the creation of a :app:`Pyramid` application, we've failed -on some axis. +First, the primary amelioration: :app:`Pyramid` *does not expect application +developers to understand ZCA concepts or any of its APIs*. If an +*application* developer needs to understand a ZCA concept or API during the +creation of a :app:`Pyramid` application, we've failed on some axis. Instead, the framework hides the presence of the ZCA registry behind -special-purpose API functions that *do* use ZCA APIs. Take for -example the ``pyramid.security.authenticated_userid`` function, -which returns the userid present in the current request or ``None`` if -no userid is present in the current request. The application -developer calls it like so: +special-purpose API functions that *do* use ZCA APIs. Take for example the +``pyramid.security.authenticated_userid`` function, which returns the userid +present in the current request or ``None`` if no userid is present in the +current request. The application developer calls it like so: .. ignore-next-block .. code-block:: python @@ -234,38 +216,34 @@ is this: return None return policy.authenticated_userid(request) -Using such wrappers, we strive to always hide the ZCA API from -application developers. Application developers should just never know -about the ZCA API: they should call a Python function with some object -germane to the domain as an argument, and it should returns a result. -A corollary that follows is that any reader of an application that has -been written using :app:`Pyramid` needn't understand the ZCA API -either. - -Hiding the ZCA API from application developers and code readers is a -form of enhancing "domain specificity". No application developer -wants to need to understand the minutiae of the mechanics of how a web -framework does its thing. People want to deal in concepts that are -closer to the domain they're working in: for example, web developers -want to know about *users*, not *utilities*. :app:`Pyramid` uses -the ZCA as an implementation detail, not as a feature which is exposed -to end users. - -However, unlike application developers, *framework developers*, -including people who want to override :app:`Pyramid` functionality -via preordained framework plugpoints like traversal or view lookup -*must* understand the ZCA registry API. - -:app:`Pyramid` framework developers were so concerned about -conceptual load issues of the ZCA registry API for framework -developers that a `replacement registry implementation -<http://svn.repoze.org/repoze.component/trunk>`_ named -:mod:`repoze.component` was actually developed. Though this package -has a registry implementation which is fully functional and -well-tested, and its API is much nicer than the ZCA registry API, work -on it was largely abandoned and it is not used in :app:`Pyramid`. -We continued to use a ZCA registry within :app:`Pyramid` because it -ultimately proved a better fit. +Using such wrappers, we strive to always hide the ZCA API from application +developers. Application developers should just never know about the ZCA API: +they should call a Python function with some object germane to the domain as +an argument, and it should returns a result. A corollary that follows is +that any reader of an application that has been written using :app:`Pyramid` +needn't understand the ZCA API either. + +Hiding the ZCA API from application developers and code readers is a form of +enhancing "domain specificity". No application developer wants to need to +understand the minutiae of the mechanics of how a web framework does its +thing. People want to deal in concepts that are closer to the domain they're +working in: for example, web developers want to know about *users*, not +*utilities*. :app:`Pyramid` uses the ZCA as an implementation detail, not as +a feature which is exposed to end users. + +However, unlike application developers, *framework developers*, including +people who want to override :app:`Pyramid` functionality via preordained +framework plugpoints like traversal or view lookup *must* understand the ZCA +registry API. + +:app:`Pyramid` framework developers were so concerned about conceptual load +issues of the ZCA registry API for framework developers that a `replacement +registry implementation <http://svn.repoze.org/repoze.component/trunk>`_ +named :mod:`repoze.component` was actually developed. Though this package +has a registry implementation which is fully functional and well-tested, and +its API is much nicer than the ZCA registry API, work on it was largely +abandoned and it is not used in :app:`Pyramid`. We continued to use a ZCA +registry within :app:`Pyramid` because it ultimately proved a better fit. .. note:: We continued using ZCA registry rather than disusing it in favor of using the registry implementation in @@ -276,151 +254,97 @@ ultimately proved a better fit. that allowed for this functionality seemed like it was just reinventing the wheel. -Making framework developers and extenders understand the ZCA registry -API is a trade-off. We (the :app:`Pyramid` developers) like the -features that the ZCA registry gives us, and we have long-ago borne -the weight of understanding what it does and how it works. The -authors of :app:`Pyramid` understand the ZCA deeply and can read -code that uses it as easily as any other code. - -But we recognize that developers who my want to extend the framework -are not as comfortable with the ZCA registry API as the original -developers are with it. So, for the purposes of being kind to -third-party :app:`Pyramid` framework developers in, we've drawn -some lines in the sand. - -#) In all "core" code, We've made use of ZCA global API functions such - as ``zope.component.getUtility`` and ``zope.component.getAdapter`` - the exception instead of the rule. So instead of: - - .. code-block:: python - :linenos: - - from pyramid.interfaces import IAuthenticationPolicy - from zope.component import getUtility - policy = getUtility(IAuthenticationPolicy) - - :app:`Pyramid` code will usually do: - - .. code-block:: python - :linenos: - - from pyramid.interfaces import IAuthenticationPolicy - from pyramid.threadlocal import get_current_registry - registry = get_current_registry() - policy = registry.getUtility(IAuthenticationPolicy) +Making framework developers and extenders understand the ZCA registry API is +a trade-off. We (the :app:`Pyramid` developers) like the features that the +ZCA registry gives us, and we have long-ago borne the weight of understanding +what it does and how it works. The authors of :app:`Pyramid` understand the +ZCA deeply and can read code that uses it as easily as any other code. - While the latter is more verbose, it also arguably makes it more - obvious what's going on. All of the :app:`Pyramid` core code uses - this pattern rather than the ZCA global API. +But we recognize that developers who might want to extend the framework are not +as comfortable with the ZCA registry API as the original developers are with +it. So, for the purposes of being kind to third-party :app:`Pyramid` +framework developers in, we've drawn some lines in the sand. -#) We've turned the component registry used by :app:`Pyramid` into - something that is accessible using the plain old dictionary API - (like the :mod:`repoze.component` API). For example, the snippet - of code in the problem section above was: +In all "core" code, We've made use of ZCA global API functions such as +``zope.component.getUtility`` and ``zope.component.getAdapter`` the exception +instead of the rule. So instead of: - .. code-block:: python - :linenos: - - from pyramid.interfaces import ISettings - from zope.component import getUtility - settings = getUtility(ISettings) - - In a better world, we might be able to spell this as: - - .. code-block:: python - :linenos: - - from pyramid.threadlocal import get_current_registry - - registry = get_current_registry() - settings = registry['settings'] +.. code-block:: python + :linenos: - In this world, we've removed the need to understand utilities and - interfaces, because we've disused them in favor of a plain dictionary - lookup. We *haven't* removed the need to understand the concept of a - *registry*, but for the purposes of this example, it's simply a - dictionary. We haven't killed off the concept of a thread local - either. Let's kill off thread locals, pretending to want to do this - in some code that has access to the :term:`request`: + from pyramid.interfaces import IAuthenticationPolicy + from zope.component import getUtility + policy = getUtility(IAuthenticationPolicy) - .. code-block:: python - :linenos: +:app:`Pyramid` code will usually do: - registry = request.registry - settings = registry['settings'] +.. code-block:: python + :linenos: - In *this* world, we've reduced the conceptual problem to understanding - attributes and the dictionary API. Every Python programmer knows - these things, even framework programmers. + from pyramid.interfaces import IAuthenticationPolicy + from pyramid.threadlocal import get_current_registry + registry = get_current_registry() + policy = registry.getUtility(IAuthenticationPolicy) -While :app:`Pyramid` still uses some suboptimal unnamed utility -registrations, future versions of it will where possible disuse these -things in favor of straight dictionary assignments and lookups, as -demonstrated above, to be kinder to new framework developers. We'll -continue to seek ways to reduce framework developer cognitive load. +While the latter is more verbose, it also arguably makes it more obvious +what's going on. All of the :app:`Pyramid` core code uses this pattern +rather than the ZCA global API. Rationale +++++++++ -Here are the main rationales involved in the :app:`Pyramid` -decision to use the ZCA registry: - -- Pedigree. A nontrivial part of the answer to this question is - "pedigree". Much of the design of :app:`Pyramid` is stolen - directly from :term:`Zope`. Zope uses the ZCA registry to do a - number of tricks. :app:`Pyramid` mimics these tricks, and, - because the ZCA registry works well for that set of tricks, - :app:`Pyramid` uses it for the same purposes. For example, the - way that :app:`Pyramid` maps a :term:`request` to a :term:`view - callable` is lifted almost entirely from Zope. The ZCA registry - plays an important role in the particulars of how this request to - view mapping is done. - -- Features. The ZCA component registry essentially provides what can - be considered something like a "superdictionary", which allows for - more complex lookups than retrieving a value based on a single key. - Some of this lookup capability is very useful for end users, such as - being able to register a view that is only found when the context is - some class of object, or when the context implements some - :term:`interface`. - -- Singularity. There's only one "place" where "application - configuration" lives in a :app:`Pyramid` application: in a - component registry. The component registry answers questions made - to it by the framework at runtime based on the configuration of *an - application*. Note: "an application" is not the same as "a - process", multiple independently configured copies of the same - :app:`Pyramid` application are capable of running in the same +Here are the main rationales involved in the :app:`Pyramid` decision to use +the ZCA registry: + +- Pedigree. A nontrivial part of the answer to this question is "pedigree". + Much of the design of :app:`Pyramid` is stolen directly from :term:`Zope`. + Zope uses the ZCA registry to do a number of tricks. :app:`Pyramid` mimics + these tricks, and, because the ZCA registry works well for that set of + tricks, :app:`Pyramid` uses it for the same purposes. For example, the way + that :app:`Pyramid` maps a :term:`request` to a :term:`view callable` using + :term:`traversal` is lifted almost entirely from Zope. The ZCA registry + plays an important role in the particulars of how this request to view + mapping is done. + +- Features. The ZCA component registry essentially provides what can be + considered something like a "superdictionary", which allows for more + complex lookups than retrieving a value based on a single key. Some of + this lookup capability is very useful for end users, such as being able to + register a view that is only found when the context is some class of + object, or when the context implements some :term:`interface`. + +- Singularity. There's only one "place" where "application configuration" + lives in a :app:`Pyramid` application: in a component registry. The + component registry answers questions made to it by the framework at runtime + based on the configuration of *an application*. Note: "an application" is + not the same as "a process", multiple independently configured copies of + the same :app:`Pyramid` application are capable of running in the same process space. -- Composability. A ZCA component registry can be populated - imperatively, or there's an existing mechanism to populate a - registry via the use of a configuration file (ZCML). We didn't need - to write a frontend from scratch to make use of - configuration-file-driven registry population. - -- Pluggability. Use of the ZCA registry allows for framework - extensibility via a well-defined and widely understood plugin - architecture. As long as framework developers and extenders - understand the ZCA registry, it's possible to extend - :app:`Pyramid` almost arbitrarily. For example, it's relatively - easy to build a ZCML directive that registers several views "all at - once", allowing app developers to use that ZCML directive as a - "macro" in code that they write. This is somewhat of a - differentiating feature from other (non-Zope) frameworks. - -- Testability. Judicious use of the ZCA registry in framework code - makes testing that code slightly easier. Instead of using - monkeypatching or other facilities to register mock objects for - testing, we inject dependencies via ZCA registrations and then use - lookups in the code find our mock objects. - -- Speed. The ZCA registry is very fast for a specific set of complex - lookup scenarios that :app:`Pyramid` uses, having been optimized - through the years for just these purposes. The ZCA registry - contains optional C code for this purpose which demonstrably has no - (or very few) bugs. +- Composability. A ZCA component registry can be populated imperatively, or + there's an existing mechanism to populate a registry via the use of a + configuration file (ZCML). We didn't need to write a frontend from scratch + to make use of configuration-file-driven registry population. + +- Pluggability. Use of the ZCA registry allows for framework extensibility + via a well-defined and widely understood plugin architecture. As long as + framework developers and extenders understand the ZCA registry, it's + possible to extend :app:`Pyramid` almost arbitrarily. For example, it's + relatively easy to build a ZCML directive that registers several views "all + at once", allowing app developers to use that ZCML directive as a "macro" + in code that they write. This is somewhat of a differentiating feature + from other (non-Zope) frameworks. + +- Testability. Judicious use of the ZCA registry in framework code makes + testing that code slightly easier. Instead of using monkeypatching or + other facilities to register mock objects for testing, we inject + dependencies via ZCA registrations and then use lookups in the code find + our mock objects. + +- Speed. The ZCA registry is very fast for a specific set of complex lookup + scenarios that :app:`Pyramid` uses, having been optimized through the years + for just these purposes. The ZCA registry contains optional C code for + this purpose which demonstrably has no (or very few) bugs. - Ecosystem. Many existing Zope packages can be used in :app:`Pyramid` with few (or no) changes due to our use of the ZCA @@ -431,17 +355,16 @@ Conclusion If you only *develop applications* using :app:`Pyramid`, there's not much to complain about here. You just should never need to understand the ZCA -registry or even know about its presence: use documented :app:`Pyramid` APIs -instead. However, you may be an application developer who doesn't read API -documentation because it's unmanly. Instead you read the raw source code, and -because you haven't read the documentation, you don't know what functions, -classes, and methods even *form* the :app:`Pyramid` API. As a result, you've -now written code that uses internals and you've painted yourself into a -conceptual corner as a result of needing to wrestle with some ZCA-using -implementation detail. If this is you, it's extremely hard to have a lot of -sympathy for you. You'll either need to get familiar with how we're using -the ZCA registry or you'll need to use only the documented APIs; that's why -we document them as APIs. +registry API: use documented :app:`Pyramid` APIs instead. However, you may +be an application developer who doesn't read API documentation because it's +unmanly. Instead you read the raw source code, and because you haven't read +the documentation, you don't know what functions, classes, and methods even +*form* the :app:`Pyramid` API. As a result, you've now written code that +uses internals and you've painted yourself into a conceptual corner as a +result of needing to wrestle with some ZCA-using implementation detail. If +this is you, it's extremely hard to have a lot of sympathy for you. You'll +either need to get familiar with how we're using the ZCA registry or you'll +need to use only the documented APIs; that's why we document them as APIs. If you *extend* or *develop* :app:`Pyramid` (create new ZCML directives, use some of the more obscure "ZCML hooks" as described in :ref:`hooks_chapter`, @@ -458,15 +381,15 @@ In this `TOPP Engineering blog entry <http://www.coactivate.org/projects/topp-engineering/blog/2008/10/20/what-bothers-me-about-the-component-architecture/>`_, Ian Bicking asserts that the way :mod:`repoze.bfg` used a Zope interface to represent an HTTP request method added too much indirection for not enough -gain. We agreed in general, and for this reason, :mod:`repoze.bfg` version 1.1 -(and subsequent versions including :app:`Pyramid` 1.0+) added :term:`view +gain. We agreed in general, and for this reason, :mod:`repoze.bfg` version +1.1 (and subsequent versions including :app:`Pyramid` 1.0+) added :term:`view predicate` and :term:`route predicate` modifiers to view configuration. Predicates are request-specific (or :term:`context` -specific) matching narrowers which don't use interfaces. Instead, each predicate uses a domain-specific string as a match value. -For example, to write a view configuration which matches only requests -with the ``POST`` HTTP request method, you might write a ``@view_config`` +For example, to write a view configuration which matches only requests with +the ``POST`` HTTP request method, you might write a ``@view_config`` decorator which mentioned the ``request_method`` predicate: .. code-block:: python @@ -478,8 +401,7 @@ decorator which mentioned the ``request_method`` predicate: return 'POSTed' You might further narrow the matching scenario by adding an ``accept`` -predicate that narrows matching to something that accepts a JSON -response: +predicate that narrows matching to something that accepts a JSON response: .. code-block:: python :linenos: @@ -490,8 +412,8 @@ response: def post_view(request): return 'POSTed' -Such a view would only match when the request indicated that HTTP -request method was ``POST`` and that the remote user agent passed +Such a view would only match when the request indicated that HTTP request +method was ``POST`` and that the remote user agent passed ``application/json`` (or, for that matter, ``application/*``) in its ``Accept`` request header. @@ -528,11 +450,11 @@ Pyramid "Encourages Use of ZCML" -------------------------------- :term:`ZCML` is a configuration language that can be used to configure the -:term:`Zope Component Architecture` registry that :app:`Pyramid` uses as its +:term:`Zope Component Architecture` registry that :app:`Pyramid` uses for application configuration. Often people claim that Pyramid "needs ZCML". -Quick answer: well, it doesn't. At least not anymore. In :mod:`repoze.bfg` -(the predecessor to Pyramid) versions 1.0 and and 1.1, an application needed to +Quick answer: it doesn't. At least not anymore. In :mod:`repoze.bfg` (the +predecessor to Pyramid) versions 1.0 and and 1.1, an application needed to possess a ZCML file for it to begin executing successfully. However, :mod:`repoze.bfg` 1.2 and greater (including :app:`Pyramid` 1.0) includes a completely imperative mode for all configuration. You will be able to make @@ -556,8 +478,9 @@ everything done completely imperatively. For example, the very most basic app = config.make_wsgi_app() serve(app) -In this mode, no ZCML is required at all. Hopefully this mode will allow -people who are used to doing everything imperatively feel more comfortable. +In this mode, no ZCML is required at all, nor any other sort of frameworky +frontend to application configuration. Hopefully this mode will allow people +who are used to doing everything imperatively feel more comfortable. Pyramid Uses ZCML; ZCML is XML and I Don't Like XML --------------------------------------------------- @@ -565,8 +488,8 @@ Pyramid Uses ZCML; ZCML is XML and I Don't Like XML :term:`ZCML` is a configuration language in the XML syntax. Due to the "imperative configuration" feature (new in :mod:`repoze.bfg` 1.2), you don't need to use ZCML at all. But if you really do want to perform declarative -configuration, perhaps because you want to build an extensible application, you -will need to use and understand it. +configuration, perhaps because you want to build an extensible application, +you may need to use and understand it. :term:`ZCML` contains elements that are mostly singleton tags that are called *declarations*. For an example: @@ -582,24 +505,22 @@ called *declarations*. For an example: This declaration associates a :term:`view` with a route pattern. -All :app:`Pyramid` declarations are singleton tags, unlike many -other XML configuration systems. No XML *values* in ZCML are -meaningful; it's always just XML tags and attributes. So in the very -common case it's not really very much different than an otherwise -"flat" configuration format like ``.ini``, except a developer can -*create* a directive that requires nesting (none of these exist in -:app:`Pyramid` itself), and multiple "sections" can exist with the -same "name" (e.g. two ``<route>`` declarations) must be able to exist -simultaneously. - -You might think some other configuration file format would be better. -But all configuration formats suck in one way or another. I -personally don't think any of our lives would be markedly better if -the declarative configuration format used by :app:`Pyramid` were -YAML, JSON, or INI. It's all just plumbing that you mostly cut and -paste once you've progressed 30 minutes into your first project. -Folks who tend to agitate for another configuration file format are -folks that haven't yet spent that 30 minutes. +All :app:`Pyramid` declarations are singleton tags, unlike many other XML +configuration systems. No XML *values* in ZCML are meaningful; it's always +just XML tags and attributes. So in the very common case it's not really +very much different than an otherwise "flat" configuration format like +``.ini``, except a developer can *create* a directive that requires nesting +(none of these exist in :app:`Pyramid` itself), and multiple "sections" can +exist with the same "name" (e.g. two ``<route>`` declarations) must be able +to exist simultaneously. + +You might think some other configuration file format would be better. But +all configuration formats suck in one way or another. I personally don't +think any of our lives would be markedly better if the declarative +configuration format used by :app:`Pyramid` were YAML, JSON, or INI. It's +all just plumbing that you mostly cut and paste once you've progressed 30 +minutes into your first project. Folks who tend to agitate for another +configuration file format are folks that haven't yet spent that 30 minutes. .. _model_traversal_confusion: @@ -610,7 +531,7 @@ The ``repoze.bfg`` documentation used to refer to the graph being traversed when :term:`traversal` is used as a "model graph". A terminology overlap confused people who wrote applications that always use ORM packages such as SQLAlchemy, which has a different notion of the definition of a "model". As -a sresult, in Pyramid 1.0a7, the tree of objects traversed is now renamed to +a result, in Pyramid 1.0a7, the tree of objects traversed is now renamed to :term:`resource tree` and its components are now named :term:`resource` objects. Associated APIs have been changed. This hopefully alleviates the terminology confusion caused by overriding the term "model". @@ -843,64 +764,59 @@ Pyramid Has Too Many Dependencies --------------------------------- This is true. At the time of this writing, the total number of Python -package distributions that :app:`Pyramid` depends upon transitively -is 18 if you use Python 2.6 or 2.7, or 16 if you use Python 2.4 or -2.5. This is a lot more than zero package distribution dependencies: -a metric which various Python microframeworks and Django boast. - -The :mod:`zope.component` and :mod:`zope.configuration` packages on -which :app:`Pyramid` depends have transitive dependencies on -several other packages (:mod:`zope.schema`, :mod:`zope.i18n`, -:mod:`zope.event`, :mod:`zope.interface`, :mod:`zope.deprecation`, -:mod:`zope.i18nmessageid`). We've been working with the Zope -community to try to collapse and untangle some of these dependencies. -We'd prefer that these packages have fewer packages as transitive -dependencies, and that much of the functionality of these packages was -moved into a smaller *number* of packages. +package distributions that :app:`Pyramid` depends upon transitively is 18 if +you use Python 2.6 or 2.7, or 16 if you use Python 2.4 or 2.5. This is a lot +more than zero package distribution dependencies: a metric which various +Python microframeworks and Django boast. + +The :mod:`zope.component` and :mod:`zope.configuration` packages on which +:app:`Pyramid` depends have transitive dependencies on several other packages +(:mod:`zope.schema`, :mod:`zope.i18n`, :mod:`zope.event`, +:mod:`zope.interface`, :mod:`zope.deprecation`, :mod:`zope.i18nmessageid`). +We've been working with the Zope community to try to collapse and untangle +some of these dependencies. We'd prefer that these packages have fewer +packages as transitive dependencies, and that much of the functionality of +these packages was moved into a smaller *number* of packages. :app:`Pyramid` also has its own direct dependencies, such as :term:`Paste`, :term:`Chameleon`, :term:`Mako` and :term:`WebOb`, and some of these in turn have their own transitive dependencies. -It should be noted that :app:`Pyramid` is positively lithe compared -to :term:`Grok`, a different Zope-based framework. As of this -writing, in its default configuration, Grok has 126 package -distribution dependencies. The number of dependencies required by -:app:`Pyramid` is many times fewer than Grok (or Zope itself, upon -which Grok is based). :app:`Pyramid` has a number of package -distribution dependencies comparable to similarly-targeted frameworks -such as Pylons 1.X. - -We try not to reinvent too many wheels (at least the ones that don't -need reinventing), and this comes at the cost of some number of -dependencies. However, "number of package distributions" is just not -a terribly great metric to measure complexity. For example, the -:mod:`zope.event` distribution on which :app:`Pyramid` depends has -a grand total of four lines of runtime code. As noted above, we're -continually trying to agitate for a collapsing of these sorts of -packages into fewer distribution files. +It should be noted that :app:`Pyramid` is positively lithe compared to +:term:`Grok`, a different Zope-based framework. As of this writing, in its +default configuration, Grok has 126 package distribution dependencies. The +number of dependencies required by :app:`Pyramid` is many times fewer than +Grok (or Zope itself, upon which Grok is based). :app:`Pyramid` has a number +of package distribution dependencies comparable to similarly-targeted +frameworks such as Pylons 1.X. + +We try not to reinvent too many wheels (at least the ones that don't need +reinventing), and this comes at the cost of some number of dependencies. +However, "number of package distributions" is just not a terribly great +metric to measure complexity. For example, the :mod:`zope.event` +distribution on which :app:`Pyramid` depends has a grand total of four lines +of runtime code. As noted above, we're continually trying to agitate for a +collapsing of these sorts of packages into fewer distribution files. Pyramid "Cheats" To Obtain Speed -------------------------------- -Complaints have been lodged by other web framework authors at various -times that :app:`Pyramid` "cheats" to gain performance. One -claimed cheating mechanism is our use (transitively) of the C -extensions provided by :mod:`zope.interface` to do fast lookups. -Another claimed cheating mechanism is the religious avoidance of -extraneous function calls. +Complaints have been lodged by other web framework authors at various times +that :app:`Pyramid` "cheats" to gain performance. One claimed cheating +mechanism is our use (transitively) of the C extensions provided by +:mod:`zope.interface` to do fast lookups. Another claimed cheating mechanism +is the religious avoidance of extraneous function calls. -If there's such a thing as cheating to get better performance, we want -to cheat as much as possible. We optimize :app:`Pyramid` -aggressively. This comes at a cost: the core code has sections that -could be expressed more readably. As an amelioration, we've commented -these sections liberally. +If there's such a thing as cheating to get better performance, we want to +cheat as much as possible. We optimize :app:`Pyramid` aggressively. This +comes at a cost: the core code has sections that could be expressed more +readably. As an amelioration, we've commented these sections liberally. Pyramid Gets Its Terminology Wrong ("MVC") ------------------------------------------ -"I'm a MVC web framework user, and I'm confused. :app:`Pyramid` -calls the controller a view! And it doesn't have any controllers." +"I'm a MVC web framework user, and I'm confused. :app:`Pyramid` calls the +controller a view! And it doesn't have any controllers." If you are in this camp, you might have come to expect things about how your existing "MVC" framework uses its terminology. For example, you probably @@ -910,12 +826,11 @@ these concepts, and each probably *works* almost exactly like your existing "MVC" web framework. We just don't use the "MVC" terminology, as we can't square its usage in the web framework space with historical reality. -People very much want to give web applications the same properties as -common desktop GUI platforms by using similar terminology, and to -provide some frame of reference for how various components in the -common web framework might hang together. But in the opinion of the -author, "MVC" doesn't match the web very well in general. Quoting from -the `Model-View-Controller Wikipedia entry +People very much want to give web applications the same properties as common +desktop GUI platforms by using similar terminology, and to provide some frame +of reference for how various components in the common web framework might +hang together. But in the opinion of the author, "MVC" doesn't match the web +very well in general. Quoting from the `Model-View-Controller Wikipedia entry <http://en.wikipedia.org/wiki/Model–view–controller>`_: .. code-block:: text @@ -946,23 +861,21 @@ the `Model-View-Controller Wikipedia entry The user interface waits for further user interactions, which restarts the cycle. -To the author, it seems as if someone edited this Wikipedia -definition, tortuously couching concepts in the most generic terms -possible in order to account for the use of the term "MVC" by current -web frameworks. I doubt such a broad definition would ever be agreed -to by the original authors of the MVC pattern. But *even so*, it -seems most "MVC" web frameworks fail to meet even this falsely generic -definition. - -For example, do your templates (views) always query models directly as -is claimed in "note that the view gets its own data from the model"? -Probably not. My "controllers" tend to do this, massaging the data for -easier use by the "view" (template). What do you do when your -"controller" returns JSON? Do your controllers use a template to -generate JSON? If not, what's the "view" then? Most MVC-style GUI web -frameworks have some sort of event system hooked up that lets the view -detect when the model changes. The web just has no such facility in -its current form: it's effectively pull-only. +To the author, it seems as if someone edited this Wikipedia definition, +tortuously couching concepts in the most generic terms possible in order to +account for the use of the term "MVC" by current web frameworks. I doubt +such a broad definition would ever be agreed to by the original authors of +the MVC pattern. But *even so*, it seems most "MVC" web frameworks fail to +meet even this falsely generic definition. + +For example, do your templates (views) always query models directly as is +claimed in "note that the view gets its own data from the model"? Probably +not. My "controllers" tend to do this, massaging the data for easier use by +the "view" (template). What do you do when your "controller" returns JSON? Do +your controllers use a template to generate JSON? If not, what's the "view" +then? Most MVC-style GUI web frameworks have some sort of event system +hooked up that lets the view detect when the model changes. The web just has +no such facility in its current form: it's effectively pull-only. So, in the interest of not mistaking desire with reality, and instead of trying to jam the square peg that is the web into the round hole of "MVC", we @@ -980,11 +893,11 @@ the current constraints of the web. Pyramid Applications are Extensible; I Don't Believe In Application Extensibility --------------------------------------------------------------------------------- -Any :app:`Pyramid` application written obeying certain constraints -is *extensible*. This feature is discussed in the :app:`Pyramid` -documentation chapter named :ref:`extending_chapter`. It is made -possible by the use of the :term:`Zope Component Architecture` and -:term:`ZCML` within :app:`Pyramid`. +Any :app:`Pyramid` application written obeying certain constraints is +*extensible*. This feature is discussed in the :app:`Pyramid` documentation +chapters named :ref:`extending_chapter` and :ref:`advconf_narr`. It is made +possible by the use of the :term:`Zope Component Architecture` and within +:app:`Pyramid`. "Extensible", in this context, means: @@ -1002,108 +915,99 @@ possible by the use of the :term:`Zope Component Architecture` and ZCA, the original developer does not need to think terribly hard about the mechanics of introducing such a plugpoint. -Many developers seem to believe that creating extensible applications -is "not worth it". They instead suggest that modifying the source of -a given application for each deployment to override behavior is more -reasonable. Much discussion about version control branching and -merging typically ensues. - -It's clear that making every application extensible isn't required. -The majority of web applications only have a single deployment, and -thus needn't be extensible at all. However, some web applications -have multiple deployments, and some have *many* deployments. For -example, a generic "content management" system (CMS) may have basic -functionality that needs to be extended for a particular deployment. -That CMS system may be deployed for many organizations at many places. -Some number of deployments of this CMS may be deployed centrally by a -third party and managed as a group. It's useful to be able to extend -such a system for each deployment via preordained plugpoints than it -is to continually keep each software branch of the system in sync with -some upstream source: the upstream developers may change code in such -a way that your changes to the same codebase conflict with theirs in -fiddly, trivial ways. Merging such changes repeatedly over the -lifetime of a deployment can be difficult and time consuming, and it's -often useful to be able to modify an application for a particular -deployment in a less invasive way. - -If you don't want to think about :app:`Pyramid` application -extensibility at all, you needn't. You can ignore extensibility -entirely. However, if you follow the set of rules defined in -:ref:`extending_chapter`, you don't need to *make* your application -extensible: any application you write in the framework just *is* -automatically extensible at a basic level. The mechanisms that -deployers use to extend it will be necessarily coarse: typically, -views, routes, and resources will be capable of being overridden, -usually via :term:`ZCML`. But for most minor (and even some major) -customizations, these are often the only override plugpoints -necessary: if the application doesn't do exactly what the deployment -requires, it's often possible for a deployer to override a view, -route, or resource and quickly make it do what he or she wants it to -do in ways *not necessarily anticipated by the original developer*. -Here are some example scenarios demonstrating the benefits of such a -feature. - -- If a deployment needs a different styling, the deployer may override - the main template and the CSS in a separate Python package which - defines overrides. - -- If a deployment needs an application page to do something - differently needs it to expose more or different information, the - deployer may override the view that renders the page within a - separate Python package. - -- If a deployment needs an additional feature, the deployer may add a - view to the override package. - -As long as the fundamental design of the upstream package doesn't -change, these types of modifications often survive across many -releases of the upstream package without needing to be revisited. - -Extending an application externally is not a panacea, and carries a -set of risks similar to branching and merging: sometimes major changes -upstream will cause you to need to revisit and update some of your -modifications. But you won't regularly need to deal wth meaningless -textual merge conflicts that trivial changes to upstream packages -often entail when it comes time to update the upstream package, -because if you extend an application externally, there just is no -textual merge done. Your modifications will also, for whatever its -worth, be contained in one, canonical, well-defined place. - -Branching an application and continually merging in order to get new -features and bugfixes is clearly useful. You can do that with a -:app:`Pyramid` application just as usefully as you can do it with -any application. But deployment of an application written in -:app:`Pyramid` makes it possible to avoid the need for this even if -the application doesn't define any plugpoints ahead of time. It's -possible that promoters of competing web frameworks dismiss this -feature in favor of branching and merging because applications written -in their framework of choice aren't extensible out of the box in a +Many developers seem to believe that creating extensible applications is "not +worth it". They instead suggest that modifying the source of a given +application for each deployment to override behavior is more reasonable. +Much discussion about version control branching and merging typically ensues. + +It's clear that making every application extensible isn't required. The +majority of web applications only have a single deployment, and thus needn't +be extensible at all. However, some web applications have multiple +deployments, and some have *many* deployments. For example, a generic +"content management" system (CMS) may have basic functionality that needs to +be extended for a particular deployment. That CMS system may be deployed for +many organizations at many places. Some number of deployments of this CMS +may be deployed centrally by a third party and managed as a group. It's +useful to be able to extend such a system for each deployment via preordained +plugpoints than it is to continually keep each software branch of the system +in sync with some upstream source: the upstream developers may change code in +such a way that your changes to the same codebase conflict with theirs in +fiddly, trivial ways. Merging such changes repeatedly over the lifetime of a +deployment can be difficult and time consuming, and it's often useful to be +able to modify an application for a particular deployment in a less invasive +way. + +If you don't want to think about :app:`Pyramid` application extensibility at +all, you needn't. You can ignore extensibility entirely. However, if you +follow the set of rules defined in :ref:`extending_chapter`, you don't need +to *make* your application extensible: any application you write in the +framework just *is* automatically extensible at a basic level. The +mechanisms that deployers use to extend it will be necessarily coarse: +typically, views, routes, and resources will be capable of being +overridden. But for most minor (and even some major) customizations, these +are often the only override plugpoints necessary: if the application doesn't +do exactly what the deployment requires, it's often possible for a deployer +to override a view, route, or resource and quickly make it do what he or she +wants it to do in ways *not necessarily anticipated by the original +developer*. Here are some example scenarios demonstrating the benefits of +such a feature. + +- If a deployment needs a different styling, the deployer may override the + main template and the CSS in a separate Python package which defines + overrides. + +- If a deployment needs an application page to do something differently needs + it to expose more or different information, the deployer may override the + view that renders the page within a separate Python package. + +- If a deployment needs an additional feature, the deployer may add a view to + the override package. + +As long as the fundamental design of the upstream package doesn't change, +these types of modifications often survive across many releases of the +upstream package without needing to be revisited. + +Extending an application externally is not a panacea, and carries a set of +risks similar to branching and merging: sometimes major changes upstream will +cause you to need to revisit and update some of your modifications. But you +won't regularly need to deal wth meaningless textual merge conflicts that +trivial changes to upstream packages often entail when it comes time to +update the upstream package, because if you extend an application externally, +there just is no textual merge done. Your modifications will also, for +whatever its worth, be contained in one, canonical, well-defined place. + +Branching an application and continually merging in order to get new features +and bugfixes is clearly useful. You can do that with a :app:`Pyramid` +application just as usefully as you can do it with any application. But +deployment of an application written in :app:`Pyramid` makes it possible to +avoid the need for this even if the application doesn't define any plugpoints +ahead of time. It's possible that promoters of competing web frameworks +dismiss this feature in favor of branching and merging because applications +written in their framework of choice aren't extensible out of the box in a comparably fundamental way. -While :app:`Pyramid` application are fundamentally extensible even -if you don't write them with specific extensibility in mind, if you're -moderately adventurous, you can also take it a step further. If you -learn more about the :term:`Zope Component Architecture`, you can -optionally use it to expose other more domain-specific configuration -plugpoints while developing an application. The plugpoints you expose -needn't be as coarse as the ones provided automatically by -:app:`Pyramid` itself. For example, you might compose your own -:term:`ZCML` directive that configures a set of views for a prebaked -purpose (e.g. ``restview`` or somesuch) , allowing other people to -refer to that directive when they make declarations in the -``configure.zcml`` of their customization package. There is a cost -for this: the developer of an application that defines custom -plugpoints for its deployers will need to understand the ZCA or he -will need to develop his own similar extensibility system. - -Ultimately, any argument about whether the extensibility features lent -to applications by :app:`Pyramid` are "good" or "bad" is somewhat -pointless. You needn't take advantage of the extensibility features -provided by a particular :app:`Pyramid` application in order to -affect a modification for a particular set of its deployments. You -can ignore the application's extensibility plugpoints entirely, and -instead use version control branching and merging to manage -application deployment modifications instead, as if you were deploying +While :app:`Pyramid` application are fundamentally extensible even if you +don't write them with specific extensibility in mind, if you're moderately +adventurous, you can also take it a step further. If you learn more about +the :term:`Zope Component Architecture`, you can optionally use it to expose +other more domain-specific configuration plugpoints while developing an +application. The plugpoints you expose needn't be as coarse as the ones +provided automatically by :app:`Pyramid` itself. For example, you might +compose your own :term:`ZCML` directive that configures a set of views for a +prebaked purpose (e.g. ``restview`` or somesuch) , allowing other people to +refer to that directive when they make declarations in the ``configure.zcml`` +of their customization package. There is a cost for this: the developer of +an application that defines custom plugpoints for its deployers will need to +understand the ZCA or he will need to develop his own similar extensibility +system. + +Ultimately, any argument about whether the extensibility features lent to +applications by :app:`Pyramid` are "good" or "bad" is mostly pointless. You +needn't take advantage of the extensibility features provided by a particular +:app:`Pyramid` application in order to affect a modification for a particular +set of its deployments. You can ignore the application's extensibility +plugpoints entirely, and instead use version control branching and merging to +manage application deployment modifications instead, as if you were deploying an application written using any other web framework. Zope 3 Enforces "TTW" Authorization Checks By Default; Pyramid Does Not @@ -1112,11 +1016,11 @@ Zope 3 Enforces "TTW" Authorization Checks By Default; Pyramid Does Not Challenge +++++++++ -:app:`Pyramid` performs automatic authorization checks only at -:term:`view` execution time. Zope 3 wraps context objects with a -`security proxy <http://wiki.zope.org/zope3/WhatAreSecurityProxies>`, -which causes Zope 3 to do also security checks during attribute -access. I like this, because it means: +:app:`Pyramid` performs automatic authorization checks only at :term:`view` +execution time. Zope 3 wraps context objects with a `security proxy +<http://wiki.zope.org/zope3/WhatAreSecurityProxies>`, which causes Zope 3 to +do also security checks during attribute access. I like this, because it +means: #) When I use the security proxy machinery, I can have a view that conditionally displays certain HTML elements (like form fields) or @@ -1132,75 +1036,69 @@ access. I like this, because it means: Defense +++++++ -:app:`Pyramid` was developed by folks familiar with Zope 2, which -has a "through the web" security model. This "TTW" security model was -the precursor to Zope 3's security proxies. Over time, as the -:app:`Pyramid` developers (working in Zope 2) created such sites, -we found authorization checks during code interpretation extremely -useful in a minority of projects. But much of the time, TTW -authorization checks usually slowed down the development velocity of -projects that had no delegation requirements. In particular, if we -weren't allowing "untrusted" users to write arbitrary Python code to -be executed by our application, the burden of "through the web" -security checks proved too costly to justify. We (collectively) -haven't written an application on top of which untrusted developers -are allowed to write code in many years, so it seemed to make sense to -drop this model by default in a new web framework. - -And since we tend to use the same toolkit for all web applications, it's -just never been a concern to be able to use the same set of -restricted-execution code under two web different frameworks. - -Justifications for disabling security proxies by default -notwithstanding, given that Zope 3 security proxies are "viral" by -nature, the only requirement to use one is to make sure you wrap a -single object in a security proxy and make sure to access that object -normally when you want proxy security checks to happen. It is -possible to override the :app:`Pyramid` "traverser" for a given -application (see :ref:`changing_the_traverser`). To get Zope3-like -behavior, it is possible to plug in a different traverser which -returns Zope3-security-proxy-wrapped objects for each traversed object -(including the :term:`context` and the :term:`root`). This would have -the effect of creating a more Zope3-like environment without much -effort. +:app:`Pyramid` was developed by folks familiar with Zope 2, which has a +"through the web" security model. This "TTW" security model was the +precursor to Zope 3's security proxies. Over time, as the :app:`Pyramid` +developers (working in Zope 2) created such sites, we found authorization +checks during code interpretation extremely useful in a minority of projects. +But much of the time, TTW authorization checks usually slowed down the +development velocity of projects that had no delegation requirements. In +particular, if we weren't allowing "untrusted" users to write arbitrary +Python code to be executed by our application, the burden of "through the +web" security checks proved too costly to justify. We (collectively) haven't +written an application on top of which untrusted developers are allowed to +write code in many years, so it seemed to make sense to drop this model by +default in a new web framework. + +And since we tend to use the same toolkit for all web applications, it's just +never been a concern to be able to use the same set of restricted-execution +code under two web different frameworks. + +Justifications for disabling security proxies by default notwithstanding, +given that Zope 3 security proxies are "viral" by nature, the only +requirement to use one is to make sure you wrap a single object in a security +proxy and make sure to access that object normally when you want proxy +security checks to happen. It is possible to override the :app:`Pyramid` +"traverser" for a given application (see :ref:`changing_the_traverser`). To +get Zope3-like behavior, it is possible to plug in a different traverser +which returns Zope3-security-proxy-wrapped objects for each traversed object +(including the :term:`context` and the :term:`root`). This would have the +effect of creating a more Zope3-like environment without much effort. .. _microframeworks_smaller_hello_world: Microframeworks Have Smaller Hello World Programs ------------------------------------------------- -Self-described "microframeworks" exist: `Bottle -<http://bottle.paws.de>`_ and `Flask <http://flask.pocoo.org/>`_ are -two that are becoming popular. `Bobo <http://bobo.digicool.com/>`_ -doesn't describe itself as a microframework, but its intended userbase -is much the same. Many others exist. We've actually even (only as a -teaching tool, not as any sort of official project) `created one using -BFG <http://bfg.repoze.org/videos#groundhog1>`_ (the precursor to -Pyramid). Microframeworks are small frameworks with one common -feature: each allows its users to create a fully functional -application that lives in a single Python file. - -Some developers and microframework authors point out that Pyramid's -"hello world" single-file program is longer (by about five lines) than -the equivalent program in their favorite microframework. Guilty as -charged; in a contest of "whose is shortest", Pyramid indeed loses. - -This loss isn't for lack of trying. Pyramid aims to be useful in the -same circumstance in which microframeworks claim dominance: -single-file applications. But Pyramid doesn't sacrifice its ability -to credibly support larger applications in order to achieve -hello-world LoC parity with the current crop of microframeworks. -Pyramid's design instead tries to avoid some common pitfalls -associated with naive declarative configuration schemes. The -subsections which follow explain the rationale. +Self-described "microframeworks" exist: `Bottle <http://bottle.paws.de>`_ and +`Flask <http://flask.pocoo.org/>`_ are two that are becoming popular. `Bobo +<http://bobo.digicool.com/>`_ doesn't describe itself as a microframework, +but its intended userbase is much the same. Many others exist. We've +actually even (only as a teaching tool, not as any sort of official project) +`created one using BFG <http://bfg.repoze.org/videos#groundhog1>`_ (the +precursor to Pyramid). Microframeworks are small frameworks with one common +feature: each allows its users to create a fully functional application that +lives in a single Python file. + +Some developers and microframework authors point out that Pyramid's "hello +world" single-file program is longer (by about five lines) than the +equivalent program in their favorite microframework. Guilty as charged; in a +contest of "whose is shortest", Pyramid indeed loses. + +This loss isn't for lack of trying. Pyramid aims to be useful in the same +circumstance in which microframeworks claim dominance: single-file +applications. But Pyramid doesn't sacrifice its ability to credibly support +larger applications in order to achieve hello-world LoC parity with the +current crop of microframeworks. Pyramid's design instead tries to avoid +some common pitfalls associated with naive declarative configuration schemes. +The subsections which follow explain the rationale. .. _you_dont_own_modulescope: Application Programmers Don't Control The Module-Scope Codepath (Import-Time Side-Effects Are Evil) +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -Please imagine a directory structure with a set of Python files in -it: +Please imagine a directory structure with a set of Python files in it: .. code-block:: text @@ -1248,13 +1146,13 @@ The contents of ``config.py``: L.append(func) return func -If we cd to the directory that holds these files and we run ``python -app.py`` given the directory structure and code above, what happens? -Presuably, our ``decorator`` decorator will be used twice, once by the -decorated function ``foo`` in ``app.py`` and once by the decorated -function ``bar`` in ``app2.py``. Since each time the decorator is -used, the list ``L`` in ``config.py`` is appended to, we'd expect a -list with two elements to be printed, right? Sadly, no: +If we cd to the directory that holds these files and we run ``python app.py`` +given the directory structure and code above, what happens? Presumably, our +``decorator`` decorator will be used twice, once by the decorated function +``foo`` in ``app.py`` and once by the decorated function ``bar`` in +``app2.py``. Since each time the decorator is used, the list ``L`` in +``config.py`` is appended to, we'd expect a list with two elements to be +printed, right? Sadly, no: .. code-block:: text @@ -1263,27 +1161,26 @@ list with two elements to be printed, right? Sadly, no: <function foo at 0x7f4ea41ab230>, <function bar at 0x7f4ea41ab2a8>] -By visual inspection, that outcome (three different functions in the -list) seems impossible. We only defined two functions and we -decorated each of those functions only once, so we believe that the -``decorator`` decorator will only run twice. However, what we believe -is wrong because the code at module scope in our ``app.py`` module was -*executed twice*. The code is executed once when the script is run as -``__main__`` (via ``python app.py``), and then it is executed again -when ``app2.py`` imports the same file as ``app``. - -What does this have to do with our comparison to microframeworks? -Many microframeworks in the current crop (e.g. Bottle, Flask) -encourage you to attach configuration decorators to objects defined at -module scope. These decorators execute arbitrarily complex -registration code which populates a singleton registry that is a -global defined in external Python module. This is analogous to the -above example: the "global registry" in the above example is the list -``L``. - -Let's see what happens when we use the same pattern with the -`Groundhog <http://bfg.repoze.org/videos#groundhog1>`_ microframework. -Replace the contents of ``app.py`` above with this: +By visual inspection, that outcome (three different functions in the list) +seems impossible. We only defined two functions and we decorated each of +those functions only once, so we believe that the ``decorator`` decorator +will only run twice. However, what we believe is wrong because the code at +module scope in our ``app.py`` module was *executed twice*. The code is +executed once when the script is run as ``__main__`` (via ``python app.py``), +and then it is executed again when ``app2.py`` imports the same file as +``app``. + +What does this have to do with our comparison to microframeworks? Many +microframeworks in the current crop (e.g. Bottle, Flask) encourage you to +attach configuration decorators to objects defined at module scope. These +decorators execute arbitrarily complex registration code which populates a +singleton registry that is a global defined in external Python module. This +is analogous to the above example: the "global registry" in the above example +is the list ``L``. + +Let's see what happens when we use the same pattern with the `Groundhog +<http://bfg.repoze.org/videos#groundhog1>`_ microframework. Replace the +contents of ``app.py`` above with this: .. code-block:: python :linenos: @@ -1317,91 +1214,87 @@ Replace the contents of ``config.py`` above with this: from groundhog import Groundhog gh = Groundhog('myapp', 'seekrit') -How many routes will be registered within the routing table of the -"gh" Groundhog application? If you answered three, you are correct. -How many would a casual reader (and any sane developer) expect to be -registered? If you answered two, you are correct. Will the double -registration be a problem? With our fictional Groundhog framework's -``route`` method backing this application, not really. It will slow -the application down a little bit, because it will need to miss twice -for a route when it does not match. Will it be a problem with another -framework, another application, or another decorator? Who knows. You -need to understand the application in its totality, the framework in -its totality, and the chronology of execution to be able to predict -what the impact of unintentional code double-execution will be. - -The encouragement to use decorators which perform population of an -external registry has an unintended consequence: the application -developer now must assert ownership of every codepath that executes -Python module scope code. This code is presumed by the current crop of -decorator-based microframeworks to execute once and only once; if it -executes more than once, weird things will start to happen. It is up -to the application developer to maintain this invariant. -Unfortunately, however, in reality, this is an impossible task, -because, Python programmers *do not own the module scope codepath, and -never will*. Microframework programmers therefore will at some point -then need to start reading the tea leaves about what *might* happen if -module scope code gets executed more than once like we do in the -previous paragraph. This is a really pretty poor situation to find -yourself in as an application developer: you probably didn't even know -you signed up for the job, because the documentation offered by -decorator-based microframeworks don't warn you about it. - -Python application programmers do not control the module scope codepath. -Anyone who tries to sell you on the idea that they do is simply mistaken. -Test runners that you may want to use to run your code's tests often perform -imports of arbitrary code in strange orders that manifest bugs like the one -demonstrated above. API documentation generation tools do the same. Some -(mutant) people even think it's safe to use the Python ``reload`` command or -delete objects from ``sys.modules``, each of which has hilarious effects when -used against code that has import-time side effects. When Python programmers -assume they can use the module-scope codepath to run arbitrary code -(especially code which populates an external registry), and this assumption -is challenged by reality, the application developer is often required to -undergo a painful, meticulous debugging process to find the root cause of an -inevitably obscure symptom. The solution is often to rearrange application -import ordering or move an import statement from module-scope into a function -body. The rationale for doing so can never be expressed adequnately in the -checkin message which accompanies the fix or documented succinctly enough for -the benefit of the rest of the development team so that the problem never -happens again. It will happen again next month too, especially if you are -working on a project with other people who haven't yet internalized the -lessons you learned while you stepped through module-scope code using -``pdb``. - -Folks who have a large investment in eager decorator-based -configuration that populates an external data structure (such as -microframework authors) may argue that the set of circumstances I -outlined above is anomalous and contrived. They will argue that it -just will never happen. If you never intend your application to grow -beyond one or two or three modules, that's probably true. However, as -your codebase grows, and becomes spread across a greater number of -modules, the circumstances in which module-scope code will be executed -multiple times will become more and more likely to occur and less and -less predictable. It's not responsible to claim that double-execution -of module-scope code will never happen. It will; it's just a matter -of luck, time, and application complexity. - -If microframework authors do admit that the circumstance isn't -contrived, they might then argue that "real" damage will never happen -as the result of the double-execution (or triple-execution, etc) of -module scope code. You would be wise to disbelieve this assertion. -The potential outcomes of multiple execution are too numerous to -predict because they involve delicate relationships between -application and framework code as well as chronology of code -execution. It's literally impossible for a framework author to know -what will happen in all circumstances ("X is executed, then Y, then X -again.. a train leaves Chicago at 50 mph... "). And even if given the -gift of omniscience for some limited set of circumstances, the -framework author almost certainly does not have the double-execution -anomaly in mind when coding new features. He's thinking of adding a -feature, not protecting against problems that might be caused by the -1% multiple execution case. However, any 1% case may cause 50% of -your pain on a project, so it'd be nice if it never occured. +How many routes will be registered within the routing table of the "gh" +Groundhog application? If you answered three, you are correct. How many +would a casual reader (and any sane developer) expect to be registered? If +you answered two, you are correct. Will the double registration be a +problem? With our fictional Groundhog framework's ``route`` method backing +this application, not really. It will slow the application down a little +bit, because it will need to miss twice for a route when it does not match. +Will it be a problem with another framework, another application, or another +decorator? Who knows. You need to understand the application in its +totality, the framework in its totality, and the chronology of execution to +be able to predict what the impact of unintentional code double-execution +will be. + +The encouragement to use decorators which perform population of an external +registry has an unintended consequence: the application developer now must +assert ownership of every codepath that executes Python module scope +code. Module-scope code is presumed by the current crop of decorator-based +microframeworks to execute once and only once; if it executes more than once, +weird things will start to happen. It is up to the application developer to +maintain this invariant. Unfortunately, however, in reality, this is an +impossible task, because, Python programmers *do not own the module scope +codepath, and never will*. Anyone who tries to sell you on the idea that +they do is simply mistaken. Test runners that you may want to use to run +your code's tests often perform imports of arbitrary code in strange orders +that manifest bugs like the one demonstrated above. API documentation +generation tools do the same. Some (mutant) people even think it's safe to +use the Python ``reload`` command or delete objects from ``sys.modules``, +each of which has hilarious effects when used against code that has +import-time side effects. + +Global-registry-mutating microframework programmers therefore will at some +point need to start reading the tea leaves about what *might* happen if +module scope code gets executed more than once like we do in the previous +paragraph. When Python programmers assume they can use the module-scope +codepath to run arbitrary code (especially code which populates an external +registry), and this assumption is challenged by reality, the application +developer is often required to undergo a painful, meticulous debugging +process to find the root cause of an inevitably obscure symptom. The +solution is often to rearrange application import ordering or move an import +statement from module-scope into a function body. The rationale for doing so +can never be expressed adequately in the checkin message which accompanies +the fix and can't be documented succinctly enough for the benefit of the rest +of the development team so that the problem never happens again. It will +happen again, especially if you are working on a project with other people +who haven't yet internalized the lessons you learned while you stepped +through module-scope code using ``pdb``. This is a really pretty poor +situation to find yourself in as an application developer: you probably +didn't even know your or your team signed up for the job, because the +documentation offered by decorator-based microframeworks don't warn you about +it. + +Folks who have a large investment in eager decorator-based configuration that +populates an external data structure (such as microframework authors) may +argue that the set of circumstances I outlined above is anomalous and +contrived. They will argue that it just will never happen. If you never +intend your application to grow beyond one or two or three modules, that's +probably true. However, as your codebase grows, and becomes spread across a +greater number of modules, the circumstances in which module-scope code will +be executed multiple times will become more and more likely to occur and less +and less predictable. It's not responsible to claim that double-execution of +module-scope code will never happen. It will; it's just a matter of luck, +time, and application complexity. + +If microframework authors do admit that the circumstance isn't contrived, +they might then argue that "real" damage will never happen as the result of +the double-execution (or triple-execution, etc) of module scope code. You +would be wise to disbelieve this assertion. The potential outcomes of +multiple execution are too numerous to predict because they involve delicate +relationships between application and framework code as well as chronology of +code execution. It's literally impossible for a framework author to know +what will happen in all circumstances. But even if given the gift of +omniscience for some limited set of circumstances, the framework author +almost certainly does not have the double-execution anomaly in mind when +coding new features. He's thinking of adding a feature, not protecting +against problems that might be caused by the 1% multiple execution case. +However, any 1% case may cause 50% of your pain on a project, so it'd be nice +if it never occured. Responsible microframeworks actually offer a back-door way around the -problem. They allow you to disuse decorator based configuration -entirely. Instead of requiring you to do the following: +problem. They allow you to disuse decorator based configuration entirely. +Instead of requiring you to do the following: .. code-block:: python :linenos: @@ -1415,8 +1308,7 @@ entirely. Instead of requiring you to do the following: if __name__ == '__main__': gh.run() -They allow you to disuse the decorator syntax and go -almost-all-imperative: +They allow you to disuse the decorator syntax and go almost-all-imperative: .. code-block:: python :linenos: @@ -1431,28 +1323,27 @@ almost-all-imperative: gh.run() This is a generic mode of operation that is encouraged in the Pyramid -documentation. Some existing microframeworks (Flask, in particular) -allow for it as well. None (other than Pyramid) *encourage* it. If -you never expect your application to grow beyond two or three or four -or ten modules, it probably doesn't matter very much which mode you -use. If your application grows large, however, imperative -configuration can provide better predictability. +documentation. Some existing microframeworks (Flask, in particular) allow for +it as well. None (other than Pyramid) *encourage* it. If you never expect +your application to grow beyond two or three or four or ten modules, it +probably doesn't matter very much which mode you use. If your application +grows large, however, imperative configuration can provide better +predictability. .. note:: - Astute readers may notice that Pyramid has configuration decorators - too. Aha! Don't these decorators have the same problems? No. - These decorators do not populate an external Python module when they - are executed. They only mutate the functions (and classes and - methods) they're attached to. These mutations must later be found - during a "scan" process that has a predictable and structured import - phase. Module-localized mutation is actually the best-case - circumstance for double-imports; if a module only mutates itself and - its contents at import time, if it is imported twice, that's OK, - because each decorator invocation will always be mutating an - independent copy of the object its attached to, not a shared - resource like a registry in another module. This has the effect - that double-registrations will never be performed. + Astute readers may notice that Pyramid has configuration decorators too. + Aha! Don't these decorators have the same problems? No. These decorators + do not populate an external Python module when they are executed. They + only mutate the functions (and classes and methods) they're attached to. + These mutations must later be found during a "scan" process that has a + predictable and structured import phase. Module-localized mutation is + actually the best-case circumstance for double-imports; if a module only + mutates itself and its contents at import time, if it is imported twice, + that's OK, because each decorator invocation will always be mutating an + independent copy of the object its attached to, not a shared resource like + a registry in another module. This has the effect that + double-registrations will never be performed. Routes (Usually) Need Relative Ordering +++++++++++++++++++++++++++++++++++++++ @@ -1482,8 +1373,8 @@ Consider the following simple `Groundhog app.run() If you run this application and visit the URL ``/admin``, you will see -"admin" page. This is the intended result. However, what if you -rearrange the order of the function definitions in the file? +"admin" page. This is the intended result. However, what if you rearrange +the order of the function definitions in the file? .. code-block:: python :linenos: @@ -1506,54 +1397,50 @@ rearrange the order of the function definitions in the file? if __name__ == '__main__': app.run() -If you run this application and visit the URL ``/admin``, you will now -be returned a 404 error. This is probably not what you intended. The -reason you see a 404 error when you rearrange function definition -ordering is that routing declarations expressed via our -microframework's routing decorators have an *ordering*, and that -ordering matters. - -In the first case, where we achieved the expected result, we first -added a route with the pattern ``/admin``, then we added a route with -the pattern ``/:action`` by virtue of adding routing patterns via -decorators at module scope. When a request with a ``PATH_INFO`` of -``/admin`` enters our application, the web framework loops over each -of our application's route patterns in the order in which they were -defined in our module. As a result, the view associated with the -``/admin`` routing pattern will be invoked: it matches first. All is -right with the world. - -In the second case, where we did not achieve the expected result, we -first added a route with the pattern ``/:action``, then we added a -route with the pattern ``/admin``. When a request with a -``PATH_INFO`` of ``/admin`` enters our application, the web framework -loops over each of our application's route patterns in the order in -which they were defined in our module. As a result, the view -associated with the ``/:action`` routing pattern will be invoked: it -matches first. A 404 error is raised. This is not what we wanted; it -just happened due to the order in which we defined our view functions. - -You may be willing to maintain an ordering of your view functions -which reifies your routing policy. Your application may be small -enough where this will never cause an issue. If it becomes large -enough to matter, however, I don't envy you. Maintaining that -ordering as your application grows larger will be difficult. At some -point, you will also need to start controlling *import* ordering as -well as function definition ordering. When your application grows -beyond the size of a single file, and when decorators are used to -register views, the non-``__main__`` modules which contain -configuration decorators must be imported somehow for their -configuration to be executed. - -Does that make you a little -uncomfortable? It should, because :ref:`you_dont_own_modulescope`. +If you run this application and visit the URL ``/admin``, you will now be +returned a 404 error. This is probably not what you intended. The reason +you see a 404 error when you rearrange function definition ordering is that +routing declarations expressed via our microframework's routing decorators +have an *ordering*, and that ordering matters. + +In the first case, where we achieved the expected result, we first added a +route with the pattern ``/admin``, then we added a route with the pattern +``/:action`` by virtue of adding routing patterns via decorators at module +scope. When a request with a ``PATH_INFO`` of ``/admin`` enters our +application, the web framework loops over each of our application's route +patterns in the order in which they were defined in our module. As a result, +the view associated with the ``/admin`` routing pattern will be invoked: it +matches first. All is right with the world. + +In the second case, where we did not achieve the expected result, we first +added a route with the pattern ``/:action``, then we added a route with the +pattern ``/admin``. When a request with a ``PATH_INFO`` of ``/admin`` enters +our application, the web framework loops over each of our application's route +patterns in the order in which they were defined in our module. As a result, +the view associated with the ``/:action`` routing pattern will be invoked: it +matches first. A 404 error is raised. This is not what we wanted; it just +happened due to the order in which we defined our view functions. + +You may be willing to maintain an ordering of your view functions which +reifies your routing policy. Your application may be small enough where this +will never cause an issue. If it becomes large enough to matter, however, I +don't envy you. Maintaining that ordering as your application grows larger +will be difficult. At some point, you will also need to start controlling +*import* ordering as well as function definition ordering. When your +application grows beyond the size of a single file, and when decorators are +used to register views, the non-``__main__`` modules which contain +configuration decorators must be imported somehow for their configuration to +be executed. + +Does that make you a little uncomfortable? It should, because +:ref:`you_dont_own_modulescope`. "Stacked Object Proxies" Are Too Clever / Thread Locals Are A Nuisance ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -In another manifestation of "import fascination", some microframeworks -use the ``import`` statement to get a handle to an object which *is -not logically global*: +In another manifestation of "import fascination", some microframeworks use +the ``import`` statement to get a handle to an object which *is not logically +global*: .. code-block:: python :linenos: @@ -1573,17 +1460,16 @@ not logically global*: # credentials were invalid The `Pylons 1.X <http://pylonshq.com>`_ web framework uses a similar -strategy. It calls these things "Stacked Object Proxies", so, for -purposes of this discussion, I'll do so as well. - -Import statements in Python (``import foo``, ``from bar import baz``) -are most frequently performed to obtain a reference to an object -defined globally within an external Python module. However, in -"normal" programs, they are never used to obtain a reference to an -object that has a lifetime measured by the scope of the body of a -function. It would be absurd to try to import, for example, a -variable named ``i`` representing a loop counter defined in the body -of a function. For example, we'd never try to import ``i`` from the +strategy. It calls these things "Stacked Object Proxies", so, for purposes +of this discussion, I'll do so as well. + +Import statements in Python (``import foo``, ``from bar import baz``) are +most frequently performed to obtain a reference to an object defined globally +within an external Python module. However, in "normal" programs, they are +never used to obtain a reference to an object that has a lifetime measured by +the scope of the body of a function. It would be absurd to try to import, +for example, a variable named ``i`` representing a loop counter defined in +the body of a function. For example, we'd never try to import ``i`` from the code below: .. code-block:: python @@ -1593,80 +1479,74 @@ code below: for i in range(10): print i -By its nature, the *request* object created as the result of a WSGI -server's call into a long-lived web framework cannot be global, -because the lifetime of a single request will be much shorter than the -lifetime of the process running the framework. A request object -created by a web framework actually has more similarity to the ``i`` -loop counter in our example above than it has to any comparable -importable object defined in the Python standard library or in -"normal" library code. - -However, systems which use stacked object proxies promote locally -scoped objects such as ``request`` out to module scope, for the -purpose of being able to offer users a "nice" spelling involving -``import``. They, for what I consider dubious reasons, would rather -present to their users the canonical way of getting at a ``request`` -as ``from framework import request`` instead of a saner ``from -myframework.threadlocals import get_request; request = get_request()`` -even though the latter is more explicit. - -It would be *most* explicit if the microframeworks did not use thread -local variables at all. Pyramid view functions are passed a request -object; many of Pyramid's APIs require that an explicit request object -be passed to them. It is *possible* to retrieve the current Pyramid -request as a threadlocal variable but it is a "in case of emergency, -break glass" type of activity. This explicitness makes Pyramid view -functions more easily unit testable, as you don't need to rely on the -framework to manufacture suitable "dummy" request (and other -similarly-scoped) objects during test setup. It also makes them more -likely to work on arbitrary systems, such as async servers that do no -monkeypatching. +By its nature, the *request* object created as the result of a WSGI server's +call into a long-lived web framework cannot be global, because the lifetime +of a single request will be much shorter than the lifetime of the process +running the framework. A request object created by a web framework actually +has more similarity to the ``i`` loop counter in our example above than it +has to any comparable importable object defined in the Python standard +library or in "normal" library code. + +However, systems which use stacked object proxies promote locally scoped +objects such as ``request`` out to module scope, for the purpose of being +able to offer users a "nice" spelling involving ``import``. They, for what I +consider dubious reasons, would rather present to their users the canonical +way of getting at a ``request`` as ``from framework import request`` instead +of a saner ``from myframework.threadlocals import get_request; request = +get_request()`` even though the latter is more explicit. + +It would be *most* explicit if the microframeworks did not use thread local +variables at all. Pyramid view functions are passed a request object; many +of Pyramid's APIs require that an explicit request object be passed to them. +It is *possible* to retrieve the current Pyramid request as a threadlocal +variable but it is a "in case of emergency, break glass" type of activity. +This explicitness makes Pyramid view functions more easily unit testable, as +you don't need to rely on the framework to manufacture suitable "dummy" +request (and other similarly-scoped) objects during test setup. It also +makes them more likely to work on arbitrary systems, such as async servers +that do no monkeypatching. Explicitly WSGI +++++++++++++++ -Some microframeworks offer a ``run()`` method of an application object -that executes a default server configuration for easy execution. - -Pyramid doesn't currently try to hide the fact that its router is a -WSGI application behind a convenience ``run()`` API. It just tells -people to import a WSGI server and use it to serve up their Pyramid -application as per the documentation of that WSGI server. - -The extra lines saved by abstracting away the serving step behind -``run()`` seem to have driven dubious second-order decisions related -to API in some microframeworks. For example, Bottle contains a -``ServerAdapter`` subclass for each type of WSGI server it supports -via its ``app.run()`` mechanism. This means that there exists code in -``bottle.py`` that depends on the following modules: ``wsgiref``, -``flup``, ``paste``, ``cherrypy``, ``fapws``, ``tornado``, -``google.appengine``, ``twisted.web``, ``diesel``, ``gevent``, -``gunicorn``, ``eventlet``, and ``rocket``. You choose the kind of -server you want to run by passing its name into the ``run`` method. -In theory, this sounds great: I can try Bottle out on ``gunicorn`` -just by passing in a name! However, to fully test Bottle, all of -these third-party systems must be installed and functional; the Bottle -developers must monitor changes to each of these packages and make -sure their code still interfaces properly with them. This expands the -packages required for testing greatly; this is a *lot* of -requirements. It is likely difficult to fully automate these tests +Some microframeworks offer a ``run()`` method of an application object that +executes a default server configuration for easy execution. + +Pyramid doesn't currently try to hide the fact that its router is a WSGI +application behind a convenience ``run()`` API. It just tells people to +import a WSGI server and use it to serve up their Pyramid application as per +the documentation of that WSGI server. + +The extra lines saved by abstracting away the serving step behind ``run()`` +seem to have driven dubious second-order decisions related to API in some +microframeworks. For example, Bottle contains a ``ServerAdapter`` subclass +for each type of WSGI server it supports via its ``app.run()`` mechanism. +This means that there exists code in ``bottle.py`` that depends on the +following modules: ``wsgiref``, ``flup``, ``paste``, ``cherrypy``, ``fapws``, +``tornado``, ``google.appengine``, ``twisted.web``, ``diesel``, ``gevent``, +``gunicorn``, ``eventlet``, and ``rocket``. You choose the kind of server +you want to run by passing its name into the ``run`` method. In theory, this +sounds great: I can try Bottle out on ``gunicorn`` just by passing in a name! +However, to fully test Bottle, all of these third-party systems must be +installed and functional; the Bottle developers must monitor changes to each +of these packages and make sure their code still interfaces properly with +them. This expands the packages required for testing greatly; this is a +*lot* of requirements. It is likely difficult to fully automate these tests due to requirements conflicts and build issues. -As a result, for single-file apps, we currently don't bother to offer -a ``run()`` shortcut; we tell folks to import their WSGI server of -choice and run it "by hand". For the people who want a server -abstraction layer, we suggest that they use PasteDeploy. In -PasteDeploy-based systems, the onus for making sure that the server -can interface with a WSGI application is placed on the server -developer, not the web framework developer, making it more likely to -be timely and correct. +As a result, for single-file apps, we currently don't bother to offer a +``run()`` shortcut; we tell folks to import their WSGI server of choice and +run it "by hand". For the people who want a server abstraction layer, we +suggest that they use PasteDeploy. In PasteDeploy-based systems, the onus +for making sure that the server can interface with a WSGI application is +placed on the server developer, not the web framework developer, making it +more likely to be timely and correct. Wrapping Up +++++++++++ -Here's a diagrammed version of the simplest pyramid application, -where comments take into account what we've discussed in the +Here's a diagrammed version of the simplest pyramid application, where +comments take into account what we've discussed in the :ref:`microframeworks_smaller_hello_world` section. .. code-block:: python @@ -1686,6 +1566,91 @@ where comments take into account what we've discussed in the app = config.make_wsgi_app() # explicitly WSGI serve(app, host='0.0.0.0') # explicitly WSGI +Pyramid Has Zope Things In It, So It's Too Complex +-------------------------------------------------- + +On occasion, someone will feel compelled to post a mailing list message that +reads something like this: + +.. code-block:: text + + had a quick look at pyramid ... too complex to me and not really + understand for which benefits.. I feel should consider whether it's time + for me to step back to django .. I always hated zope (useless ?) + complexity and I love simple way of thinking + +(Paraphrased from a real email, actually.) + +Let's take this criticism point-by point. + +Too Complex ++++++++++++ + +If you can understand this hello world program, you can use Pyramid: + +.. code-block:: python + :linenos: + + from paste.httpserver import serve + from pyramid.config import Configurator + from pyramid.response import Response + + def hello_world(request): + return Response('Hello world!') + + if __name__ == '__main__': + config = Configurator() + config.add_view(hello_world) + app = config.make_wsgi_app() + serve(app) + +Pyramid has ~ 650 of documentation (printed), covering topics from the very +basic to the most advanced. *Nothing* is left undocumented, quite literally. +It also has an *awesome*, very helpful community. Visit the #repoze and/or +#pylons IRC channels on freenode.net and see. + +Hate Zope ++++++++++ + +I'm sorry you feel that way. The Zope brand has certainly taken its share of +lumps over the years, and has a reputation for being insular and mysterious. +But the word "Zope" is literally quite meaningless without qualification. +What *part* of Zope do you hate? "Zope" is a brand, not a technology. + +If it's Zope2-the-web-framework, Pyramid is not that. The primary designers +and developers of Pyramid, if anyone, should know. We wrote Pyramid's +predecessor (:mod:`repoze.bfg`), in part, because *we* knew that Zope 2 had +usability issues and limitations. :mod:`repoze.bfg` (and now :app:`Pyramid`) +was written to address these issues. + +If it's Zope3-the-web-framework, Pyramid is *definitely* not that. Making +use of lots of Zope 3 technologies is territory already staked out by the +:term:`Grok` project. Save for the obvious fact that they're both web +frameworks, :mod:`Pyramid` is very, very different than Grok. Grok exposes +lots of Zope technologies to end users. On the other hand, if you need to +understand a Zope-only concept while using Pyramid, then we've failed on some +very basic axis. + +If it's just the word Zope: this can only be guilt by association. Because a +piece of software internally uses some package named ``zope.foo``, it doesn't +turn the piece of software that uses it into "Zope". There is a lot of +*great* software written that has the word Zope in its name. Zope is not +some sort of monolithic thing, and a lot of its software is usable +externally. And while it's not really the job of this document to defend it, +Zope has been around for over 10 years and has an incredibly large, active +community. If you don't believe this, +http://taichino.appspot.com/pypi_ranking/authors is an eye-opening reality +check. + +Love Simplicity ++++++++++++++++ + +Years of effort have gone into honing this package and its documentation to +make it as simple as humanly possible for developers to use. Everything is a +tradeoff, of course, and people have their own ideas about what "simple" is. +You may have a style difference if you believe Pyramid is complex. Its +developers obviously disagree. + Other Challenges ---------------- diff --git a/docs/glossary.rst b/docs/glossary.rst index a3aacebce..49d273197 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -54,7 +54,8 @@ Glossary For example, the asset specification ``my.package:static/baz.css`` identifies the file named ``baz.css`` in the ``static`` subdirectory of the ``my.package`` - Python :term:`package`. + Python :term:`package`. See :ref:`asset_specifications` for more + info. package A directory on disk which contains an ``__init__.py`` file, making @@ -845,4 +846,10 @@ Glossary `WebTest <http://pythonpaste.org/webtest/>`_ is a package which can help you write functional tests for your WSGI application. + WebError + WSGI middleware which can display debuggable traceback information in + the browser when an exception is raised by a Pyramid application. See + http://pypi.python.org/pypi/WebError . + + diff --git a/docs/index.rst b/docs/index.rst index fbf9de810..23ffb3b1b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -45,10 +45,11 @@ Narrative documentation in chapter form explaining how to use narr/renderers narr/templates narr/resources - narr/static + narr/assets narr/webob narr/sessions narr/flash + narr/csrf narr/security narr/hybrid narr/i18n @@ -57,9 +58,9 @@ Narrative documentation in chapter form explaining how to use narr/environment narr/testing narr/hooks + narr/advconfig narr/declarative narr/extending - narr/assets narr/router narr/threadlocals narr/zca diff --git a/docs/latexindex.rst b/docs/latexindex.rst index 6a1992ba4..058835937 100644 --- a/docs/latexindex.rst +++ b/docs/latexindex.rst @@ -38,10 +38,11 @@ Narrative Documentation narr/renderers narr/templates narr/resources - narr/static + narr/assets narr/webob narr/sessions narr/flash + narr/csrf narr/security narr/hybrid narr/i18n @@ -50,9 +51,9 @@ Narrative Documentation narr/environment narr/testing narr/hooks + narr/advconfig narr/declarative narr/extending - narr/assets narr/router narr/startup narr/threadlocals @@ -89,7 +90,6 @@ API Reference api/config api/events api/exceptions - api/flash api/httpexceptions api/i18n api/interfaces @@ -125,7 +125,9 @@ ZCML Directive Reference zcml/configure zcml/default_permission zcml/forbidden + zcml/handler zcml/include + zcml/localenegotiator zcml/notfound zcml/remoteuserauthenticationpolicy zcml/renderer @@ -134,6 +136,7 @@ ZCML Directive Reference zcml/scan zcml/static zcml/subscriber + zcml/translationdir zcml/utility zcml/view diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst new file mode 100644 index 000000000..f8b3ee191 --- /dev/null +++ b/docs/narr/advconfig.rst @@ -0,0 +1,395 @@ +.. index:: + pair: advanced; configuration + +.. _advconfig_narr: + +Advanced Configuration +====================== + +To support application extensibility, the :app:`Pyramid` +:term:`Configurator`, by default, detects configuration conflicts and allows +you to include configuration imperatively from other packages or modules. It +also, by default, performs configuration in two separate phases. This allows +you to ignore relative configuration statement ordering in some +circumstances. + +.. index:: + single: imperative configuration + +.. _conflict_detection: + +Conflict Detection +------------------ + +Here's a familiar example of one of the simplest :app:`Pyramid` applications, +configured imperatively: + +.. code-block:: python + :linenos: + + from paste.httpserver import serve + from pyramid.config import Configurator + from pyramid.response import Response + + def hello_world(request): + return Response('Hello world!') + + if __name__ == '__main__': + config = Configurator() + config.add_view(hello_world) + app = config.make_wsgi_app() + serve(app, host='0.0.0.0') + +When you start this application, all will be OK. However, what happens if we +try to add another view to the configuration with the same set of +:term:`predicate` arguments as one we've already added? + +.. code-block:: python + :linenos: + + from paste.httpserver import serve + from pyramid.config import Configurator + from pyramid.response import Response + + def hello_world(request): + return Response('Hello world!') + + def goodbye_world(request): + return Response('Goodbye world!') + + if __name__ == '__main__': + config = Configurator() + + config.add_view(hello_world, name='hello') + + # conflicting view configuration + config.add_view(goodbye_world, name='hello') + + app = config.make_wsgi_app() + serve(app, host='0.0.0.0') + +The application now has two conflicting view configuration statements. When +we try to start it again, it won't start. Instead, we'll receive a traceback +that ends something like this: + +.. code-block:: guess + :linenos: + + Traceback (most recent call last): + File "app.py", line 12, in <module> + app = config.make_wsgi_app() + File "pyramid/config.py", line 839, in make_wsgi_app + self.commit() + File "pyramid/pyramid/config.py", line 473, in commit + self._ctx.execute_actions() + File "zope/configuration/config.py", line 600, in execute_actions + for action in resolveConflicts(self.actions): + File "zope/configuration/config.py", line 1507, in resolveConflicts + raise ConfigurationConflictError(conflicts) + zope.configuration.config.ConfigurationConflictError: + Conflicting configuration actions + For: ('view', None, '', None, <InterfaceClass pyramid.interfaces.IView>, + None, None, None, None, None, False, None, None, None) + ('app.py', 14, '<module>', 'config.add_view(hello_world)') + ('app.py', 17, '<module>', 'config.add_view(hello_world)') + +This traceback is trying to tell us: + +- We've got conflicting information for a set of view configuration + statements (The ``For:`` line). + +- There are two statements which conflict, shown beneath the ``For:`` line: + ``config.add_view(hello_world. 'hello')`` on line 14 of ``app.py``, and + ``config.add_view(goodbye_world, 'hello')`` on line 17 of ``app.py``. + +These two configuration statements are in conflict because we've tried to +tell the system that the set of :term:`predicate` values for both view +configurations are exactly the same. Both the ``hello_world`` and +``goodbye_world`` views are configured to respond under the same set of +circumstances. This circumstance: the :term:`view name` (represented by the +``name=`` predicate) is ``hello``. + +This presents an ambiguity that :app:`Pyramid` cannot resolve. Rather than +allowing the circumstance to go unreported, by default Pyramid raises a +:exc:`ConfigurationConflictError` error and prevents the application from +running. + +Conflict detection happens for any kind of configuration: imperative +configuration, :term:`ZCML` configuration, or configuration that results from +the execution of a :term:`scan`. + +.. note:: If you use, ZCML, its conflict detection algorithm is described in + :ref:`zcml_conflict_detection`. + +Manually Resolving Conflicts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are a number of ways to manually resolve conflicts: the "right" way, by +strategically using :meth:`pyramid.config.Configurator.commit`, or by using +an "autocommitting" configurator. + +The Right Thing ++++++++++++++++ + +The most correct way to resolve conflicts is to "do the needful": change your +configuration code to not have conflicting configuration statements. The +details of how this is done depends entirely on the configuration statements +made by your application. Use the detail provided in the +:exc:`ConfigurationConflictError` to track down the offending conflicts and +modify your configuration code accordingly. + +If you're getting a conflict while trying to extend an existing application, +and that application has a function which performs configuration like this +one: + +.. code-block:: python + :linenos: + + def add_routes(config): + config.add_route(...) + +Don't call this function directly with ``config`` as an argument. Instead, +use :meth:`pyramid.config.Configuration.include`: + +.. code-block:: python + :linenos: + + config.include(add_routes) + +Using :meth:`~pyramid.config.Configuration.include` instead of calling the +function directly provides a modicum of automated conflict resolution, with +the configuration statements you define in the calling code overriding those +of the included function. See also :ref:`automatic_conflict_resolution` and +:ref:`including_configuration`. + +Using ``config.commit()`` ++++++++++++++++++++++++++ + +You can manually commit a configuration by using the +:meth:`pyramid.config.Configurator.commit` method between configuration +calls. For example, we prevent conflicts from occurring in the application +we examined previously as the result of adding a ``commit``. Here's the +application that generates conflicts: + +.. code-block:: python + :linenos: + + from paste.httpserver import serve + from pyramid.config import Configurator + from pyramid.response import Response + + def hello_world(request): + return Response('Hello world!') + + def goodbye_world(request): + return Response('Goodbye world!') + + if __name__ == '__main__': + config = Configurator() + + config.add_view(hello_world, name='hello') + + # conflicting view configuration + config.add_view(goodbye_world, name='hello') + + app = config.make_wsgi_app() + serve(app, host='0.0.0.0') + +We can prevent the two ``add_view`` calls from conflicting by issuing a call +to :meth:`~pyramid.config.Configurator.commit` between them: + +.. code-block:: python + :linenos: + + from paste.httpserver import serve + from pyramid.config import Configurator + from pyramid.response import Response + + def hello_world(request): + return Response('Hello world!') + + def goodbye_world(request): + return Response('Goodbye world!') + + if __name__ == '__main__': + config = Configurator() + + config.add_view(hello_world, name='hello') + + config.commit() # commit any pending configuration actions + + # no-longer-conflicting view configuration + config.add_view(goodbye_world, name='hello') + + app = config.make_wsgi_app() + serve(app, host='0.0.0.0') + +In the above example we've issued a call to +:meth:`~pyramid.config.Configurator.commit` between the two ``add_view`` +calls. :meth:`~pyramid.config.Configurator.commit` will cause any pending +configuration statements. + +Calling :meth:`~pyramid.config.Configurator.commit` is safe at any time. It +executes all pending configuration actions and leaves the configuration +action list "clean". + +Note that :meth:`~pyramid.config.Configurator.commit` has no effect when +you're using an *autocommitting* configurator (see +:ref:`autocommitting_configurator`). + +.. _autocommitting_configurator: + +Using An Autocommitting Configurator +++++++++++++++++++++++++++++++++++++ + +You can also use a heavy hammer to circumvent conflict detection by using a +configurator constructor parameter: ``autocommit=True``. For example: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + if __name__ == '__main__': + config = Configurator(autocommit=True) + +When the ``autocommit`` parameter passed to the Configurator is ``True``, +conflict detection (and :ref:`twophase_config`) is disabled. Configuration +statements will be executed immediately, and succeeding statements will +override preceding ones. + +:meth:`pyramid.config.Configurator.commit` has no effect when ``autocommit`` +is ``True``. + +If you use a Configurator in code that performs unit testing, it's usually a +good idea to use an autocommitting Configurator, because you are usually +unconcerned about conflict detection or two-phase configuration in test code. + +.. _automatic_conflict_resolution: + +Automatic Conflict Resolution +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If your code uses the :meth:`pyramid.config.Configurator.include` method to +include external configuration, some conflicts are automatically resolved. +Configuration statements that are made as the result of an "include" will be +overridden by configuration statements that happen within the caller of +the "include" method. See also + +Automatic conflict resolution supports this goal: if a user wants to reuse a +Pyramid application, and they want to customize the configuration of this +application without hacking its code "from outside", they can "include" a +configuration function from the package and override only some of its +configuration statements within the code that does the include. No conflicts +will be generated by configuration statements within the code which does the +including, even if configuration statements in the included code would +conflict if it was moved "up" to the calling code. + +Methods Which Provide Conflict Detection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These are the methods of the configurator which provide conflict detection: + +:meth:`~pyramid.config.Configurator.add_view`, +:meth:`~pyramid.config.Configurator.add_route`, +:meth:`~pyramid.config.Configurator.add_renderer`, +:meth:`~pyramid.config.Configurator.set_request_factory`, +:meth:`~pyramid.config.Configurator.set_renderer_globals_factory` +:meth:`~pyramid.config.Configurator.set_locale_negotiator` and +:meth:`~pyramid.config.Configurator.set_default_permission`. + +Some other methods of the configurator also indirectly provide conflict +detection, because they're implemented in terms of conflict-aware methods: + +- :meth:`~pyramid.config.Configurator.add_handler`, a frontend for + ``add_route`` and ``add_view``. + +- :meth:`~pyramid.config.Configurator.add_route` does a second type of + conflict detection when a ``view`` parameter is passed (it calls + ``add_view``). + +- :meth:`~pyramid.config.Configurator.static_view`, a frontend for + ``add_route`` and ``add_view``. + +.. _including_configuration: + +Including Configuration from External Sources +--------------------------------------------- + +Some application programmers will factor their configuration code in such a +way that it is easy to reuse and override configuration statements. For +example, such a developer might factor out a function used to add routes to +his application: + +.. code-block:: python + :linenos: + + def add_routes(config): + config.add_route(...) + +Rather than calling this function directly with ``config`` as an argument. +Instead, use :meth:`pyramid.config.Configuration.include`: + +.. code-block:: python + :linenos: + + config.include(add_routes) + +Using ``include`` rather than calling the function directly will allow +:ref:`automatic_conflict_resolution` to work. + +.. note: See :ref:`the_include_tag` for a declarative alternative to + :meth:`pyramid.config.Configurator.include`. + +.. _twophase_config: + +Two-Phase Configuration +----------------------- + +When a non-autocommitting :term:`Configurator` is used to do configuration +(the default), configuration execution happens in two phases. In the first +phase, "eager" configuration actions (actions that must happen before all +others, such as registering a renderer) are executed, and *discriminators* +are computed for each of the actions that depend on the result of the eager +actions. In the second phase, the discriminators of all actions are compared +to do conflict detection. + +Due to this, for configuration methods that have no internal ordering +constraints, execution order of configuration method calls is not important. +For example, the relative ordering of +:meth:`pyramid.config.Configurator.add_view` and +:meth:`pyramid.config.Configurator.add_renderer` is unimportant when a +non-autocommitting configurator is used. This code snippet: + +.. code-block:: python + :linenos: + + config.add_view('some.view', renderer='path_to_custom/renderer.rn') + config.add_renderer('.rn', SomeCustomRendererFactory) + +Has the same result as: + +.. code-block:: python + :linenos: + + config.add_renderer('.rn', SomeCustomRendererFactory) + config.add_view('some.view', renderer='path_to_custom/renderer.rn') + +Even though the view statement depends on the registration of a custom +renderer, due to two-phase configuration, the order in which the +configuration statements are issued is not important. ``add_view`` will be +able to find the ``.rn`` renderer even if ``add_renderer`` is called after +``add_view``. + +The same is untrue when you use an *autocommitting* configurator (see +:ref:`autocommitting_configurator`). When an autocommitting configurator is +used, two-phase configuration is disabled, and configuration statements must +be ordered in dependency order. + +Some configuration methods, such as +:meth:`pyramid.config.Configurator.add_route` and +:meth:`pyramid.config.Configurator.add_handler` have internal ordering +constraints: they routes they imply require relative ordering. Such ordering +constraints are not absolved by two-phase configuration. Routes are still +added in configuration execution order. + diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index 1932e19ff..f73ff231a 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -1,74 +1,403 @@ .. index:: single: assets + single: static asssets .. _assets_chapter: -Assets -====== +Static Assets +============= An :term:`asset` is any file contained within a Python :term:`package` which is *not* a Python source code file. For example, each of the following is an asset: -- a :term:`Chameleon` template file contained within a Python package. +- a GIF image file contained within a Python package or contained within any + subdirectory of a Python package. -- a GIF image file contained within a Python package. +- a CSS file contained within a Python package or contained within any + subdirectory of a Python package. -- a CSS file contained within a Python package. - -- a JavaScript source file contained within a Python package. +- a JavaScript source file contained within a Python package or contained + within any subdirectory of a Python package. - A directory within a package that does not have an ``__init__.py`` in it (if it possessed an ``__init__.py`` it would *be* a package). +- a :term:`Chameleon` or :term:`Mako` template file contained within a Python + package. + The use of assets is quite common in most web development projects. For example, when you create a :app:`Pyramid` application using one of the available "paster" templates, as described in :ref:`creating_a_project`, the directory representing the application contains a Python :term:`package`. Within that Python package, there are directories full of files which are -assets. For example, there is a ``templates`` directory which contains -``.pt`` files, and a ``static`` directory which contains ``.css``, ``.js``, -and ``.gif`` files. +static assets. For example, there's a ``static`` directory which contains +``.css``, ``.js``, and ``.gif`` files. These asset files are delivered when +a user visits an application URL. -.. _understanding_assets: +.. _asset_specifications: -Understanding Assets --------------------- +Understanding Asset Specifications +---------------------------------- Let's imagine you've created a :app:`Pyramid` application that uses a :term:`Chameleon` ZPT template via the -:func:`pyramid.chameleon_zpt.render_template_to_response` API. For example, -the application might address the asset named ``templates/some_template.pt`` -using that API within a ``views.py`` file inside a ``myapp`` package: +:func:`pyramid.renderers.render_to_response` API. For example, the +application might address the asset using the :term:`asset specification` +``myapp:templates/some_template.pt`` using that API within a ``views.py`` +file inside a ``myapp`` package: .. ignore-next-block .. code-block:: python :linenos: - from pyramid.chameleon_zpt import render_template_to_response - render_template_to_response('templates/some_template.pt') + from pyramid.renderers import render_to_response + render_to_response('myapp:templates/some_template.pt', {}, request) -"Under the hood", when this API is called, :app:`Pyramid` attempts -to make sense out of the string ``templates/some_template.pt`` -provided by the developer. To do so, it first finds the "current" -package. The "current" package is the Python package in which the -``views.py`` module which contains this code lives. This would be the -``myapp`` package, according to our example so far. By resolving the -current package, :app:`Pyramid` has enough information to locate -the actual template file. These are the elements it needs: +"Under the hood", when this API is called, :app:`Pyramid` attempts to make +sense out of the string ``myapp:templates/some_template.pt`` provided by the +developer. This string is an :term:`asset specification`. It is composed of +two parts: - The *package name* (``myapp``) -- The *asset name* (``templates/some_template.pt``) +- The *asset name* (``templates/some_template.pt``), relative to the package + directory. + +The two parts are separated by the colon character. + +:app:`Pyramid` uses the Python :term:`pkg_resources` API to resolve the +package name and asset name to an absolute (operating-system-specific) file +name. It eventually passes this resolved absolute filesystem path to the +Chameleon templating engine, which then uses it to load, parse, and execute +the template file. + +There is a second form of asset specification: a *relative* asset +specification. Instead of using an "absolute" asset specification which +includes the package name, in certain circumstances you can omit the package +name from the specification. For example, you might be able to use +``templates/mytemplate.pt`` instead of ``myapp:templates/some_template.pt``. +Such asset specifications are usually relative to a "current package." The +"current package" is usually the package which contains the code that *uses* +the asset specification. :app:`Pyramid` APIs which accept relative asset +specifications typically describe what the asset is relative to in their +individual documentation. + +.. index:: + single: add_static_view + +.. _static_assets_section: + +Serving Static Assets +--------------------- + +:app:`Pyramid` makes it possible to serve up static asset files from a +directory on a filesystem to an application user's browser. Use the +:meth:`pyramid.config.Configurator.add_static_view` to instruct +:app:`Pyramid` to serve static assets such as JavaScript and CSS files. This +mechanism makes a directory of static files available at a name relative to +the application root URL, e.g. ``/static`` or as an external URL. + +.. note:: :meth:`~pyramid.config.Configurator.add_static_view` cannot serve a + single file, nor can it serve a directory of static files directly + relative to the root URL of a :app:`Pyramid` application. For these + features, see :ref:`advanced_static`. + +Here's an example of a use of +:meth:`~pyramid.config.Configurator.add_static_view` that will serve files up +from the ``/var/www/static`` directory of the computer which runs the +:app:`Pyramid` application as URLs beneath the ``/static`` URL prefix. + +.. code-block:: python + :linenos: + + # config is an instance of pyramid.config.Configurator + config.add_static_view(name='static', path='/var/www/static') + +The ``name`` prepresents a URL *prefix*. In order for files that live in the +``path`` directory to be served, a URL that requests one of them must begin +with that prefix. In the example above, ``name`` is ``static``, and ``path`` +is ``/var/www/static``. In English, this means that you wish to serve the +files that live in ``/var/www/static`` as sub-URLs of the ``/static`` URL +prefix. Therefore, the file ``/var/www/static/foo.css`` will be returned +when the user visits your application's URL ``/static/foo.css``. + +A static directory named at ``path`` may contain subdirectories recursively, +and any subdirectories may hold files; these will be resolved by the static +view as you would expect. The ``Content-Type`` header returned by the static +view for each particular type of file is dependent upon its file extension. + +By default, all files made available via +:meth:`~pyramid.config.Configurator.add_static_view` are accessible by +completely anonymous users. Simple authorization can be required, however. +To protect a set of static files using a permission, in addition to passing +the required ``name`` and ``path`` arguments, also pass the ``permission`` +keyword argument to :meth:`~pyramid.config.Configurator.add_static_view`. +The value of the ``permission`` argument represents the :term:`permission` +that the user must have relative to the current :term:`context` when the +static view is invoked. A user will be required to possess this permission +to view any of the files represented by ``path`` of the static view. If your +static resources must be protected by a more complex authorization scheme, +see :ref:`advanced_static`. + +Here's another example that uses an :term:`asset specification` instead of an +absolute path as the ``path`` argument. To convince +:meth:`pyramid.config.Configurator.add_static_view` to serve files up under +the ``/static`` URL from the ``a/b/c/static`` directory of the Python package +named ``some_package``, we can use a fully qualified :term:`asset +specification` as the ``path``: + +.. code-block:: python + :linenos: + + # config is an instance of pyramid.config.Configurator + config.add_static_view(name='static', path='some_package:a/b/c/static') + +The ``path`` provided to :meth:`pyramid.config.Configurator.add_static_view` +may be a fully qualified :term:`asset specification` or an *absolute path*. + +Instead of representing a URL prefix, the ``name`` argument of a call to +:meth:`pyramid.config.Configurator.add_static_view` can alternately be a +*URL*. Each of examples we've seen so far have shown usage of the ``name`` +argument as a URL prefix. However, when ``name`` is a *URL*, static assets +can be served from an external webserver. In this mode, the ``name`` is used +as the URL prefix when generating a URL using :func:`pyramid.url.static_url`. + +For example, :meth:`pyramid.config.Configurator.add_static_view` may +be fed a ``name`` argument which is ``http://example.com/images``: + +.. code-block:: python + :linenos: + + # config is an instance of pyramid.config.Configurator + config.add_static_view(name='http://example.com/images', + path='mypackage:images') + +Because :meth:`pyramid.config.Configurator.add_static_view` is provided with +a ``name`` argument that is the URL ``http://example.com/images``, subsequent +calls to :func:`pyramid.url.static_url` with paths that start with the +``path`` argument passed to +:meth:`pyramid.config.Configurator.add_static_view` will generate a URL +something like ``http://example.com/images/logo.png``. The external +webserver listening on ``example.com`` must be itself configured to respond +properly to such a request. The :func:`pyramid.url.static_url` API is +discussed in more detail later in this chapter. + +.. note:: + + The :ref:`static_directive` ZCML directive offers an declarative + equivalent to :meth:`pyramid.config.Configurator.add_static_view`. Use of + the :ref:`static_directive` ZCML directive is completely equivalent to + using imperative configuration for the same purpose. + +.. index:: + single: generating static asset urls + single: static asset urls + +.. _generating_static_asset_urls: + +Generating Static Asset URLs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When a :meth:`pyramid.config.Configurator.add_static_view` method is used to +register a static asset directory, a special helper API named +:func:`pyramid.url.static_url` can be used to generate the appropriate URL +for an asset that lives in one of the directories named by the static +registration ``path`` attribute. + +For example, let's assume you create a set of static declarations like so: + +.. code-block:: python + :linenos: + + config.add_static_view(name='static1', path='mypackage:assets/1') + config.add_static_view(name='static2', path='mypackage:assets/2') + +These declarations create URL-accessible directories which have URLs that +begin with ``/static1`` and ``/static2``, respectively. The assets in the +``assets/1`` directory of the ``mypackage`` package are consulted when a user +visits a URL which begins with ``/static1``, and the assets in the +``assets/2`` directory of the ``mypackage`` package are consulted when a user +visits a URL which begins with ``/static2``. + +You needn't generate the URLs to static assets "by hand" in such a +configuration. Instead, use the :func:`pyramid.url.static_url` API to +generate them for you. For example: + +.. code-block:: python + :linenos: + + from pyramid.url import static_url + from pyramid.chameleon_zpt import render_template_to_response + + def my_view(request): + css_url = static_url('mypackage:assets/1/foo.css', request) + js_url = static_url('mypackage:assets/2/foo.js', request) + return render_template_to_response('templates/my_template.pt', + css_url = css_url, + js_url = js_url) + +If the request "application URL" of the running system is +``http://example.com``, the ``css_url`` generated above would be: +``http://example.com/static1/foo.css``. The ``js_url`` generated +above would be ``http://example.com/static2/foo.js``. + +One benefit of using the :func:`pyramid.url.static_url` function rather than +constructing static URLs "by hand" is that if you need to change the ``name`` +of a static URL declaration, the generated URLs will continue to resolve +properly after the rename. + +URLs may also be generated by :func:`pyramid.url.static_url` to static assets +that live *outside* the :app:`Pyramid` application. This will happen when +the :meth:`pyramid.config.Configurator.add_static_view` API associated with +the path fed to :func:`pyramid.url.static_url` is a *URL* instead of a view +name. For example, the ``name`` argument may be ``http://example.com`` while +the the ``path`` given may be ``mypackage:images``: + +.. code-block:: python + :linenos: + + config.add_static_view(name='http://example.com/images', + path='mypackage:images') + +Under such a configuration, the URL generated by ``static_url`` for +assets which begin with ``mypackage:images`` will be prefixed with +``http://example.com/images``: + +.. code-block:: python + :linenos: + + static_url('mypackage:images/logo.png', request) + # -> http://example.com/images/logo.png + +Using :func:`pyramid.url.static_url` in conjunction with a +:meth:`pyramid.configuration.Configurator.add_static_view` makes it possible +to put static media on a separate webserver during production (if the +``name`` argument to :meth:`pyramid.config.Configurator.add_static_view` is a +URL), while keeping static media package-internal and served by the +development webserver during development (if the ``name`` argument to +:meth:`pyramid.config.Configurator.add_static_view` is a URL prefix). To +create such a circumstance, we suggest using the +:attr:`pyramid.registry.Registry.settings` API in conjunction with a setting +in the application ``.ini`` file named ``media_location``. Then set the +value of ``media_location`` to either a prefix or a URL depending on whether +the application is being run in development or in production (use a different +``.ini`` file for production than you do for development). This is just a +suggestion for a pattern; any setting name other than ``media_location`` +could be used. + +.. index:: + single: static assets view + +.. _advanced_static: + +Advanced: Serving Static Assets Using a View Callable +----------------------------------------------------- + +For more flexibility, static assets can be served by a :term:`view callable` +which you register manually. For example, if you're using :term:`URL +dispatch`, you may want static assets to only be available as a fallback if +no previous route matches. Alternately, you might like to serve a particular +static asset manually, because its download requires authentication. + +Note that you cannot use the :func:`pyramid.url.static_url` API to generate +URLs against assets made accessible by registering a custom static view. + +Root-Relative Custom Static View (URL Dispatch Only) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :class:`pyramid.view.static` helper class generates a Pyramid view +callable. This view callable can serve static assets from a directory. An +instance of this class is actually used by the +:meth:`pyramid.config.Configurator.add_static_view` configuration method, so +its behavior is almost exactly the same once it's configured. + +.. warning:: The following example *will not work* for applications that use + :term:`traversal`, it will only work if you use :term:`URL dispatch` + exclusively. The root-relative route we'll be registering will always be + matched before traversal takes place, subverting any views registered via + ``add_view`` (at least those without a ``route_name``). A + :class:`pyramid.view.static` static view cannot be made root-relative when + you use traversal. + +To serve files within a directory located on your filesystem at +``/path/to/static/dir`` as the result of a "catchall" route hanging from the +root that exists at the end of your routing table, create an instance of the +:class:`pyramid.view.static` class inside a ``static.py`` file in your +application root as below. + +.. ignore-next-block +.. code-block:: python + :linenos: + + from pyramid.view import static + static_view = static('/path/to/static/dir') + +.. note:: For better cross-system flexibility, use an :term:`asset + specification` as the argument to :class:`pyramid.view.static` instead of + a physical absolute filesystem path, e.g. ``mypackage:static`` instead of + ``/path/to/mypackage/static``. + +Subsequently, you may wire the files that are served by this view up to be +accessible as ``/<filename>`` using a configuration method in your +application's startup code. + +.. code-block:: python + :linenos: + + # .. every other add_route and/or add_handler declaration should come + # before this one, as it will, by default, catch all requests + + config.add_route('catchall_static', '/*subpath', 'myapp.static.static_view') + +The special name ``*subpath`` above is used by the +:class:`pyramid.view.static` view callable to signify the path of the file +relative to the directory you're serving. + +Registering A View Callable to Serve a "Static" Asset +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can register a simple view callable to serve a single static asset. To +do so, do things "by hand". First define the view callable. + +.. code-block:: python + :linenos: + + import os + from webob import Response + + def favicon_view(request): + here = os.path.dirname(__file__) + icon = open(os.path.join(here, 'static', 'favicon.ico')) + return Response(content_type='image/x-icon', app_iter=icon) + +The above bit of code within ``favicon_view`` computes "here", which is a +path relative to the Python file in which the function is defined. It then +uses the Python ``open`` function to obtain a file handle to a file within +"here" named ``static``, and returns a response using the open the file +handle as the response's ``app_iter``. It makes sure to set the right +content_type too. + +You might register such a view via configuration as a view callable that +should be called as the result of a traversal: + +.. code-block:: python + :linenos: + + config.add_view('myapp.views.favicon_view', name='favicon.ico') + +Or you might register it to be the view callable for a particular route: + +.. code-block:: python + :linenos: + + config.add_route('favicon', '/favicon.ico', + view='myapp.views.favicon_view') -:app:`Pyramid` uses the :term:`pkg_resources` API to resolve the package name -and asset name to an absolute (operating-system-specific) file name. It -eventually passes this resolved absolute filesystem path to the Chameleon -templating engine, which then uses it to load, parse, and execute the -template file. +Because this is a simple view callable, it can be protected with a +:term:`permission` or can be configured to respond under different +circumstances using :term:`view predicate` arguments. -Package names often contain dots. For example, ``pyramid`` is a package. -Asset names usually look a lot like relative UNIX file paths. .. index:: pair: overriding; assets diff --git a/docs/narr/csrf.rst b/docs/narr/csrf.rst new file mode 100644 index 000000000..7586b0ed7 --- /dev/null +++ b/docs/narr/csrf.rst @@ -0,0 +1,63 @@ +.. _csrf_chapter: + +Preventing Cross-Site Request Forgery Attacks +============================================= + +`Cross-site request forgery +<http://en.wikipedia.org/wiki/Cross-site_request_forgery>`_ attacks are a +phenomenon whereby a user with an identity on your website might click on a +URL or button on another website which unwittingly redirects the user to your +application to perform some command that requires elevated privileges. + +You can avoid most of these attacks by making sure that a the correct *CSRF +token* has been set in an :app:`Pyramid` session object before performing any +actions in code which requires elevated privileges and is invoked via a form +post. To use CSRF token support, you must enable a :term:`session factory` +as described in :ref:`using_the_default_session_factory` or +:ref:`using_alternate_session_factories`. + +Using the ``session.new_csrf_token`` Method +------------------------------------------- + +To add a CSRF token to the session, use the ``session.new_csrf_token`` method. + +.. code-block:: python + :linenos: + + token = request.session.new_csrf_token() + +The ``.new_csrf_token`` method accepts no arguments. It returns a *token* +string, which will be opaque and randomized. This token will also be set +into the session, awaiting pickup by the ``session.get_csrf_token`` method. +You can subsequently use the returned token as the value of a hidden field in +a form that posts to a method that requires elevated privileges. The handler +for the form post should use ``session.get_csrf_token`` (explained below) to +obtain the current CSRF token related to the user from the session, and +compare it to the value of the hidden form field. + +Using the ``session.get_csrf_token`` Method +------------------------------------------- + +To get the current CSRF token from the session, use the +``session.get_csrf_token`` method. + +.. code-block:: python + :linenos: + + token = request.session.get_csrf_token() + +The ``get_csrf_token`` method accepts no arguments. It returns the "current" +*token* string (as per the last call to ``session.new_csrf_token``). You can +then use it to compare against the token provided within form post hidden +value data. For example, if your form rendering included the CSRF token +obtained via ``session.new_csrf_token`` as a hidden input field named +``csrf_token``: + +.. code-block:: python + :linenos: + + token = request.session.get_csrf_token() + if token != request.POST['csrf_token']: + raise ValueError('CSRF token did not match') + + diff --git a/docs/narr/declarative.rst b/docs/narr/declarative.rst index deccb6c48..f36e55b29 100644 --- a/docs/narr/declarative.rst +++ b/docs/narr/declarative.rst @@ -3,28 +3,27 @@ Declarative Configuration ========================= -The mode of configuration most comprehensively detailed by examples in -narrative chapters in this book is "imperative" configuration. This is the -configuration mode in which a developer cedes the least amount of control to -the framework; it's "imperative" because you express the configuration -directly in Python code, and you have the full power of Python at your -disposal as you issue configuration statements. However, another mode of -configuration exists within :app:`Pyramid`, which often provides better -extensibility and configuration conflict detection. +The mode of configuration detailed in the majority of examples within this +this book is "imperative" configuration. This is the configuration mode in +which a developer cedes the least amount of control to the framework; it's +"imperative" because you express the configuration directly in Python code, +and you have the full power of Python at your disposal as you issue +configuration statements. However, another mode of configuration exists +within :app:`Pyramid` named :term:`ZCML` which often provides better +opportunity for extensibility. A complete listing of ZCML directives is available within :ref:`zcml_directives`. This chapter provides an overview of how you might get started with ZCML and highlights some common tasks performed when you use -ZCML. You can get a better understanding of when it's appropriate to use -ZCML from :ref:`extending_chapter`. +ZCML. .. index:: single: declarative configuration .. _declarative_configuration: -Declarative Configuration -------------------------- +ZCML Configuration +------------------ A :app:`Pyramid` application can be configured "declaratively", if so desired. Declarative configuration relies on *declarations* made external to @@ -48,9 +47,7 @@ In a file named ``helloworld.py``: if __name__ == '__main__': config = Configurator() - config.begin() config.load_zcml('configure.zcml') - config.end() app = config.make_wsgi_app() serve(app, host='0.0.0.0') @@ -83,9 +80,7 @@ the ``if __name__ == '__main__'`` section of ``helloworld.py``: if __name__ == '__main__': config = Configurator() - config.begin() config.add_view(hello_world) - config.end() app = config.make_wsgi_app() serve(app, host='0.0.0.0') @@ -99,9 +94,7 @@ it now reads as: if __name__ == '__main__': config = Configurator() - config.begin() config.load_zcml('configure.zcml') - config.end() app = config.make_wsgi_app() serve(app, host='0.0.0.0') @@ -163,6 +156,8 @@ configure your application; instead you need to use :term:`ZCML`. .. index:: single: ZCML conflict detection +.. _zcml_conflict_detection: + ZCML Conflict Detection ~~~~~~~~~~~~~~~~~~~~~~~ @@ -224,9 +219,7 @@ To do so, first, create a file named ``helloworld.py``: if __name__ == '__main__': config = Configurator() - config.begin() config.load_zcml('configure.zcml') - config.end() app = config.make_wsgi_app() serve(app, host='0.0.0.0') @@ -269,10 +262,8 @@ within the ``if __name__ == '__main__'`` section of ``helloworld.py``: if __name__ == '__main__': config = Configurator() - config.begin() config.add_view(hello_world) config.add_view(goodbye_world, name='goodbye') - config.end() app = config.make_wsgi_app() serve(app, host='0.0.0.0') @@ -287,9 +278,7 @@ name='goodbye')``, so that it now reads as: if __name__ == '__main__': config = Configurator() - config.begin() config.load_zcml('configure.zcml') - config.end() app = config.make_wsgi_app() serve(app, host='0.0.0.0') @@ -345,6 +334,8 @@ contain other directives. See also :ref:`configure_directive` and :ref:`word_on_xml_namespaces`. +.. _the_include_tag: + The ``<include>`` Tag ~~~~~~~~~~~~~~~~~~~~~ @@ -478,6 +469,45 @@ declaratively. More information about this mode of configuration is available in :ref:`declarative_configuration` and within :ref:`zcml_reference`. +.. index:: + single: ZCML granularity + +ZCML Granularity +~~~~~~~~~~~~~~~~ + +It's extremely helpful to third party application "extenders" (aka +"integrators") if the :term:`ZCML` that composes the configuration for an +application is broken up into separate files which do very specific things. +These more specific ZCML files can be reintegrated within the application's +main ``configure.zcml`` via ``<include file="otherfile.zcml"/>`` +declarations. When ZCML files contain sets of specific declarations, an +integrator can avoid including any ZCML he does not want by including only +ZCML files which contain the declarations he needs. He is not forced to +"accept everything" or "use nothing". + +For example, it's often useful to put all ``<route>`` declarations in a +separate ZCML file, as ``<route>`` statements have a relative ordering that +is extremely important to the application: if an extender wants to add a +route to the "middle" of the routing table, he will always need to disuse all +the routes and cut and paste the routing configuration into his own +application. It's useful for the extender to be able to disuse just a +*single* ZCML file in this case, accepting the remainder of the configuration +from other :term:`ZCML` files in the original application. + +Granularizing ZCML is not strictly required. An extender can always disuse +*all* your ZCML, choosing instead to copy and paste it into his own package, +if necessary. However, doing so is considerate, and allows for the best +reusability. Sometimes it's possible to include only certain ZCML files from +an application that contain only the registrations you really need, omitting +others. But sometimes it's not. For brute force purposes, when you're +getting ``view`` or ``route`` registrations that you don't actually want in +your overridden application, it's always appropriate to just *not include* +any ZCML file from the overridden application. Instead, just cut and paste +the entire contents of the ``configure.zcml`` (and any ZCML file included by +the overridden application's ``configure.zcml``) into your own package and +omit the ``<include package=""/>`` ZCML declaration in the overriding +package's ``configure.zcml``. + .. _zcml_scanning: Scanning via ZCML @@ -503,9 +533,7 @@ file points to is scanned. if __name__ == '__main__': from pyramid.config import Configurator config = Configurator() - config.begin() config.load_zcml('configure.zcml') - config.end() app = config.make_wsgi_app() serve(app, host='0.0.0.0') @@ -1266,9 +1294,143 @@ which we assume lives in a ``subscribers.py`` module within your application: See also :ref:`subscriber_directive` and :ref:`events_chapter`. +.. index:: + single: not found view + +.. _notfound_zcml: + +Configuring a Not Found View via ZCML +------------------------------------- + +If your application uses :term:`ZCML`, you can replace the Not Found view by +placing something like the following ZCML in your ``configure.zcml`` file. + +.. code-block:: xml + :linenos: + + <view + view="helloworld.views.notfound_view" + context="pyramid.exceptions.NotFound" + /> + +Replace ``helloworld.views.notfound_view`` with the Python dotted name to the +notfound view you want to use. + +See :ref:`changing_the_notfound_view` for more information. + +.. index:: + single: forbidden view -.. Todo -.. ---- +.. _forbidden_zcml: + +Configuring a Forbidden View via ZCML +------------------------------------- + +If your application uses :term:`ZCML`, you can replace the Forbidden view by +placing something like the following ZCML in your ``configure.zcml`` file. + +.. code-block:: xml + :linenos: + + <view + view="helloworld.views.notfound_view" + context="pyramid.exceptions.Forbidden" + /> + +Replace ``helloworld.views.forbidden_view`` with the Python dotted name to +the forbidden view you want to use. + +See :ref:`changing_the_forbidden_view` for more information. + +.. _changing_traverser_zcml: + +Configuring an Alternate Traverser via ZCML +------------------------------------------- + +Use an ``adapter`` stanza in your application's ``configure.zcml`` to +change the default traverser: + +.. code-block:: xml + :linenos: + + <adapter + factory="myapp.traversal.Traverser" + provides="pyramid.interfaces.ITraverser" + for="*" + /> + +Or to register a traverser for a specific resource type: + +.. code-block:: xml + :linenos: + + <adapter + factory="myapp.traversal.Traverser" + provides="pyramid.interfaces.ITraverser" + for="myapp.resources.MyRoot" + /> + +See :ref:`changing_the_traverser` for more information. + +.. index:: + single: url generator + +.. _changing_resource_url_zcml: + +Changing ``resource_url`` URL Generation via ZCML +------------------------------------------------- + +You can change how :func:`pyramid.url.resource_url` generates a URL for a +specific type of resource by adding an adapter statement to your +``configure.zcml``. + +.. code-block:: xml + :linenos: + + <adapter + factory="myapp.traversal.URLGenerator" + provides="pyramid.interfaces.IContextURL" + for="myapp.resources.MyRoot *" + /> + +See :ref:`changing_resource_url` for more information. + +.. _changing_request_factory_zcml: + +Changing the Request Factory via ZCML +------------------------------------- + +A ``MyRequest`` class can be registered via ZCML as a request factory through +the use of the ZCML ``utility`` directive. In the below, we assume it lives +in a package named ``mypackage.mymodule``. + +.. code-block:: xml + :linenos: + + <utility + component="mypackage.mymodule.MyRequest" + provides="pyramid.interfaces.IRequestFactory" + /> + +See :ref:`changing_request_factory` for more information. + +.. _adding_renderer_globals_zcml: + +Changing the Renderer Globals Factory via ZCML +---------------------------------------------- + +A renderer globals factory can be registered via ZCML as a through the use of +the ZCML ``utility`` directive. In the below, we assume a +``renderers_globals_factory`` function lives in a package named +``mypackage.mymodule``. + +.. code-block:: xml + :linenos: + + <utility + component="mypackage.mymodule.renderer_globals_factory" + provides="pyramid.interfaces.IRendererGlobalsFactory" + /> -.. - hooks chapter still has topics for ZCML +See :ref:`adding_renderer_globals` for more information. diff --git a/docs/narr/extending.rst b/docs/narr/extending.rst index 9802a01f6..524dcb2ac 100644 --- a/docs/narr/extending.rst +++ b/docs/narr/extending.rst @@ -3,11 +3,61 @@ Extending An Existing :app:`Pyramid` Application =================================================== -If the developer of a :app:`Pyramid` application has obeyed certain -constraints while building that application, a third party should be -able to change its behavior without needing to modify its source code. -The behavior of a :app:`Pyramid` application that obeys certain -constraints can be *overridden* or *extended* without modification. +If a :app:`Pyramid` developer has obeyed certain constraints while building +an application, a third party should be able to change the application's +behavior without needing to modify its source code. The behavior of a +:app:`Pyramid` application that obeys certain constraints can be *overridden* +or *extended* without modification. + +We'll define some jargon here for the benefit of identifying the parties +involved in such an effort. + +Developer + The original application developer. + +Integrator + Another developer who wishes to reuse the application written by the + original application developer in an unanticipated context. He may also + wish to modify the original application without changing the original + application's source code. + +The Difference Between "Extensible" and "Pluggable" Applications +---------------------------------------------------------------- + +Other web frameworks, such as :term:`Django`, advertise that they allow +developers to create "pluggable applications". They claim that if you create +an application in a certain way, it will be integratable in a sensible, +structured way into another arbitrarily-written application or project +created by a third-party developer. + +:app:`Pyramid`, as a platform, does not claim to provide such a feature. The +platform provides no guarantee that you can create an application and package +it up such that an arbitrary integrator can use it as a subcomponent in a +larger Pyramid application or project. Pyramid does not mandate the +constraints necessary for such a pattern to work satisfactorily. Because +Pyramid is not very "opinionated", developers are able to use wildly +different patterns and technologies to build an application. A given Pyramid +application may happen to be reusable by a particular third party integrator, +because the integrator and the original developer may share similar base +technology choices (such as the use of a particular relational database or +ORM). But the same application may not be reusable by a different developer, +because he has made different technology choices which are incompatible with +the original developer's. + +As a result, the concept of a "pluggable application" is left to layers built +above Pyramid, such as a "CMS" layer or "application server" layer. Such +layers are apt to provide the necessary "opinions" (such as mandating a +storage layer, a templating system, and a structured, well-documented pattern +of registering that certain URLs map to certain bits of code) which makes the +concept of a "pluggable application" possible. "Pluggable applications", +thus, should not plug in to Pyramid itself but should instead plug into a +system written atop Pyramid. + +Although it does not provide for "pluggable applications", Pyramid *does* +provide a rich set of mechanisms which allows for the extension of a single +existing application. Such features can be used by frameworks built using +Pyramid as a base. All Pyramid applications may not be *pluggable*, but all +Pyramid applications are *extensible*. .. index:: single: extensible application @@ -15,65 +65,65 @@ constraints can be *overridden* or *extended* without modification. Rules for Building An Extensible Application -------------------------------------------- -There's only one rule you need to obey if you want to build a -maximally extensible :app:`Pyramid` application: you should not use -any :term:`configuration decoration` or :term:`imperative -configuration`. This means the application developer should avoid -relying on :term:`configuration decoration` meant to be detected via -a :term:`scan`, and you mustn't configure your :app:`Pyramid` -application *imperatively* by using any code which configures the -application through methods of the :term:`Configurator` (except for -the :meth:`pyramid.config.Configurator.load_zcml` method). - -Instead, you must always use :term:`ZCML` for the equivalent -purposes. :term:`ZCML` declarations that belong to an application can be -"overridden" by integrators as necessary, but decorators and imperative code -which perform the same tasks cannot. Use only :term:`ZCML` to configure your -application if you'd like it to be extensible. See +There is only one rule you need to obey if you want to build a maximally +extensible :app:`Pyramid` application: as a developer, you should factor any +overrideable :term:`imperative configuration` you've created into functions +which can be used via :meth:`pyramid.config.Configurator.include` rather than +inlined as calls to methods of a :term:`Configurator` within the ``main`` +function in your application's ``__init__.py``. For example, rather than: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + if __name__ == '__main__': + config = Configurator() + config.add_view('myapp.views.view1', name='view1') + config.add_view('myapp.views.view2', name='view2') + +You should do move the calls to ``add_view`` outside of the (non-reusable) +``if __name__ == '__main__'`` block, and into a reusable function: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + if __name__ == '__main__': + config = Configurator() + config.include(add_views) + + def add_views(config): + config.add_view('myapp.views.view1', name='view1') + config.add_view('myapp.views.view2', name='view2') + +Doing this allows an integrator to maximally reuse the configuration +statements that relate to your application by allowing him to selectively +include or disinclude the configuration functions you've created from an +"override package". + +Alternately, you can use :term:`ZCML` for the purpose of making configuration +extensible and overrideable. :term:`ZCML` declarations that belong to an +application can be overridden and extended by integrators as necessary in a +similar fashion. If you use only :term:`ZCML` to configure your application, +it will automatically be maximally extensible without any manual effort. See :ref:`declarative_chapter` for information about using ZCML. Fundamental Plugpoints ~~~~~~~~~~~~~~~~~~~~~~ The fundamental "plug points" of an application developed using -:app:`Pyramid` are *routes*, *views*, and *resources*. Routes are -declarations made using the ZCML ``<route>`` directive. Views are -declarations made using the ZCML ``<view>`` directive (or the -``@view_config`` decorator). Resources are files that are accessed by -:app:`Pyramid` using the :term:`pkg_resources` API such as static -files and templates. - -.. index:: - single: ZCML granularity - -ZCML Granularity -~~~~~~~~~~~~~~~~ - -It's extremely helpful to third party application "extenders" (aka -"integrators") if the :term:`ZCML` that composes the configuration for -an application is broken up into separate files which do very specific -things. These more specific ZCML files can be reintegrated within the -application's main ``configure.zcml`` via ``<include -file="otherfile.zcml"/>`` declarations. When ZCML files contain sets -of specific declarations, an integrator can avoid including any ZCML -he does not want by including only ZCML files which contain the -declarations he needs. He is not forced to "accept everything" or -"use nothing". - -For example, it's often useful to put all ``<route>`` declarations in -a separate ZCML file, as ``<route>`` statements have a relative -ordering that is extremely important to the application: if an -extender wants to add a route to the "middle" of the routing table, he -will always need to disuse all the routes and cut and paste the -routing configuration into his own application. It's useful for the -extender to be able to disuse just a *single* ZCML file in this case, -accepting the remainder of the configuration from other :term:`ZCML` -files in the original application. - -Granularizing ZCML is not strictly required. An extender can always -disuse *all* your ZCML, choosing instead to copy and paste it into his -own package, if necessary. However, doing so is considerate, and -allows for the best reusability. +:app:`Pyramid` are *routes*, *views*, and *assets*. Routes are declarations +made using the :meth:`pyramid.config.Configurator.add_route` method (or the +ZCML ``<route>`` directive). Views are declarations made using the +:meth:`pyramid.config.Configurator.add_view` method (or the ZCML ``<view>`` +directive). Assets are files that are accessed by :app:`Pyramid` using the +:term:`pkg_resources` API such as static files and templates via a +:term:`asset specification`. Other directives and configurator methods also +deal in routes, views, and assets. For example, +:meth:`pyramid.config.Configurator.add_handler` adds a single route, and some +number of views. .. index:: single: extending an existing application @@ -81,96 +131,88 @@ allows for the best reusability. Extending an Existing Application --------------------------------- -The steps for extending an existing application depend largely on -whether the application does or does not use configuration decorators -and/or imperative code. +The steps for extending an existing application depend largely on whether the +application does or does not use configuration decorators and/or imperative +code. + +If The Application Has Configuration Decorations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You've inherited a :app:`Pyramid` application which you'd like to extend or +override that uses :class:`pyramid.view.view_config` decorators or other +:term:`configuration decoration` decorators. -Extending an Application Which Possesses Configuration Decorators Or Which Does Configuration Imperatively -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If you just want to *extend* the application, you can run a :term:`scan` +against the application's package, then add additional configuration that +registers more views or routes. -If you've inherited a :app:`Pyramid` application which uses -:class:`pyramid.view.view_config` decorators or which performs -configuration imperatively, one of two things may be true: +.. code-block:: python + :linenos: + + if __name__ == '__main__': + config.scan('someotherpackage') + config.add_view('mypackage.views.myview', name='myview') -- If you just want to *extend* the application, you can write - additional ZCML that registers more views or routes, loading any - existing ZCML and continuing to use any existing imperative - configuration done by the original application. +If you want to *override* configuration in the application, you *may* need to +run :meth:`pyramid.config.Configurator.commit` after performing the scan of +the original package, then add additional configuration that registers more +views or routes which performs overrides. -- If you want to *override* configuration in the application, you - *may* need to change the source code of the original application. +.. code-block:: python + :linenos: - If the only source of trouble is the existence of - :class:`pyramid.view.view_config` decorators, you can just prevent a - :term:`scan` from happening (by omitting the ``<scan>`` declaration - from ZCML or omitting any call to the - :meth:`pyramid.config.Configurator.scan` method). This - will cause the decorators to do nothing. At this point, you will - need to convert all the configuration done in decorators into - equivalent :term:`ZCML` and add that ZCML to a separate Python - package as described in :ref:`extending_the_application`. + if __name__ == '__main__': + config.scan('someotherpackage') + config.commit() + config.add_view('mypackage.views.myview', name='myview' - If the source of trouble is configuration done imperatively in a - function called during application startup, you'll need to change - the code: convert imperative configuration statements into - equivalent :term:`ZCML` declarations. +Once this is done, you should be able to extend or override the application +like any other (see :ref:`extending_the_application`). -Once this is done, you should be able to extend or override the -application like any other (see :ref:`extending_the_application`). +You can alternately just prevent a :term:`scan` from happening (by omitting +any call to the :meth:`pyramid.config.Configurator.scan` method). This will +cause the decorators attached to objects in the target application to do +nothing. At this point, you will need to convert all the configuration done +in decorators into equivalent imperative configuration or ZCML and add that +configuration or ZCML to a separate Python package as described in +:ref:`extending_the_application`. .. _extending_the_application: -Extending an Application Which Does Not Possess Configuration Decorators or Imperative Configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To extend or override the behavior of an existing application, you -will need to write some :term:`ZCML`, and perhaps some implementations -of the types of things you'd like to override (such as views), which -are referred to within that ZCML. - -The general pattern for extending an existing application looks -something like this: - -- Create a new Python package. The easiest way to do this is to - create a new :app:`Pyramid` application using the "paster" - template mechanism. See :ref:`creating_a_project` for more - information. - -- Install the new package into the same Python environment as the - original application (e.g. ``python setup.py develop`` or ``python - setup.py install``). - -- Change the ``configure.zcml`` in the new package to include the - original :app:`Pyramid` application's ``configure.zcml`` via an - include statement, e.g. ``<include package="theoriginalapp"/>``. - Alternately, if the original application writer anticipated - overriding some things and not others, instead of including the - "main" ``configure.zcml`` of the original application, include only - specific ZCML files from the original application using the ``file`` - attribute of the ``<include>`` statement, e.g. ``<include - package="theoriginalapp" file="views.zcml"/>``. - -- On a line in the new package's ``configure.zcml`` file that falls - after (XML-ordering-wise) all the ``include`` statements of the original - package ZCML, put an ``includeOverrides`` statement which identifies - *another* ZCML file within the new package (for example - ``<includeOverrides file="overrides.zcml"/>``. - -- Create an ``overrides.zcml`` file within the new package. The - statements in the ``overrides.zcml`` file will override any ZCML - statements made within the original application (such as view - declarations). - -- Create Python files containing views and other overridden elements, - such as templates and static resources as necessary, and wire these - up using ZCML registrations within the ``overrides.zcml`` file. - These registrations may extend or override the original view - registrations. See :ref:`overriding_views`, - :ref:`overriding_routes` and :ref:`overriding_resources`. +Extending the Application +~~~~~~~~~~~~~~~~~~~~~~~~~ + +To extend or override the behavior of an existing application, you will need +to create a new package which includes the configuration of the old package, +and you'll perhaps need to create implementations of the types of things +you'd like to override (such as views), which are referred to within the +original package. -- In the ``__init__.py`` of the new package, load the ``configure.zcml`` file - of the new package using the - :meth:`pyramid.config.Configurator.load_zcml` method. +The general pattern for extending an existing application looks something +like this: + +- Create a new Python package. The easiest way to do this is to create a new + :app:`Pyramid` application using the "paster" template mechanism. See + :ref:`creating_a_project` for more information. + +- In the new package, create Python files containing views and other + overridden elements, such as templates and static resources as necessary. + +- Install the new package into the same Python environment as the original + application (e.g. ``python setup.py develop`` or ``python setup.py + install``). + +- Change the ``main`` function in the new package's ``__init__py`` to include + the original :app:`Pyramid` application's configuration functions via + :meth:`pyramid.config.Configurator.include` statements or a :term:`scan`. + +- Wire the new views and assets created in the new package up using + imperative registrations within the ``main`` function of the + ``__init__.py`` file of the new application. These wiring should happen + *after* including the configuration functions of the old application. + These registrations will extend or override any registrations performed by + the original application. See :ref:`overriding_views`, + :ref:`overriding_routes` and :ref:`overriding_resources`. .. index:: pair: overriding; views @@ -180,26 +222,44 @@ something like this: Overriding Views ~~~~~~~~~~~~~~~~~ -The ZCML ``<view>`` declarations you make which *override* application -behavior will usually have the same ``context`` and ``name`` (and -:term:`predicate` attributes, if used) as the original. These -``<view>`` declarations will point at "new" view code. The new view -code itself will usually be cut-n-paste copies of view callables from -the original application with slight tweaks. For example: +The :term:`view configuration` declarations you make which *override* +application behavior will usually have the same :term:`view predicate` +attributes as the original you wish to override. These ``<view>`` +declarations will point at "new" view code, in the override package you've +created. The new view code itself will usually be cut-n-paste copies of view +callables from the original application with slight tweaks. + +For example, if the original application has the following +``configure_views`` configuration method: + +.. code-block:: python + :linenos: + + def configure_views(config): + config.add_view('theoriginalapp.views.theview', name='theview') -.. code-block:: xml +You can override the first view configuration statement made by +``configure_views`` within the override package, after loading the original +configuration function: + +.. code-block:: python :linenos: - <view - context="theoriginalapplication.resources.SomeResource" - name="theview" - view=".views.a_view_that_does_something_slightly_different" - /> + from pyramid.config import Configurator + from originalapp import configure_views + + if __name == '__main__': + config = Configurator() + config.include(configure_views) + config.add_view('theoverrideapp.views.theview', name='theview') + +In this case, the ``theoriginalapp.views.theview`` view will never be +executed. Instead, a new view, ``theoverrideapp.views.theview`` will be +executed instead, when request circumstances dictate. -A similar pattern can be used to *extend* the application with ``<view>`` -declarations. Just register a new view against some existing resource type -(using ``context``) and make sure the URLs it implies are available on some -other page rendering. +A similar pattern can be used to *extend* the application with ``add_view`` +declarations. Just register a new view against some other set of predicates +to make sure the URLs it implies are available on some other page rendering. .. index:: pair: overriding; routes @@ -209,48 +269,27 @@ other page rendering. Overriding Routes ~~~~~~~~~~~~~~~~~ -Route setup is currently typically performed in a sequence of ordered -ZCML ``<route>`` declarations. Because these declarations are ordered -relative to each other, and because this ordering is typically -important, you should retain the relative ordering of these -declarations when performing an override. Typically, this means -*copying* all the ``<route>`` declarations into an external ZCML file -and changing them as necessary. Then disinclude any ZCML from the -original application which contains the original declarations. +Route setup is currently typically performed in a sequence of ordered calls +to :meth:`pyramid.config.Configurator.add_route`. Because these calls are +ordered relative to each other, and because this ordering is typically +important, you should retain their relative ordering when performing an +override. Typically, this means *copying* all the ``add_route`` statements +into the override package's file and changing them as necessary. Then +disinclude any ``add_route`` statements from the original application. .. index:: pair: overriding; resources .. _overriding_resources: -Overriding Resources -~~~~~~~~~~~~~~~~~~~~ - -"Resource" files are static files on the filesystem that are -accessible within a Python *package*. An entire chapter is devoted to -resources: :ref:`resources_chapter`. Within this chapter is a section -named :ref:`overriding_resources_section`. This section of that -chapter describes in detail how to override package resources with -other resources by using :term:`ZCML` ``<resource>`` declarations. Add -such ``<resource>`` declarations to your override package's -``configure.zcml`` to perform overrides. - -.. index:: - single: ZCML inclusion - -Dealing With ZCML Inclusions ----------------------------- - -Sometimes it's possible to include only certain ZCML files from an -application that contain only the registrations you really need, -omitting others. But sometimes it's not. For brute force purposes, -when you're getting ``view`` or ``route`` registrations that you don't -actually want in your overridden application, it's always appropriate -to just *not include* any ZCML file from the overridden application. -Instead, just cut and paste the entire contents of the -``configure.zcml`` (and any ZCML file included by the overridden -application's ``configure.zcml``) into your own package and omit the -``<include package=""/>`` ZCML declaration in the overriding package's -``configure.zcml``. - +Overriding Assets +~~~~~~~~~~~~~~~~~ +Assets are files on the filesystem that are accessible within a Python +*package*. An entire chapter is devoted to resources: :ref:`assets_chapter`. +Within this chapter is a section named :ref:`overriding_assets_section`. +This section of that chapter describes in detail how to override package +resources with other resources by using the +:meth:`pyramid.config.Configurator.override_asset` method. Add such +``override_asset`` calls to your override package's ``__init__.py`` to +perform overrides. diff --git a/docs/narr/flash.rst b/docs/narr/flash.rst index 331793533..71c6cf305 100644 --- a/docs/narr/flash.rst +++ b/docs/narr/flash.rst @@ -11,113 +11,101 @@ factory` as described in :ref:`using_the_default_session_factory` or Flash messaging has two main uses: to display a status message only once to the user after performing an internal redirect, and to allow generic code to log messages for single-time display without having direct access to an HTML -template. The user interface consists of two methods of the :term:`session` -object. +template. The user interface consists of a number of methods of the +:term:`session` object. Using the ``session.flash`` Method ---------------------------------- -To add a message to a flash queue, use a session object's ``flash`` method: +To add a message to a flash message queue, use a session object's ``flash`` +method: .. code-block:: python :linenos: request.session.flash('mymessage') -The ``.flash`` method appends a message to the queue, creating the queue if -necessary. The message is not modified in any way. +The ``.flash`` method appends a message to a flash queue, creating the queue +if necessary. -The ``category`` argument names a category or level. The library defines -several default category names: ``debug``, ``info``, ``success``, ``warning`` -and ``error``. The default category level is ``info``. +``.flash`` accepts three arguments: -The ``queue_name`` argument allows you to define multiple message -queues. This can be used to display different kinds of messages in different -places on a page. You cam pass any name for your queue, but it must be a -string. The default value is the empty string, which chooses the default -queue. Each queue is independent, and can be popped by ``unflash`` -separately. +.. method:: flash(message, queue='', allow_duplicate=True) -Constant names for flash message category names are importable from the -:mod:`pyramid.flash` module as ``DEBUG``, ``INFO``, ``SUCCESS``, ``WARNING`` -and ``ERROR``, which respectively name ``debug``, ``info``, ``success``, -``warning`` and ``error`` strings. For example you can do this: +The ``message`` argument is required. It represents a message you wish to +later display to a user. It is usually a string but the ``message`` you +provide is not modified in any way. -.. code-block:: python +The ``queue`` argument allows you to choose a queue to which to append the +message you provide. This can be used to push different kinds of messages +into flash storage for later display in different places on a page. You can +pass any name for your queue, but it must be a string. The default value is +the empty string, which chooses the default queue. Each queue is independent, +and can be popped by ``pop_flash`` or examined via ``peek_flash`` separately. +``queue`` defaults to the empty string. The empty string represents the +default flash message queue. - from pyramid import flash - request.session.flash(msg, flash.DEBUG) +.. code-block:: python -Or you can use the literal name ``debug``: + request.session.flash(msg, 'myappsqueue') -.. code-block:: python +The ``allow_duplicate`` argument defaults to ``True``. If this is +``False``, if you attempt to add a message to a queue which is already +present in the queue, it will not be added. - request.session.flash(msg, 'debug') +Using the ``session.pop_flash`` Method +-------------------------------------- -Both examples do the same thing. The meanings of flash category names are -detailed in :mod:`pyramid.flash`. +Once one or more messages have been added to a flash queue by the +``session.flash`` API, the ``session.pop_flash`` API can be used to pop that +queue and return it for use. To pop a particular queue of messages from the flash object, use the session -object's ``unflash`` method. +object's ``pop_flash`` method. + +.. method:: pop_flash(queue='') .. code-block:: python :linenos: - >>> request.session.flash('info message', 'info') - >>> messages = request.session.unflash() - >>> messages['info'] + >>> request.session.flash('info message') + >>> request.session.pop_flash() ['info message'] -Using the ``session.unflash`` Method ------------------------------------- - -Once one or more messages has been added to a flash queue by the -``session.flash`` API, the ``session.unflash`` API can be used to pop that -queue and return it for use. - -For example some code that runs in a view callable might call the -``session.flash`` API: +Calling ``session.pop_flash()`` again like above without a corresponding call +to ``session.flash`` will return an empty list, because the queue has already +been popped. .. code-block:: python :linenos: - request.session.flash('mymessage') - -A corresponding ``session.unflash`` might be called on a subsequent request: + >>> request.session.flash('info message') + >>> request.session.pop_flash() + ['info message'] + >>> request.session.pop_flash() + [] -.. code-block:: python - :linenos: +The object returned from ``pop_flash`` is a list. - messages = request.session.unflash() +Using the ``session.pop_flash`` Method +-------------------------------------- -Calling ``session.unflash`` again like above without a corresponding call to -``session.flash`` will return an empty ``messages`` object, because the queue -has already been popped. +Once one or more messages has been added to a flash queue by the +``session.flash`` API, the ``session.peek_flash`` API can be used to "peek" +at that queue. Unlike ``session.pop_flash``, the queue is not popped from +flash storage. -The ``messages`` object returned from ``unflash`` is a dictionary-like -object. Its keys are category names, and its values are sequences of -strings. For ease of use, the dict-like object returned by ``unflash`` isn't -a "plain" dict: it's an object which has several helper methods, each named -after a particular flash category level. These methods return all messages -related to the category name: +.. method:: peek_flash(queue='') .. code-block:: python :linenos: - >>> request.session.flash('debug message', 'debug') - >>> request.session.flash('info message', 'info') - >>> messages = request.session.unflash() - >>> info_messages = messages.debug() - ['debug message'] - >>> info_messages = messages.info() + >>> request.session.flash('info message') + >>> request.session.peek_flash() ['info message'] - -The full API of the ``messages`` object returned by ``unflash`` is documented -in :class:`pyramid.interfaces.IFlashMessages`. - -.. The ``ignore_duplicate`` flag tells whether to suppress duplicate -.. messages. If true, and another message with identical text exists in the -.. queue, don't add the new message. But if the existing message has a -.. different category than the new message, change its category to match the -.. new message. - + >>> request.session.peek_flash() + ['info message'] + >>> request.session.pop_flash() + ['info message'] + >>> request.session.peek_flash() + [] diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 006f5d5cb..2917b5254 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -3,8 +3,8 @@ Using Hooks =========== -"Hooks" can be used to influence the behavior of the :app:`Pyramid` -framework in various ways. +"Hooks" can be used to influence the behavior of the :app:`Pyramid` framework +in various ways. .. index:: single: not found view @@ -14,61 +14,38 @@ framework in various ways. Changing the Not Found View --------------------------- -When :app:`Pyramid` can't map a URL to view code, it invokes a -:term:`not found view`, which is a :term:`view callable`. A default -notfound view exists. The default not found view can be overridden -through application configuration. This override can be done via -:term:`imperative configuration` or :term:`ZCML`. +When :app:`Pyramid` can't map a URL to view code, it invokes a :term:`not +found view`, which is a :term:`view callable`. A default notfound view +exists. The default not found view can be overridden through application +configuration. -The :term:`not found view` callable is a view callable like any other. -The :term:`view configuration` which causes it to be a "not found" -view consists only of naming the :exc:`pyramid.exceptions.NotFound` -class as the ``context`` of the view configuration. +The :term:`not found view` callable is a view callable like any other. The +:term:`view configuration` which causes it to be a "not found" view consists +only of naming the :exc:`pyramid.exceptions.NotFound` class as the +``context`` of the view configuration. -.. topic:: Using Imperative Configuration +If your application uses :term:`imperative configuration`, you can replace +the Not Found view by using the :meth:`pyramid.config.Configurator.add_view` +method to register an "exception view": - If your application uses :term:`imperative configuration`, you can - replace the Not Found view by using the - :meth:`pyramid.config.Configurator.add_view` method to - register an "exception view": - - .. code-block:: python - :linenos: - - from pyramid.exceptions import NotFound - from helloworld.views import notfound_view - config.add_view(notfound_view, context=NotFound) - - Replace ``helloworld.views.notfound_view`` with a reference to the - Python :term:`view callable` you want to use to represent the Not - Found view. - -.. topic:: Using ZCML - - If your application uses :term:`ZCML`, you can replace the Not Found - view by placing something like the following ZCML in your - ``configure.zcml`` file. - - .. code-block:: xml - :linenos: +.. code-block:: python + :linenos: - <view - view="helloworld.views.notfound_view" - context="pyramid.exceptions.NotFound" - /> + from pyramid.exceptions import NotFound + from helloworld.views import notfound_view + config.add_view(notfound_view, context=NotFound) - Replace ``helloworld.views.notfound_view`` with the Python dotted name - to the notfound view you want to use. +Replace ``helloworld.views.notfound_view`` with a reference to the +:term:`view callable` you want to use to represent the Not Found view. -Like any other view, the notfound view must accept at least a -``request`` parameter, or both ``context`` and ``request``. The -``request`` is the current :term:`request` representing the denied -action. The ``context`` (if used in the call signature) will be the -instance of the :exc:`pyramid.exceptions.NotFound` exception that -caused the view to be called. +Like any other view, the notfound view must accept at least a ``request`` +parameter, or both ``context`` and ``request``. The ``request`` is the +current :term:`request` representing the denied action. The ``context`` (if +used in the call signature) will be the instance of the +:exc:`pyramid.exceptions.NotFound` exception that caused the view to be +called. -Here's some sample code that implements a minimal NotFound view -callable: +Here's some sample code that implements a minimal NotFound view callable: .. code-block:: python :linenos: @@ -93,6 +70,9 @@ callable: :exc:`pyramid.exceptions.NotFound` exception instance. If available, the resource context will still be available as ``request.context``. +For information about how to configure a not found view via :term:`ZCML`, see +:ref:`notfound_zcml`. + .. index:: single: forbidden view @@ -101,59 +81,36 @@ callable: Changing the Forbidden View --------------------------- -When :app:`Pyramid` can't authorize execution of a view based on -the :term:`authorization policy` in use, it invokes a :term:`forbidden -view`. The default forbidden response has a 401 status code and is -very plain, but the view which generates it can be overridden as -necessary using either :term:`imperative configuration` or -:term:`ZCML`. - -The :term:`forbidden view` callable is a view callable like any other. -The :term:`view configuration` which causes it to be a "not found" -view consists only of naming the :exc:`pyramid.exceptions.Forbidden` -class as the ``context`` of the view configuration. - -.. topic:: Using Imperative Configuration - - If your application uses :term:`imperative configuration`, you can - replace the Forbidden view by using the - :meth:`pyramid.config.Configurator.add_view` method to - register an "exception view": - - .. code-block:: python - :linenos: - - from helloworld.views import forbidden_view - from pyramid.exceptions import Forbidden - config.add_view(forbidden_view, context=Forbidden) - - Replace ``helloworld.views.forbidden_view`` with a reference to the - Python :term:`view callable` you want to use to represent the - Forbidden view. +When :app:`Pyramid` can't authorize execution of a view based on the +:term:`authorization policy` in use, it invokes a :term:`forbidden view`. +The default forbidden response has a 401 status code and is very plain, but +the view which generates it can be overridden as necessary. -.. topic:: Using ZCML +The :term:`forbidden view` callable is a view callable like any other. The +:term:`view configuration` which causes it to be a "not found" view consists +only of naming the :exc:`pyramid.exceptions.Forbidden` class as the +``context`` of the view configuration. - If your application uses :term:`ZCML`, you can replace the - Forbidden view by placing something like the following ZCML in your - ``configure.zcml`` file. +You can replace the forbidden view by using the +:meth:`pyramid.config.Configurator.add_view` method to register an "exception +view": - .. code-block:: xml - :linenos: +.. code-block:: python + :linenos: - <view - view="helloworld.views.notfound_view" - context="pyramid.exceptions.Forbidden" - /> + from helloworld.views import forbidden_view + from pyramid.exceptions import Forbidden + config.add_view(forbidden_view, context=Forbidden) - Replace ``helloworld.views.forbidden_view`` with the Python - dotted name to the forbidden view you want to use. +Replace ``helloworld.views.forbidden_view`` with a reference to the Python +:term:`view callable` you want to use to represent the Forbidden view. -Like any other view, the forbidden view must accept at least a -``request`` parameter, or both ``context`` and ``request``. The -``context`` (available as ``request.context`` if you're using the -request-only view argument pattern) is the context found by the router -when the view invocation was denied. The ``request`` is the current -:term:`request` representing the denied action. +Like any other view, the forbidden view must accept at least a ``request`` +parameter, or both ``context`` and ``request``. The ``context`` (available +as ``request.context`` if you're using the request-only view argument +pattern) is the context found by the router when the view invocation was +denied. The ``request`` is the current :term:`request` representing the +denied action. Here's some sample code that implements a minimal forbidden view: @@ -161,10 +118,10 @@ Here's some sample code that implements a minimal forbidden view: :linenos: from pyramid.views import view_config + from pyramid.response import Response - @view_config(renderer='templates/login_form.pt') def forbidden_view(request): - return {} + return Response('forbidden') .. note:: When a forbidden view callable is invoked, it is passed a :term:`request`. The ``exception`` attribute of the request will @@ -181,164 +138,26 @@ Here's some sample code that implements a minimal forbidden view: an alternate forbidden view. For example, it would make sense to return a response with a ``403 Forbidden`` status code. -.. index:: - single: traverser - -.. _changing_the_traverser: - -Changing the Traverser ----------------------- - -The default :term:`traversal` algorithm that :app:`Pyramid` uses is -explained in :ref:`traversal_algorithm`. Though it is rarely -necessary, this default algorithm can be swapped out selectively for a -different traversal pattern via configuration. - -Use an ``adapter`` stanza in your application's ``configure.zcml`` to -change the default traverser: - -.. code-block:: xml - :linenos: - - <adapter - factory="myapp.traversal.Traverser" - provides="pyramid.interfaces.ITraverser" - for="*" - /> - -In the example above, ``myapp.traversal.Traverser`` is assumed to be -a class that implements the following interface: - -.. code-block:: python - :linenos: - - class Traverser(object): - def __init__(self, root): - """ Accept the root object returned from the root factory """ - - def __call__(self, request): - """ Return a dictionary with (at least) the keys ``root``, - ``context``, ``view_name``, ``subpath``, ``traversed``, - ``virtual_root``, and ``virtual_root_path``. These values are - typically the result of a resource tree traversal. ``root`` - is the physical root object, ``context`` will be a resource - object, ``view_name`` will be the view name used (a Unicode - name), ``subpath`` will be a sequence of Unicode names that - followed the view name but were not traversed, ``traversed`` - will be a sequence of Unicode names that were traversed - (including the virtual root path, if any) ``virtual_root`` - will be a resource object representing the virtual root (or the - physical root if traversal was not performed), and - ``virtual_root_path`` will be a sequence representing the - virtual root path (a sequence of Unicode names) or None if - traversal was not performed. - - Extra keys for special purpose functionality can be added as - necessary. - - All values returned in the dictionary will be made available - as attributes of the ``request`` object. - """ - -More than one traversal algorithm can be active at the same time. For -instance, if your :term:`root factory` returns more than one type of -object conditionally, you could claim that an alternate traverser -adapter is ``for`` only one particular class or interface. When the -root factory returned an object that implemented that class or -interface, a custom traverser would be used. Otherwise, the default -traverser would be used. For example: - -.. code-block:: xml - :linenos: - - <adapter - factory="myapp.traversal.Traverser" - provides="pyramid.interfaces.ITraverser" - for="myapp.resources.MyRoot" - /> - -If the above stanza was added to a ``configure.zcml`` file, -:app:`Pyramid` would use the ``myapp.traversal.Traverser`` only -when the application :term:`root factory` returned an instance of the -``myapp.resources.MyRoot`` object. Otherwise it would use the default -:app:`Pyramid` traverser to do traversal. +For information about how to configure a forbidden view via :term:`ZCML`, see +:ref:`forbidden_zcml`. .. index:: - single: url generator - -Changing How :mod:`pyramid.url.resource_url` Generates a URL ------------------------------------------------------------- - -When you add a traverser as described in :ref:`changing_the_traverser`, it's -often convenient to continue to use the :func:`pyramid.url.resource_url` API. -However, since the way traversal is done will have been modified, the URLs it -generates by default may be incorrect. - -If you've added a traverser, you can change how -:func:`pyramid.url.resource_url` generates a URL for a specific type of -resource by adding an adapter stanza for -:class:`pyramid.interfaces.IContextURL` to your application's -``configure.zcml``: - -.. code-block:: xml - :linenos: - - <adapter - factory="myapp.traversal.URLGenerator" - provides="pyramid.interfaces.IContextURL" - for="myapp.resources.MyRoot *" - /> - -In the above example, the ``myapp.traversal.URLGenerator`` class will -be used to provide services to :func:`pyramid.url.resource_url` any -time the :term:`context` passed to ``resource_url`` is of class -``myapp.resources.MyRoot``. The asterisk following represents the type -of interface that must be possessed by the :term:`request` (in this -case, any interface, represented by asterisk). - -The API that must be implemented by a class that provides -:class:`pyramid.interfaces.IContextURL` is as follows: - -.. code-block:: python - :linenos: - - from zope.interface import Interface - - class IContextURL(Interface): - """ An adapter which deals with URLs related to a context. - """ - def __init__(self, context, request): - """ Accept the context and request """ - - def virtual_root(self): - """ Return the virtual root object related to a request and the - current context""" - - def __call__(self): - """ Return a URL that points to the context """ - -The default context URL generator is available for perusal as the -class :class:`pyramid.traversal.TraversalContextURL` in the -`traversal module -<http://github.com/Pylons/pyramid/blob/master/pyramid/traversal.py>`_ of -the :term:`Pylons` GitHub Pyramid repository. + single: request factory .. _changing_the_request_factory: Changing the Request Factory ---------------------------- -Whenever :app:`Pyramid` handles a :term:`WSGI` request, it creates -a :term:`request` object based on the WSGI environment it has been -passed. By default, an instance of the -:class:`pyramid.request.Request` class is created to represent the -request object. +Whenever :app:`Pyramid` handles a :term:`WSGI` request, it creates a +:term:`request` object based on the WSGI environment it has been passed. By +default, an instance of the :class:`pyramid.request.Request` class is created +to represent the request object. -The class (aka "factory") that :app:`Pyramid` uses to create a -request object instance can be changed by passing a -``request_factory`` argument to the constructor of the -:term:`configurator`. This argument can be either a callable or a -:term:`dotted Python name` representing a callable. +The class (aka "factory") that :app:`Pyramid` uses to create a request object +instance can be changed by passing a ``request_factory`` argument to the +constructor of the :term:`configurator`. This argument can be either a +callable or a :term:`dotted Python name` representing a callable. .. code-block:: python :linenos: @@ -350,24 +169,9 @@ request object instance can be changed by passing a config = Configurator(request_factory=MyRequest) -The same ``MyRequest`` class can alternately be registered via ZCML as -a request factory through the use of the ZCML ``utility`` directive. -In the below, we assume it lives in a package named -``mypackage.mymodule``. - -.. code-block:: xml - :linenos: - - <utility - component="mypackage.mymodule.MyRequest" - provides="pyramid.interfaces.IRequestFactory" - /> - -Lastly, if you're doing imperative configuration, and you'd rather do -it after you've already constructed a :term:`configurator` it can also -be registered via the -:meth:`pyramid.config.Configurator.set_request_factory` -method: +If you're doing imperative configuration, and you'd rather do it after you've +already constructed a :term:`configurator` it can also be registered via the +:meth:`pyramid.config.Configurator.set_request_factory` method: .. code-block:: python :linenos: @@ -381,26 +185,29 @@ method: config = Configurator() config.set_request_factory(MyRequest) +To use ZCML for the same purpose, see :ref:`changing_request_factory_zcml`. + +.. index:: + single: renderer globals + .. _adding_renderer_globals: Adding Renderer Globals ----------------------- -Whenever :app:`Pyramid` handles a request to perform a rendering -(after a view with a ``renderer=`` configuration attribute is invoked, -or when the any of the methods beginning with ``render`` within the -:mod:`pyramid.renderers` module are called), *renderer globals* can -be injected into the *system* values sent to the renderer. By -default, no renderer globals are injected, and the "bare" system -values (such as ``request``, ``context``, and ``renderer_name``) are -the only values present in the system dictionary passed to every -renderer. - -A callback that :app:`Pyramid` will call every time a renderer is -invoked can be added by passing a ``renderer_globals_factory`` -argument to the constructor of the :term:`configurator`. This -callback can either be a callable object or a :term:`dotted Python -name` representing such a callable. +Whenever :app:`Pyramid` handles a request to perform a rendering (after a +view with a ``renderer=`` configuration attribute is invoked, or when the any +of the methods beginning with ``render`` within the :mod:`pyramid.renderers` +module are called), *renderer globals* can be injected into the *system* +values sent to the renderer. By default, no renderer globals are injected, +and the "bare" system values (such as ``request``, ``context``, and +``renderer_name``) are the only values present in the system dictionary +passed to every renderer. + +A callback that :app:`Pyramid` will call every time a renderer is invoked can +be added by passing a ``renderer_globals_factory`` argument to the +constructor of the :term:`configurator`. This callback can either be a +callable object or a :term:`dotted Python name` representing such a callable. .. code-block:: python :linenos: @@ -411,30 +218,15 @@ name` representing such a callable. config = Configurator( renderer_globals_factory=renderer_globals_factory) -Such a callback must accept a single positional argument (notionally -named ``system``) which will contain the original system values. It -must return a dictionary of values that will be merged into the system -dictionary. See :ref:`renderer_system_values` for discription of the -values present in the system dictionary. +Such a callback must accept a single positional argument (notionally named +``system``) which will contain the original system values. It must return a +dictionary of values that will be merged into the system dictionary. See +:ref:`renderer_system_values` for discription of the values present in the +system dictionary. -A renderer globals factory can alternately be registered via ZCML as a -through the use of the ZCML ``utility`` directive. In the below, we -assume a ``renderers_globals_factory`` function lives in a package -named ``mypackage.mymodule``. - -.. code-block:: xml - :linenos: - - <utility - component="mypackage.mymodule.renderer_globals_factory" - provides="pyramid.interfaces.IRendererGlobalsFactory" - /> - -Lastly, if you're doing imperative configuration, and you'd rather do -it after you've already constructed a :term:`configurator` it can also -be registered via the -:meth:`pyramid.config.Configurator.set_renderer_globals_factory` -method: +If you're doing imperative configuration, and you'd rather do it after you've +already constructed a :term:`configurator` it can also be registered via the +:meth:`pyramid.config.Configurator.set_renderer_globals_factory` method: .. code-block:: python :linenos: @@ -450,6 +242,12 @@ method: Another mechanism which allows event subscribers to add renderer global values exists in :ref:`beforerender_event`. +If you'd rather ZCML to register a renderer globals factory, see +:ref:`adding_renderer_globals_zcml`. + +.. index:: + single: before render event + .. _beforerender_event: Using The Before Render Event @@ -472,8 +270,8 @@ that can be used for this purpose. For example: An object of this type is sent as an event just before a :term:`renderer` is invoked (but *after* the application-level renderer globals factory added via -:class:`pyramid.config.Configurator.set_renderer_globals_factory`, if -any, has injected its own keys into the renderer globals dictionary). +:class:`pyramid.config.Configurator.set_renderer_globals_factory`, if any, +has injected its own keys into the renderer globals dictionary). If a subscriber attempts to add a key that already exist in the renderer globals dictionary, a :exc:`KeyError` is raised. This limitation is enforced @@ -488,21 +286,24 @@ interface at :class:`pyramid.interfaces.IBeforeRender`. Another mechanism which allows event subscribers more control when adding renderer global values exists in :ref:`adding_renderer_globals`. +.. index:: + single: response callback + .. _using_response_callbacks: Using Response Callbacks ------------------------ -Unlike many other web frameworks, :app:`Pyramid` does not eagerly -create a global response object. Adding a :term:`response callback` -allows an application to register an action to be performed against a -response object once it is created, usually in order to mutate it. +Unlike many other web frameworks, :app:`Pyramid` does not eagerly create a +global response object. Adding a :term:`response callback` allows an +application to register an action to be performed against a response object +once it is created, usually in order to mutate it. -The :meth:`pyramid.request.Request.add_response_callback` method is -used to register a response callback. +The :meth:`pyramid.request.Request.add_response_callback` method is used to +register a response callback. -A response callback is a callable which accepts two positional -parameters: ``request`` and ``response``. For example: +A response callback is a callable which accepts two positional parameters: +``request`` and ``response``. For example: .. code-block:: python :linenos: @@ -515,11 +316,11 @@ parameters: ``request`` and ``response``. For example: No response callback is called if an unhandled exception happens in application code, or if the response object returned by a :term:`view -callable` is invalid. Response callbacks *are*, however, invoked when -a :term:`exception view` is rendered successfully: in such a case, the -:attr:`request.exception` attribute of the request when it enters a -response callback will be an exception object instead of its default -value of ``None``. +callable` is invalid. Response callbacks *are*, however, invoked when a +:term:`exception view` is rendered successfully: in such a case, the +:attr:`request.exception` attribute of the request when it enters a response +callback will be an exception object instead of its default value of +``None``. Response callbacks are called in the order they're added (first-to-most-recently-added). All response callbacks are called *after* @@ -532,18 +333,21 @@ response callback to happen as the result of *every* request, you must re-register the callback into every new request (perhaps within a subscriber of a :class:`pyramid.events.NewRequest` event). +.. index:: + single: finished callback + .. _using_finished_callbacks: Using Finished Callbacks ------------------------ -A :term:`finished callback` is a function that will be called -unconditionally by the :app:`Pyramid` :term:`router` at the very -end of request processing. A finished callback can be used to perform -an action at the end of a request unconditionally. +A :term:`finished callback` is a function that will be called unconditionally +by the :app:`Pyramid` :term:`router` at the very end of request processing. +A finished callback can be used to perform an action at the end of a request +unconditionally. -The :meth:`pyramid.request.Request.add_finished_callback` method is -used to register a finished callback. +The :meth:`pyramid.request.Request.add_finished_callback` method is used to +register a finished callback. A finished callback is a callable which accepts a single positional parameter: ``request``. For example: @@ -563,25 +367,24 @@ parameter: ``request``. For example: Finished callbacks are called in the order they're added ( first- to most-recently- added). Finished callbacks (unlike a :term:`response -callback`) are *always* called, even if an exception happens in -application code that prevents a response from being generated. - -The set of finished callbacks associated with a request are called -*very late* in the processing of that request; they are essentially -the very last thing called by the :term:`router` before a request -"ends". They are called after response processing has already occurred -in a top-level ``finally:`` block within the router request processing -code. As a result, mutations performed to the ``request`` provided to -a finished callback will have no meaningful effect, because response -processing will have already occurred, and the request's scope will -expire almost immediately after all finished callbacks have been -processed. +callback`) are *always* called, even if an exception happens in application +code that prevents a response from being generated. + +The set of finished callbacks associated with a request are called *very +late* in the processing of that request; they are essentially the very last +thing called by the :term:`router` before a request "ends". They are called +after response processing has already occurred in a top-level ``finally:`` +block within the router request processing code. As a result, mutations +performed to the ``request`` provided to a finished callback will have no +meaningful effect, because response processing will have already occurred, +and the request's scope will expire almost immediately after all finished +callbacks have been processed. It is often necessary to tell whether an exception occurred within -:term:`view callable` code from within a finished callback: in such a -case, the :attr:`request.exception` attribute of the request when it -enters a response callback will be an exception object instead of its -default value of ``None``. +:term:`view callable` code from within a finished callback: in such a case, +the :attr:`request.exception` attribute of the request when it enters a +response callback will be an exception object instead of its default value of +``None``. Errors raised by finished callbacks are not handled specially. They will be propagated to the caller of the :app:`Pyramid` router @@ -592,28 +395,175 @@ finished callback to happen as the result of *every* request, you must re-register the callback into every new request (perhaps within a subscriber of a :class:`pyramid.events.NewRequest` event). +.. index:: + single: traverser + +.. _changing_the_traverser: + +Changing the Traverser +---------------------- + +The default :term:`traversal` algorithm that :app:`Pyramid` uses is explained +in :ref:`traversal_algorithm`. Though it is rarely necessary, this default +algorithm can be swapped out selectively for a different traversal pattern +via configuration. + +.. code-block:: python + :linenos: + + from pyramid.interfaces import ITraverser + from zope.interface import Interface + from myapp.traversal import Traverser + + config.registry.registerAdapter(Traverser, (Interface,), ITraverser) + +In the example above, ``myapp.traversal.Traverser`` is assumed to be a class +that implements the following interface: + +.. code-block:: python + :linenos: + + class Traverser(object): + def __init__(self, root): + """ Accept the root object returned from the root factory """ + + def __call__(self, request): + """ Return a dictionary with (at least) the keys ``root``, + ``context``, ``view_name``, ``subpath``, ``traversed``, + ``virtual_root``, and ``virtual_root_path``. These values are + typically the result of a resource tree traversal. ``root`` + is the physical root object, ``context`` will be a resource + object, ``view_name`` will be the view name used (a Unicode + name), ``subpath`` will be a sequence of Unicode names that + followed the view name but were not traversed, ``traversed`` + will be a sequence of Unicode names that were traversed + (including the virtual root path, if any) ``virtual_root`` + will be a resource object representing the virtual root (or the + physical root if traversal was not performed), and + ``virtual_root_path`` will be a sequence representing the + virtual root path (a sequence of Unicode names) or None if + traversal was not performed. + + Extra keys for special purpose functionality can be added as + necessary. + + All values returned in the dictionary will be made available + as attributes of the ``request`` object. + """ + +More than one traversal algorithm can be active at the same time. For +instance, if your :term:`root factory` returns more than one type of object +conditionally, you could claim that an alternate traverser adapter is ``for`` +only one particular class or interface. When the root factory returned an +object that implemented that class or interface, a custom traverser would be +used. Otherwise, the default traverser would be used. For example: + +.. code-block:: python + :linenos: + + from pyramid.interfaces import ITraverser + from zope.interface import Interface + from myapp.traversal import Traverser + from myapp.resources import MyRoot + + config.registry.registerAdapter(Traverser, (MyRoot,), ITraverser) + +If the above stanza was added to a Pyramid ``__init__.py`` file's ``main`` +function, :app:`Pyramid` would use the ``myapp.traversal.Traverser`` only +when the application :term:`root factory` returned an instance of the +``myapp.resources.MyRoot`` object. Otherwise it would use the default +:app:`Pyramid` traverser to do traversal. + +For information about how to configure an alternate traverser via +:term:`ZCML`, see :ref:`changing_traverser_zcml`. + +.. index:: + single: url generator + +.. _changing_resource_url: + +Changing How :mod:`pyramid.url.resource_url` Generates a URL +------------------------------------------------------------ + +When you add a traverser as described in :ref:`changing_the_traverser`, it's +often convenient to continue to use the :func:`pyramid.url.resource_url` API. +However, since the way traversal is done will have been modified, the URLs it +generates by default may be incorrect. + +If you've added a traverser, you can change how +:func:`pyramid.url.resource_url` generates a URL for a specific type of +resource by adding a registerAdapter call for +:class:`pyramid.interfaces.IContextURL` to your application: + +.. code-block:: python + :linenos: + + from pyramid.interfaces import ITraverser + from zope.interface import Interface + from myapp.traversal import URLGenerator + from myapp.resources import MyRoot + + config.registry.registerAdapter(URLGenerator, (MyRoot, Interface), + IContextURL) + +In the above example, the ``myapp.traversal.URLGenerator`` class will be used +to provide services to :func:`pyramid.url.resource_url` any time the +:term:`context` passed to ``resource_url`` is of class +``myapp.resources.MyRoot``. The second argument in the ``(MyRoot, +Interface)`` tuple represents the type of interface that must be possessed by +the :term:`request` (in this case, any interface, represented by +``zope.interface.Interface``). + +The API that must be implemented by a class that provides +:class:`pyramid.interfaces.IContextURL` is as follows: + +.. code-block:: python + :linenos: + + from zope.interface import Interface + + class IContextURL(Interface): + """ An adapter which deals with URLs related to a context. + """ + def __init__(self, context, request): + """ Accept the context and request """ + + def virtual_root(self): + """ Return the virtual root object related to a request and the + current context""" + + def __call__(self): + """ Return a URL that points to the context """ + +The default context URL generator is available for perusal as the class +:class:`pyramid.traversal.TraversalContextURL` in the `traversal module +<http://github.com/Pylons/pyramid/blob/master/pyramid/traversal.py>`_ of the +:term:`Pylons` GitHub Pyramid repository. + +.. index:: + single: configuration decorator + .. _registering_configuration_decorators: Registering Configuration Decorators ------------------------------------ Decorators such as :class:`pyramid.view.view_config` don't change the -behavior of the functions or classes they're decorating. Instead, -when a :term:`scan` is performed, a modified version of the function -or class is registered with :app:`Pyramid`. - -You may wish to have your own decorators that offer such -behaviour. This is possible by using the :term:`Venusian` package in -the same way that it is used by :app:`Pyramid`. - -By way of example, let's suppose you want to write a decorator that -registers the function it wraps with a :term:`Zope Component -Architecture` "utility" within the :term:`application registry` -provided by :app:`Pyramid`. The application registry and the -utility inside the registry is likely only to be available once your -application's configuration is at least partially completed. A normal -decorator would fail as it would be executed before the configuration -had even begun. +behavior of the functions or classes they're decorating. Instead, when a +:term:`scan` is performed, a modified version of the function or class is +registered with :app:`Pyramid`. + +You may wish to have your own decorators that offer such behaviour. This is +possible by using the :term:`Venusian` package in the same way that it is +used by :app:`Pyramid`. + +By way of example, let's suppose you want to write a decorator that registers +the function it wraps with a :term:`Zope Component Architecture` "utility" +within the :term:`application registry` provided by :app:`Pyramid`. The +application registry and the utility inside the registry is likely only to be +available once your application's configuration is at least partially +completed. A normal decorator would fail as it would be executed before the +configuration had even begun. However, using :term:`Venusian`, the decorator could be written as follows: @@ -671,10 +621,8 @@ performed, enabling you to set up the utility in advance: if __name__ == '__main__': config = Configurator() - config.begin() config.registry.registerUtility(UtilityImplementation()) config.scan() - config.end() app = config.make_wsgi_app() serve(app, host='0.0.0.0') diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index d8cc5cb1c..c2a5b8ce7 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -881,11 +881,8 @@ application startup. For example: :linenos: from pyramid.config import Configurator - config.begin() config.add_translation_dirs('my.application:locale/', 'another.application:locale/') - # ... - config.end() A message catalog in a translation directory added via :meth:`pyramid.config.Configurator.add_translation_dirs` @@ -1020,9 +1017,7 @@ For example: from pyramid.config import Configurator config = Configurator() - config.begin() config.set_locale_negotiator(my_locale_negotiator) - config.end() .. note:: You can also add a custom locale negotiator via ZCML. See :ref:`zcml_adding_a_locale_negotiator` diff --git a/docs/narr/project.rst b/docs/narr/project.rst index a096d8ca4..55a2711f3 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -64,8 +64,7 @@ The included templates are these: :term:`ZCML` (declarative configuration). ``pyramid_zodb`` - URL mapping via :term:`traversal` and persistence via :term:`ZODB`, using - :term:`ZCML` (declarative configuration). + URL mapping via :term:`traversal` and persistence via :term:`ZODB`. ``pyramid_routesalchemy`` URL mapping via :term:`URL dispatch` and persistence via @@ -257,6 +256,8 @@ create`` -generated project. Within a project generated by the single: IPython single: paster pshell +.. _interactive_shell: + The Interactive Shell --------------------- diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst index f90b1eb12..8cf2cead2 100644 --- a/docs/narr/resources.rst +++ b/docs/narr/resources.rst @@ -324,6 +324,11 @@ would have been what was returned anyway, but your code can perform arbitrary logic as necessary. For example, your code may wish to override the hostname or port number of the generated URL. +Note that the URL generated by ``__resource_url__`` should be fully +qualified, should end in a slash, and should not contain any query string or +anchor elements (only path elements) to work best with +:func:`pyramid.url.resource_url`. + Generating the Path To a Resource --------------------------------- @@ -399,7 +404,7 @@ Obtaining the Lineage of a Resource ----------------------------------- :func:`pyramid.location.lineage` returns a generator representing the -:term:`lineage` of the :term:`location` aware:term:`resource` object. +:term:`lineage` of the :term:`location` aware :term:`resource` object. The :func:`~pyramid.location.lineage` function returns the resource it is passed, then each parent of the resource, in order. For example, if the @@ -528,7 +533,7 @@ declares that the blog entry implements an :term:`interface`. implements(IBlogEntry) def __init__(self, title, body, author): self.title = title - self.body = body + self.body = body self.author = author self.created = datetime.datetime.now() @@ -563,7 +568,7 @@ To do so, use the :func:`zope.interface.directlyProvides` function: class BlogEntry(object): def __init__(self, title, body, author): self.title = title - self.body = body + self.body = body self.author = author self.created = datetime.datetime.now() @@ -591,7 +596,7 @@ the :func:`zope.interface.alsoProvides` function: class BlogEntry(object): def __init__(self, title, body, author): self.title = title - self.body = body + self.body = body self.author = author self.created = datetime.datetime.now() diff --git a/docs/narr/static.rst b/docs/narr/static.rst deleted file mode 100644 index 53564a632..000000000 --- a/docs/narr/static.rst +++ /dev/null @@ -1,258 +0,0 @@ -Static Assets -============= - -:app:`Pyramid` makes it possible to serve up static asset files from a -directory on a filesystem. This chapter describes how to configure -:app:`Pyramid` to do so. - -.. index:: - single: add_static_view - -.. _static_assets_section: - -Serving Static Assets ---------------------- - -Use the :meth:`pyramid.config.Configurator.add_static_view` to instruct -:app:`Pyramid` to serve static assets such as JavaScript and CSS files. This -mechanism makes static files available at a name relative to the application -root URL, e.g. ``/static``. - -Note that the ``path`` provided to -:meth:`pyramid.config.Configurator.add_static_view` may be a fully qualified -:term:`asset specification`, or an *absolute path*. - -Here's an example of a use of -:meth:`pyramid.config.Configurator.add_static_view` that will serve -files up under the ``/static`` URL from the ``/var/www/static`` directory of -the computer which runs the :app:`Pyramid` application using an absolute -path. - -.. code-block:: python - :linenos: - - # config is an instance of pyramid.config.Configurator - config.add_static_view(name='static', path='/var/www/static') - -Here's an example of :meth:`pyramid.config.Configurator.add_static_view` that -will serve files up under the ``/static`` URL from the ``a/b/c/static`` -directory of the Python package named ``some_package`` using a fully -qualified :term:`asset specification`. - -.. code-block:: python - :linenos: - - # config is an instance of pyramid.config.Configurator - config.add_static_view(name='static', path='some_package:a/b/c/static') - -Whether you use for ``path`` a fully qualified asset specification, or an -absolute path, when you place your static files on the filesystem in the -directory represented as the ``path`` of the directive, you will then be able -to view the static files in this directory via a browser at URLs prefixed -with the directive's ``name``. For instance if the ``static`` directive's -``name`` is ``static`` and the static directive's ``path`` is -``/path/to/static``, ``http://localhost:6543/static/foo.js`` will return the -file ``/path/to/static/dir/foo.js``. The static directory may contain -subdirectories recursively, and any subdirectories may hold files; these will -be resolved by the static view as you would expect. - -While the ``path`` argument can be a number of different things, the ``name`` -argument of the call to :meth:`pyramid.config.Configurator.add_static_view` -can also be one of a number of things: a *view name* or a *URL*. The above -examples have shown usage of the ``name`` argument as a view name. When -``name`` is a *URL* (or any string with a slash (``/``) in it), static assets -can be served from an external webserver. In this mode, the ``name`` is used -as the URL prefix when generating a URL using :func:`pyramid.url.static_url`. - -For example, :meth:`pyramid.config.Configurator.add_static_view` may -be fed a ``name`` argument which is ``http://example.com/images``: - -.. code-block:: python - :linenos: - - # config is an instance of pyramid.config.Configurator - config.add_static_view(name='http://example.com/images', - path='mypackage:images') - -Because :meth:`pyramid.config.Configurator.add_static_view` is -provided with a ``name`` argument that is the URL prefix -``http://example.com/images``, subsequent calls to -:func:`pyramid.url.static_url` with paths that start with the ``path`` -argument passed to :meth:`pyramid.config.Configurator.add_static_view` -will generate a URL something like ``http://example.com/images/logo.png``. The -external webserver listening on ``example.com`` must be itself configured to -respond properly to such a request. The :func:`pyramid.url.static_url` API -is discussed in more detail later in this chapter. - -The :ref:`static_directive` ZCML directive offers an declarative equivalent -to :meth:`pyramid.config.Configurator.add_static_view`. Use of the -:ref:`static_directive` ZCML directive is completely equivalent to using -imperative configuration for the same purpose. - -.. note:: - - Using :func:`pyramid.url.static_url` in conjunction with a - :meth:`pyramid.configuration.Configurator.add_static_view` makes it - possible to put static media on a separate webserver during production (if - the ``name`` argument to - :meth:`pyramid.config.Configurator.add_static_view` is a URL), - while keeping static media package-internal and served by the development - webserver during development (if the ``name`` argument to - :meth:`pyramid.config.Configurator.add_static_view` is a view - name). To create such a circumstance, we suggest using the - :attr:`pyramid.registry.Registry.settings` API in conjunction with a - setting in the application ``.ini`` file named ``media_location``. Then - set the value of ``media_location`` to either a view name or a URL - depending on whether the application is being run in development or in - production (use a different `.ini`` file for production than you do for - development). This is just a suggestion for a pattern; any setting name - other than ``media_location`` could be used. - -.. index:: - single: generating static asset urls - single: static asset urls - -.. _generating_static_asset_urls: - -Generating Static Asset URLs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When a :meth:`pyramid.config.Configurator.add_static_view` method is used to -register a static asset directory, a special helper API named -:func:`pyramid.url.static_url` can be used to generate the appropriate URL -for an asset that lives in one of the directories named by the static -registration ``path`` attribute. - -For example, let's assume you create a set of static declarations like so: - -.. code-block:: python - :linenos: - - config.add_static_view(name='static1', path='mypackage:assets/1') - config.add_static_view(name='static2', path='mypackage:assets/2') - -These declarations create URL-accessible directories which have URLs that -begin with ``/static1`` and ``/static2``, respectively. The assets in the -``assets/1`` directory of the ``mypackage`` package are consulted when a user -visits a URL which begins with ``/static1``, and the assets in the -``assets/2`` directory of the ``mypackage`` package are consulted when a user -visits a URL which begins with ``/static2``. - -You needn't generate the URLs to static assets "by hand" in such a -configuration. Instead, use the :func:`pyramid.url.static_url` API to -generate them for you. For example: - -.. code-block:: python - :linenos: - - from pyramid.url import static_url - from pyramid.chameleon_zpt import render_template_to_response - - def my_view(request): - css_url = static_url('mypackage:assets/1/foo.css', request) - js_url = static_url('mypackage:assets/2/foo.js', request) - return render_template_to_response('templates/my_template.pt', - css_url = css_url, - js_url = js_url) - -If the request "application URL" of the running system is -``http://example.com``, the ``css_url`` generated above would be: -``http://example.com/static1/foo.css``. The ``js_url`` generated -above would be ``http://example.com/static2/foo.js``. - -One benefit of using the :func:`pyramid.url.static_url` function rather than -constructing static URLs "by hand" is that if you need to change the ``name`` -of a static URL declaration, the generated URLs will continue to resolve -properly after the rename. - -URLs may also be generated by :func:`pyramid.url.static_url` to static assets -that live *outside* the :app:`Pyramid` application. This will happen when -the :meth:`pyramid.config.Configurator.add_static_view` API associated with -the path fed to :func:`pyramid.url.static_url` is a *URL* instead of a view -name. For example, the ``name`` argument may be ``http://example.com`` while -the the ``path`` given may be ``mypackage:images``: - -.. code-block:: python - :linenos: - - config.add_static_view(name='http://example.com/images', path='mypackage:images') - -Under such a configuration, the URL generated by ``static_url`` for -assets which begin with ``mypackage:images`` will be prefixed with -``http://example.com/images``: - -.. code-block:: python - :linenos: - - static_url('mypackage:images/logo.png', request) - # -> http://example.com/images/logo.png - -.. index:: - single: static assets view - -Advanced: Serving Static Assets Using a View Callable ------------------------------------------------------ - -For more flexibility, static assets can be served by a :term:`view callable` -which you register manually. For example, you may want static assets to only -be available when the :term:`context` is of a particular type, or when -certain request headers are present. - -The :class:`pyramid.view.static` helper class is used to perform this -task. This class creates an object that is capable acting as a :app:`Pyramid` -view callable which serves static assets from a directory. For instance, to -serve files within a directory located on your filesystem at -``/path/to/static/dir`` from the URL path ``/static`` in your application, -create an instance of the :class:`pyramid.view.static` class inside a -``static.py`` file in your application root as below. - -.. ignore-next-block -.. code-block:: python - :linenos: - - from pyramid.view import static - static_view = static('/path/to/static/dir') - -.. note:: the argument to :class:`pyramid.view.static` can also be - a "here-relative" pathname, e.g. ``my/static`` (meaning relative to the - Python package of the module in which the view is being defined). - It can also be a :term:`asset specification` - (e.g. ``anotherpackage:some/subdirectory``). - -Subsequently, you may wire this view up to be accessible as ``/static`` using -the :mod:`pyramid.config.Configurator.add_view` method in your application's -startup code against either the class or interface that represents your root -resource object. - -.. code-block:: python - :linenos: - - config.add_view('mypackage.static.static_view', name='static', - context='mypackage.resources.Root') - -In this case, ``mypackage.resources.Root`` refers to the class of your -:app:`Pyramid` application's resource tree. - -The context argument above limits where the static view is accessible to URL -paths directly under the root object. If you omit the ``context`` argument, -then ``static`` will be accessible as the static view against any resource -object in the resource tree. This will allow ``/static/foo.js`` to work, but -it will also allow for ``/anything/static/foo.js`` too, as long as -``anything`` can be resolved. - -Note that you cannot use the :func:`pyramid.url.static_url` API to generate -URLs against assets made accessible by registering a custom static view. - -.. warning:: - - When adding a static view to your root object, you need to be careful that - there are no resource objects contained in the root with the same key as - the view name (e.g., ``static``). Resource objects take precedence during - traversal, thus such a name collision will cause the resource to "shadow" - your static view. To avoid this issue, and ensure that your root - resource's ``__getitem__`` is never called when a static asset is - requested, you can refer to them unambiguously using the ``@@`` prefix - (goggles) in their URLs. For the above examples you could use - '/@@static/foo.js' instead of '/static/foo.js' to avoid such shadowing. - See :ref:`traversal_chapter` for information about "goggles" (``@@``). - diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 76eca454d..4c601340f 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -1231,6 +1231,44 @@ which you started the application from. For example: See :ref:`environment_chapter` for more information about how, and where to set these values. +.. index:: + pair: routes; printing + single: paster proutes + +Displaying All Application Routes +--------------------------------- + +You can use the ``paster proutes`` command in a terminal window to print a +summary of routes related to your application. Much like the ``paster +pshell`` command (see :ref:`interactive shell`), the ``paster proutes`` +command accepts two arguments. The first argument to ``proutes`` is the path +to your application's ``.ini`` file. The second is the ``app`` section name +inside the ``.ini`` file which points to your application. + +For example: + +.. code-block:: text + :linenos: + + [chrism@thinko MyProject]$ ../bin/paster proutes development.ini MyProject + Name Pattern View + ---- ------- ---- + home / <function my_view> + home2 / <function my_view> + another /another None + static/ static/*subpath <static_view object> + catchall /*subpath <function static_view> + +``paster proutes`` generates a table. The table has three columns: a Name +name column, a Pattern column, and a View column. The items listed in the +Name column are route names, the items listen in the Pattern column are route +patterns, and the items listed in the View column are representations of the +view callable that will be invoked when a request matches the associated +route pattern. The view column may show ``None`` if no associated view +callable could be found. If no routes are configured within your +application, nothing will be printed to the console when ``paster proutes`` +is executed. + References ---------- diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index 062c553b5..1b66ace96 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -2,14 +2,13 @@ Adding Authorization ==================== -Our application currently allows anyone with access to the server to -view, edit, and add pages to our wiki. For purposes of demonstration -we'll change our application to allow people whom are members of a -*group* named ``group:editors`` to add and edit wiki pages but we'll -continue allowing anyone with access to the server to view pages. -:app:`Pyramid` provides facilities for *authorization* and -*authentication*. We'll make use of both features to provide security -to our application. +Our application currently allows anyone with access to the server to view, +edit, and add pages to our wiki. For purposes of demonstration we'll change +our application to allow people whom are members of a *group* named +``group:editors`` to add and edit wiki pages but we'll continue allowing +anyone with access to the server to view pages. :app:`Pyramid` provides +facilities for *authorization* and *authentication*. We'll make use of both +features to provide security to our application. The source code for this tutorial stage can be browsed via `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/ @@ -19,33 +18,29 @@ The source code for this tutorial stage can be browsed via Configuring a ``pyramid`` Authentication Policy -------------------------------------------------- -For any :app:`Pyramid` application to perform authorization, we -need to add a ``security.py`` module and we'll need to change our -:term:`application registry` to add an :term:`authentication policy` -and a :term:`authorization policy`. +For any :app:`Pyramid` application to perform authorization, we need to add a +``security.py`` module and we'll need to change our :term:`application +registry` to add an :term:`authentication policy` and a :term:`authorization +policy`. -Changing ``configure.zcml`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Adding Authentication and Authorization Policies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -We'll change our ``configure.zcml`` file to enable an -``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` to -enable declarative security checking. We'll also add a new view -stanza, which specifies a :term:`forbidden view`. This configures our -login view to show up when :app:`Pyramid` detects that a view -invocation can not be authorized. When you're done, your -``configure.zcml`` will look like so: +We'll change our package's ``__init__.py`` file to enable an +``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` to enable +declarative security checking. When you're done, your ``__init__.py`` will +look like so: -.. literalinclude:: src/authorization/tutorial/configure.zcml +.. literalinclude:: src/authorization/tutorial/__init__.py :linenos: - :language: xml + :language: python -Note that the ``authtktauthenticationpolicy`` tag has two attributes: -``secret`` and ``callback``. ``secret`` is a string representing an -encryption key used by the "authentication ticket" machinery -represented by this policy: it is required. The ``callback`` is a -string, representing a :term:`dotted Python name`, which points at the -``groupfinder`` function in the current directory's ``security.py`` -file. We haven't added that module yet, but we're about to. +Note that the creation of an ``AuthTktAuthenticationPolicy`` requires two +arguments: ``secret`` and ``callback``. ``secret`` is a string representing +an encryption key used by the "authentication ticket" machinery represented +by this policy: it is required. The ``callback`` is a reference to a +``groupfinder`` function in the ``tutorial`` package's ``security.py`` file. +We haven't added that module yet, but we're about to. Adding ``security.py`` ~~~~~~~~~~~~~~~~~~~~~~ @@ -59,14 +54,13 @@ content: :language: python The ``groupfinder`` function defined here is an authorization policy -"callback"; it is a callable that accepts a userid and a request. If -the userid exists in the set of users known by the system, the -callback will return a sequence of group identifiers (or an empty -sequence if the user isn't a member of any groups). If the userid -*does not* exist in the system, the callback will return ``None``. In -a production system this data will most often come from a database, -but here we use "dummy" data to represent user and groups -sources. Note that the ``editor`` user is a member of the +"callback"; it is a callable that accepts a userid and a request. If the +userid exists in the set of users known by the system, the callback will +return a sequence of group identifiers (or an empty sequence if the user +isn't a member of any groups). If the userid *does not* exist in the system, +the callback will return ``None``. In a production system this data will +most often come from a database, but here we use "dummy" data to represent +user and groups sources. Note that the ``editor`` user is a member of the ``group:editors`` group in our dummy group data (the ``GROUPS`` data structure). @@ -88,6 +82,26 @@ and logout views. Add a file named ``login.py`` to your application :linenos: :language: python +Note that the ``login`` view callable in the ``login.py`` file has *two* view +configuration decorators. The order of these decorators is unimportant. +Each just adds a different :term:`view configuration` for the ``login`` view +callable. + +The first view configuration decorator configures the ``login`` view callable +so it will be invoked when someone visits ``/login`` (when the context is a +Wiki and the view name is ``login``). The second decorator (with context of +``pyramid.exceptions.Forbidden``) specifies a :term:`forbidden view`. This +configures our login view to be presented to the user when :app:`Pyramid` +detects that a view invocation can not be authorized. Because we've +configured a forbidden view, the ``login`` view callable will be invoked +whenever one of our users tries to execute a view callable that they are not +allowed to invoke as determined by the :term:`authorization policy` in use. +In our application, for example, this means that if a user has not logged in, +and he tries to add or edit a Wiki page, he will be shown the login form. +Before being allowed to continue on to the add or edit form, he will have to +provide credentials that give him permission to add or edit via this login +form. + Changing Existing Views ~~~~~~~~~~~~~~~~~~~~~~~ @@ -142,17 +156,15 @@ class="main_content">`` div: <a href="${request.application_url}/logout">Logout</a> </span> -Giving Our Root Model Object an ACL ------------------------------------ +Giving Our Root Resource an ACL +------------------------------- -We need to give our root model object an :term:`ACL`. This ACL will -be sufficient to provide enough information to the :app:`Pyramid` -security machinery to challenge a user who doesn't have appropriate -credentials when he attempts to invoke the ``add_page`` or -``edit_page`` views. +We need to give our root resource object an :term:`ACL`. This ACL will be +sufficient to provide enough information to the :app:`Pyramid` security +machinery to challenge a user who doesn't have appropriate credentials when +he attempts to invoke the ``add_page`` or ``edit_page`` views. -We need to perform some imports at module scope in our ``models.py`` -file: +We need to perform some imports at module scope in our ``models.py`` file: .. code-block:: python :linenos: @@ -160,8 +172,8 @@ file: from pyramid.security import Allow from pyramid.security import Everyone -Our root model is a ``Wiki`` object. We'll add the following line at -class scope to our ``Wiki`` class: +Our root resource object is a ``Wiki`` instance. We'll add the following +line at class scope to our ``Wiki`` class: .. code-block:: python :linenos: @@ -169,12 +181,11 @@ class scope to our ``Wiki`` class: __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'group:editors', 'edit') ] -It's only happenstance that we're assigning this ACL at class scope. -An ACL can be attached to an object *instance* too; this is how "row -level security" can be achieved in :app:`Pyramid` applications. We -actually only need *one* ACL for the entire system, however, because -our security requirements are simple, so this feature is not -demonstrated. +It's only happenstance that we're assigning this ACL at class scope. An ACL +can be attached to an object *instance* too; this is how "row level security" +can be achieved in :app:`Pyramid` applications. We actually only need *one* +ACL for the entire system, however, because our security requirements are +simple, so this feature is not demonstrated. Our resulting ``models.py`` file will now look like so: @@ -185,76 +196,71 @@ Our resulting ``models.py`` file will now look like so: Adding ``permission`` Declarations to our ``view_config`` Decorators -------------------------------------------------------------------- -To protect each of our views with a particular permission, we need to -pass a ``permission`` argument to each of our -:class:`pyramid.view.view_config` decorators. To do so, within -``views.py``: +To protect each of our views with a particular permission, we need to pass a +``permission`` argument to each of our :class:`pyramid.view.view_config` +decorators. To do so, within ``views.py``: -- We add ``permission='view'`` to the decorator attached to the - ``view_wiki`` view function. This makes the assertion that only - users who possess the effective ``view`` permission at the time of - the request may invoke this view. We've granted - :data:`pyramid.security.Everyone` the view permission at the root - model via its ACL, so everyone will be able to invoke the - ``view_wiki`` view. +- We add ``permission='view'`` to the decorator attached to the ``view_wiki`` + view function. This makes the assertion that only users who possess the + ``view`` permission against the context resource at the time of the request + may invoke this view. We've granted :data:`pyramid.security.Everyone` the + view permission at the root model via its ACL, so everyone will be able to + invoke the ``view_wiki`` view. -- We add ``permission='view'`` to the decorator attached to the - ``view_page`` view function. This makes the assertion that only - users who possess the effective ``view`` permission at the time of +- We add ``permission='view'`` to the decorator attached to the ``view_page`` + view function. This makes the assertion that only users who possess the + effective ``view`` permission against the context resource at the time of the request may invoke this view. We've granted - :data:`pyramid.security.Everyone` the view permission at the root - model via its ACL, so everyone will be able to invoke the - ``view_page`` view. - -- We add ``permission='edit'`` to the decorator attached to the - ``add_page`` view function. This makes the assertion that only - users who possess the effective ``edit`` permission at the time of - the request may invoke this view. We've granted the - ``group:editors`` principal the ``edit`` permission at the root - model via its ACL, so only the a user whom is a member of the group - named ``group:editors`` will able to invoke the ``add_page`` view. - We've likewise given the ``editor`` user membership to this group - via thes ``security.py`` file by mapping him to the - ``group:editors`` group in the ``GROUPS`` data structure (``GROUPS = - {'editor':['group:editors']}``); the ``groupfinder`` function - consults the ``GROUPS`` data structure. This means that the - ``editor`` user can add pages. - -- We add ``permission='edit'`` to the decorator attached to the - ``edit_page`` view function. This makes the assertion that only - users who possess the effective ``edit`` permission at the time of - the request may invoke this view. We've granted the - ``group:editors`` principal the ``edit`` permission at the root - model via its ACL, so only the a user whom is a member of the group - named ``group:editors`` will able to invoke the ``edit_page`` view. - We've likewise given the ``editor`` user membership to this group - via thes ``security.py`` file by mapping him to the - ``group:editors`` group in the ``GROUPS`` data structure (``GROUPS = - {'editor':['group:editors']}``); the ``groupfinder`` function - consults the ``GROUPS`` data structure. This means that the - ``editor`` user can edit pages. + :data:`pyramid.security.Everyone` the view permission at the root model via + its ACL, so everyone will be able to invoke the ``view_page`` view. + +- We add ``permission='edit'`` to the decorator attached to the ``add_page`` + view function. This makes the assertion that only users who possess the + effective ``edit`` permission against the context resource at the time of + the request may invoke this view. We've granted the ``group:editors`` + principal the ``edit`` permission at the root model via its ACL, so only + the a user whom is a member of the group named ``group:editors`` will able + to invoke the ``add_page`` view. We've likewise given the ``editor`` user + membership to this group via thes ``security.py`` file by mapping him to + the ``group:editors`` group in the ``GROUPS`` data structure (``GROUPS = + {'editor':['group:editors']}``); the ``groupfinder`` function consults the + ``GROUPS`` data structure. This means that the ``editor`` user can add + pages. + +- We add ``permission='edit'`` to the decorator attached to the ``edit_page`` + view function. This makes the assertion that only users who possess the + effective ``edit`` permission against the context resource at the time of + the request may invoke this view. We've granted the ``group:editors`` + principal the ``edit`` permission at the root model via its ACL, so only + the a user whom is a member of the group named ``group:editors`` will able + to invoke the ``edit_page`` view. We've likewise given the ``editor`` user + membership to this group via thes ``security.py`` file by mapping him to + the ``group:editors`` group in the ``GROUPS`` data structure (``GROUPS = + {'editor':['group:editors']}``); the ``groupfinder`` function consults the + ``GROUPS`` data structure. This means that the ``editor`` user can edit + pages. Viewing the Application in a Browser ------------------------------------ -We can finally examine our application in a browser. The views we'll -try are as follows: +We can finally examine our application in a browser. The views we'll try are +as follows: -- Visiting ``http://localhost:6543/`` in a browser invokes the - ``view_wiki`` view. This always redirects to the ``view_page`` view - of the FrontPage page object. It is executable by any user. +- Visiting ``http://localhost:6543/`` in a browser invokes the ``view_wiki`` + view. This always redirects to the ``view_page`` view of the ``FrontPage`` + page resource. It is executable by any user. -- Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes - the ``view_page`` view of the front page page object. This is - because it's the :term:`default view` (a view without a ``name``) - for ``Page`` objects. It is executable by any user. +- Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes the + ``view_page`` view of the ``FrontPage`` Page resource. This is because + it's the :term:`default view` (a view without a ``name``) for ``Page`` + resources. It is executable by any user. -- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser - invokes the edit view for the front page object. It is executable - by only the ``editor`` user. If a different user (or the anonymous - user) invokes it, a login form will be displayed. Supplying the - credentials with the username ``editor``, password ``editor`` will - show the edit page form being displayed. +- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser invokes + the edit view for the ``FrontPage`` Page resource. It is executable by + only the ``editor`` user. If a different user (or the anonymous user) + invokes it, a login form will be displayed. Supplying the credentials with + the username ``editor``, password ``editor`` will show the edit page form + being displayed. - Visiting ``http://localhost:6543/add_page/SomePageName`` in a browser invokes the add view for a page. It is executable by only diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index c7c722f70..f6e1f800a 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -2,10 +2,9 @@ Basic Layout ============ -The starter files generated by the ``pyramid_zodb`` template are basic, -but they provide a good orientation for the high-level patterns common -to most :term:`traversal` -based :app:`Pyramid` (and :term:`ZODB` -based) projects. +The starter files generated by the ``pyramid_zodb`` template are basic, but +they provide a good orientation for the high-level patterns common to most +:term:`traversal` -based :app:`Pyramid` (and :term:`ZODB` based) projects. The source code for this tutorial stage can be browsed via `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/basiclayout/ @@ -21,9 +20,9 @@ well as to contain application configuration code. When you run the application using the ``paster`` command using the ``development.ini`` generated config file, the application configuration -points at an Setuptools *entry point* described as ``egg:tutorial#app``. In -our application, because the application's ``setup.py`` file says so, this -entry point happens to be the ``app`` function within the file named +points at an Setuptools *entry point* described as ``egg:tutorial``. In our +application, because the application's ``setup.py`` file says so, this entry +point happens to be the ``main`` function within the file named ``__init__.py``: .. literalinclude:: src/basiclayout/tutorial/__init__.py @@ -48,65 +47,40 @@ entry point happens to be the ``app`` function within the file named factory` and the settings keywords parsed by PasteDeploy. The root factory is named ``get_root``. -#. *Line 16*. Load the - ``configure.zcml`` file from our package using the - :meth:`pyramid.config.Configurator.load_zcml` method. - -#. *Line 17*. Use the +#. *Line 16*. Register a 'static view' which answers requests which start + with with URL path ``/static`` using the + :meth:`pyramid.config.Configurator.add_static_view method`. This + statement registers a view that will serve up static assets, such as CSS + and image files, for us, in this case, at + ``http://localhost:6543/static/`` and below. The first argument is the + "name" ``static``, which indicates that the URL path prefix of the view + will be ``/static``. the The second argument of this tag is the "path", + which is an :term:`asset specification`, so it finds the resources it + should serve within the ``static`` directory inside the ``tutorial`` + package. + +#. *Line 17*. Perform a :term:`scan`. A scan will find :term:`configuration + decoration`, such as view configuration decorators + (e.g. ``@view_config``) in the source code of the ``tutorial`` package and + will take actions based on these decorators. The argument to + :meth:`~pyramid.config.Configurator.scan` is the package name to scan, + which is ``tutorial``. + +#. *Line 18*. Use the :meth:`pyramid.config.Configurator.make_wsgi_app` method to return a :term:`WSGI` application. -Configuration With ``configure.zcml`` --------------------------------------- - -The ``pyramid_zodb`` template uses :term:`ZCML` to perform system -configuration. The ZCML file generated by the template looks like the -following: - - .. literalinclude:: src/basiclayout/tutorial/configure.zcml - :linenos: - :language: xml - -#. *Line 1*. The root ``<configure>`` element. - -#. *Line 4*. Boilerplate, the comment explains. - -#. *Lines 6-10*. Register a ``<view>`` that names a ``context`` type - that is a class. ``.views.my_view`` is a *function* we write - (generated by the ``pyramid_zodb`` template) that is given a - ``context`` object and a ``request`` and which returns a - dictionary. The ``renderer`` tag indicates that the - ``templates/mytemplate.pt`` template should be used to turn the - dictionary returned by the view into a response. - ``templates/mytemplate.pt`` is a *relative* path: it names the - ``mytemplate.pt`` file which lives in the ``templates`` - subdirectory of the directory in which this ``configure.zcml`` - lives in. In this case, it means it lives in the ``tutorial`` - package's ``templates`` directory as ``mytemplate.pt`` - - Since this ``<view>`` doesn't have a ``name`` attribute, it is the - "default" view for that class. - -#. *Lines 12-15*. Register a ``static`` view which answers requests - which start with ``/static``. This is a view that will serve up - static resources for us, in this case, at - ``http://localhost:6543/static/`` and below. The ``path`` element - of this tag is a relative directory name, so it finds the resources - it should serve within the ``static`` directory inside - the ``tutorial`` package. - -Content Models with ``models.py`` ---------------------------------- +Resources and Models with ``models.py`` +--------------------------------------- :app:`Pyramid` uses the word :term:`resource` to describe objects arranged hierarchically in a :term:`resource tree`. This tree is consulted by :term:`traversal` to map URLs to code. In this application, the resource tree represents the site structure, but it *also* represents the -:term:`domain model` of the application, because eeach resource is a node +:term:`domain model` of the application, because each resource is a node stored persistently in a :term:`ZODB` database. The ``models.py`` file is where the ``pyramid_zodb`` Paster template put the classes that implement our -resource objects, each of which happens also to be a domain model -object. +resource objects, each of which happens also to be a domain model object. Here is the source for ``models.py``: @@ -114,14 +88,13 @@ Here is the source for ``models.py``: :linenos: :language: py -#. *Lines 3-4*. The ``MyModel`` class we referred to in the ZCML file - named ``configure.zcml`` is implemented here. Instances of this - class will be capable of being persisted in :term:`ZODB` because - the class inherits from the - :class:`persistent.mapping.PersistentMapping` class. The - ``__parent__`` and ``__name__`` are important parts of the - :term:`traversal` protocol. By default, have these as ``None`` - indicating that this is the :term:`root` object. +#. *Lines 3-4*. The ``MyModel`` :term:`resource` class is implemented here. + Instances of this class will be capable of being persisted in :term:`ZODB` + because the class inherits from the + :class:`persistent.mapping.PersistentMapping` class. The ``__parent__`` + and ``__name__`` are important parts of the :term:`traversal` protocol. + By default, have these as ``None`` indicating that this is the + :term:`root` object. #. *Lines 6-12*. ``appmaker`` is used to return the *application root* object. It is called on *every request* to the @@ -134,3 +107,94 @@ Here is the source for ``models.py``: commit the transaction. We then return the application root object. +Views With ``views.py`` +----------------------- + +Our paster template generated a default ``views.py`` on our behalf. It +contains a single view, which is used to render the page shown when you visit +the URL ``http://localhost:6543/``. + +Here is the source for ``views.py``: + + .. literalinclude:: src/basiclayout/tutorial/views.py + :linenos: + :language: py + +Let's try to understand the components in this module: + +#. *Lines 1-2*. Perform some dependency imports. + +#. *Line 4*. Use the :func:`pyramid.view.view_config` :term:`configuration + decoration` to perform a :term:`view configuration` registration. This + view configuration registration will be activated when the application is + started. It will be activated by virtue of it being found as the result + of a :term:`scan` (when Line 17 of ``__init__.py`` is run). + + The ``@view_config`` decorator accepts a number of keyword arguments. We + use two keyword arguments here: ``context`` and ``renderer``. + + The ``context`` argument signifies that the decorated view callable should + only be run when :term:`traversal` finds the ``tutorial.models.MyModel`` + :term:`resource` to be the :term:`context` of a request. In English, this + means that when the URL ``/`` is visited, because ``MyModel`` is the root + model, this view callable will be invoked. + + The ``renderer`` argument names an :term:`asset specification` of + ``tutorial:templates/mytemplate.pt``. This asset specification points at + a :term:`Chameleon` template which lives in the ``mytemplate.pt`` file + within the ``templates`` directory of the ``tutorial`` package. And + indeed if you look in the ``templates`` directory of this package, you'll + see a ``mytemplate.pt`` template file, which renders the default home page + of the generated project. + + Since this call to ``@view_config`` doesn't pass a ``name`` argument, the + ``my_view`` function which it decorates represents the "default" view + callable used when the context is of the type ``MyModel``. + +#. *Lines 5-6*. We define a :term:`view callable` named ``my_view``, which + we decorated in the step above. This view callable is a *function* we + write generated by the ``pyramid_zodb`` template that is given a + ``request`` and which returns a dictionary. The ``mytemplate.pt`` + :term:`renderer` named by the asset specification in the step above will + convert this dictionary to a :term:`response` on our behalf. + + The function returns the dictionary ``{'project':'tutorial'}``. This + dictionary is used by the template named by the ``mytemplate.pt`` asset + specification to fill in certain values on the page. + +The WSGI Pipeline in ``development.ini`` +---------------------------------------- + +The ``development.ini`` (in the tutorial :term:`project` directory, as +opposed to the tutorial :term:`package` directory) looks like this: + +.. literalinclude:: src/views/development.ini + :linenos: + :language: ini + + +Note the existence of a ``[pipeline:main]`` section which specifies our WSGI +pipeline. This "pipeline" will be served up as our WSGI application. As far +as the WSGI server is concerned the pipeline *is* our application. Simpler +configurations don't use a pipeline: instead they expose a single WSGI +application as "main". Our setup is more complicated, so we use a pipeline +composed of :term:`middleware`. + +The ``egg:WebError#evalerror`` middleware is at the "top" of the pipeline. +This is middleware which displays debuggable errors in the browser while +you're developing (not recommended for deployment). + +The ``egg:repoze.zodbconn#closer`` middleware is in the middle of the +pipeline. This is a piece of middleware which closes the ZODB connection +opened by the ``PersistentApplicationFinder`` at the end of the request. + +The ``egg:repoze.tm#tm`` middleware is the last piece of middleware in the +pipeline. This commits a transaction near the end of the request unless +there's an exception raised. + +The final line in the ``[pipeline:main]`` section is ``tutorial``, which +refers to the ``[app:tutorial]`` section above it. The ``[app:tutorial]`` +section is the section which actually defines our application settings. The +values within this section are passed as ``**settings`` to the ``main`` +function we defined in ``__init__.py`` when the server is started via +``paster serve``. diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst index f317d31dd..f201f6dc7 100644 --- a/docs/tutorials/wiki/definingmodels.rst +++ b/docs/tutorials/wiki/definingmodels.rst @@ -20,71 +20,67 @@ The source code for this tutorial stage can be browsed via Deleting the Database --------------------- -We're going to remove the ``MyModel`` Python model class from our -``models.py`` file. Since this class is referred to within our -persistent storage (represented on disk as a file named ``Data.fs``), +In a subsequent step, we're going to remove the ``MyModel`` Python model +class from our ``models.py`` file. Since this class is referred to within +our persistent storage (represented on disk as a file named ``Data.fs``), we'll have strange things happen the next time we want to visit the -application in a browser. Remove the ``Data.fs`` from the -``tutorial`` directory before proceeding any further. It's always -fine to do this as long as you don't care about the content of the -database; the database itself will be recreated as necessary. +application in a browser. Remove the ``Data.fs`` from the ``tutorial`` +directory before proceeding any further. It's always fine to do this as long +as you don't care about the content of the database; the database itself will +be recreated as necessary. Adding Model Classes -------------------- The next thing we want to do is remove the ``MyModel`` class from the -generated ``models.py`` file. The ``MyModel`` class is only a sample -and we're not going to use it. +generated ``models.py`` file. The ``MyModel`` class is only a sample and +we're not going to use it. .. note:: - There is nothing automagically special about the filename - ``models.py``. A project may have many models throughout its - codebase in arbitrarily-named files. Files implementing models - often have ``model`` in their filenames (or they may live in a - Python subpackage of your application package named ``models``) , - but this is only by convention. - -Then, we'll add a ``Wiki`` class. Because this is a ZODB application, -this class should inherit from -:class:`persistent.mapping.PersistentMapping`. We want it to inherit -from the :class:`persistent.mapping.PersistentMapping` class because -our Wiki class will be a mapping of wiki page names to ``Page`` -objects. The :class:`persistent.mapping.PersistentMapping` class -provides our class with mapping behavior, and makes sure that our Wiki -page is stored as a "first-class" persistent object in our ZODB -database. - -Our ``Wiki`` class should also have a ``__name__`` attribute set to -``None`` at class scope, and should have a ``__parent__`` attribute -set to ``None`` at class scope as well. If a model has a -``__parent__`` attribute of ``None`` in a traversal-based -:app:`Pyramid` application, it means that it's the :term:`root` -model. The ``__name__`` of the root model is also always ``None``. + There is nothing automagically special about the filename ``models.py``. A + project may have many models throughout its codebase in arbitrarily-named + files. Files implementing models often have ``model`` in their filenames, + or they may live in a Python subpackage of your application package named + ``models``, but this is only by convention. + +Then, we'll add a ``Wiki`` class. Because this is a ZODB application, this +class should inherit from :class:`persistent.mapping.PersistentMapping`. We +want it to inherit from the :class:`persistent.mapping.PersistentMapping` +class because our Wiki class will be a mapping of wiki page names to ``Page`` +objects. The :class:`persistent.mapping.PersistentMapping` class provides +our class with mapping behavior, and makes sure that our Wiki page is stored +as a "first-class" persistent object in our ZODB database. + +Our ``Wiki`` class should also have a ``__name__`` attribute set to ``None`` +at class scope, and should have a ``__parent__`` attribute set to ``None`` at +class scope as well. If a model has a ``__parent__`` attribute of ``None`` +in a traversal-based :app:`Pyramid` application, it means that it's the +:term:`root` model. The ``__name__`` of the root model is also always +``None``. Then we'll add a ``Page`` class. This class should inherit from the -:class:`persistent.Persistent` class. We'll also give it an -``__init__`` method that accepts a single parameter named ``data``. -This parameter will contain the :term:`ReStructuredText` body -representing the wiki page content. Note that ``Page`` objects don't -have an initial ``__name__`` or ``__parent__`` attribute. All objects -in a traversal graph must have a ``__name__`` and a ``__parent__`` -attribute. We don't specify these here because both ``__name__`` and -``__parent__`` will be set by by a :term:`view` function when a Page -is added to our Wiki mapping. - -Add an Appmaker ---------------- - -We're using a mini-framework callable named -``PersistentApplicationFinder`` in our application (see ``__init__.py``). -A ``PersistentApplicationFinder`` accepts a ZODB URL as well as an -"appmaker" callback. This callback typically lives in the -``models.py`` file. - -We want to change the appmaker function in our ``models.py`` file so -that our application root is a Wiki instance, and we'll also slot a -single page object (the front page) into the wiki. +:class:`persistent.Persistent` class. We'll also give it an ``__init__`` +method that accepts a single parameter named ``data``. This parameter will +contain the :term:`ReStructuredText` body representing the wiki page content. +Note that ``Page`` objects don't have an initial ``__name__`` or +``__parent__`` attribute. All objects in a traversal graph must have a +``__name__`` and a ``__parent__`` attribute. We don't specify these here +because both ``__name__`` and ``__parent__`` will be set by by a :term:`view` +function when a Page is added to our Wiki mapping. + +As a last step, we want to change the ``appmaker`` function in our +``models.py`` file so that the :term:`root` :term:`resource` of our +application is a Wiki instance. We'll also slot a single page object (the +front page) into the Wiki within the ``appmaker``. This will provide +:term:`traversal` a :term:`resource tree` to work against when it attempts to +resolve URLs to resources. + +We're using a mini-framework callable named ``PersistentApplicationFinder`` +in our application (see ``__init__.py``). A ``PersistentApplicationFinder`` +accepts a ZODB URL as well as an "appmaker" callback. This callback +typically lives in the ``models.py`` file. We'll just change this function, +making the necessary edits. Looking at the Result of Our Edits to ``models.py`` --------------------------------------------------- @@ -96,19 +92,37 @@ something like this: :linenos: :language: python +Removing View Configuration +--------------------------- + +In a previous step in this chapter, we removed the +``tutorial.models.MyModel`` class. However, our ``views.py`` module still +attempts to import this class. Temporarily, we'll change ``views.py`` so +that it no longer references ``MyModel`` by removing its imports and removing +a reference to it from the arguments passed to the ``@view_config`` +:term:`configuration decoration` decorator which sits atop the ``my_view`` +view callable. + +The result of all of our edits to ``views.py`` will end up looking +something like this: + +.. literalinclude:: src/models/tutorial/views.py + :linenos: + :language: python + Testing the Models ------------------ -To make sure the code we just wrote works, we write tests for the -model classes and the appmaker. Changing ``tests.py``, we'll write a -separate test class for each model class, and we'll write a test class -for the ``appmaker``. +To make sure the code we just wrote works, we write tests for the model +classes and the appmaker. Changing ``tests.py``, we'll write a separate test +class for each model class, and we'll write a test class for the +``appmaker``. -To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided -as a result of the ``pyramid_zodb`` project generator. We'll add -three test classes: one for the ``Page`` model named -``PageModelTests``, one for the ``Wiki`` model named -``WikiModelTests``, and one for the appmaker named ``AppmakerTests``. +To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided as a +result of the ``pyramid_zodb`` project generator. We'll add three test +classes: one for the ``Page`` model named ``PageModelTests``, one for the +``Wiki`` model named ``WikiModelTests``, and one for the appmaker named +``AppmakerTests``. When we're done changing ``tests.py``, it will look something like so: @@ -116,6 +130,22 @@ When we're done changing ``tests.py``, it will look something like so: :linenos: :language: python +Declaring Dependencies in Our ``setup.py`` File +----------------------------------------------- + +Our application now depends on packages which are not dependencies of the +original "tutorial" application as it was generated by the ``paster create`` +command. We'll add these dependencies to our ``tutorial`` package's +``setup.py`` file by assigning these dependencies to both the +``install_requires`` and the ``tests_require`` parameters to the ``setup`` +function. In particular, we require the ``docutils`` package. + +Our resulting ``setup.py`` should look like so: + +.. literalinclude:: src/models/setup.py + :linenos: + :language: python + Running the Tests ----------------- @@ -145,20 +175,3 @@ The expected output is something like this: OK -Declaring Dependencies in Our ``setup.py`` File ------------------------------------------------ - -Our application depends on packages which are not dependencies of the -original "tutorial" application as it was generated by the ``paster -create`` command. We'll add these dependencies to our ``tutorial`` -package's ``setup.py`` file by assigning these dependencies to both -the ``install_requires`` and the ``tests_require`` parameters to the -``setup`` function. In particular, we require the ``docutils`` -package. - -Our resulting ``setup.py`` should look like so: - -.. literalinclude:: src/models/setup.py - :linenos: - :language: python - diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 5250cb5e5..5b0e5dca1 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -2,28 +2,41 @@ Defining Views ============== -A :term:`view callable` in a traversal-based :app:`Pyramid` -application is typically a simple Python function that accepts two -parameters: :term:`context`, and :term:`request`. A view callable is -assumed to return a :term:`response` object. - -.. note:: A :app:`Pyramid` view can also be defined as callable which accepts - *one* arguments: a :term:`request`. You'll see this one-argument pattern - used in other :app:`Pyramid` tutorials and applications. It was also used - in the ``my_view`` view callable that we deleted in the last chapter. - Either calling convention will work in any :app:`Pyramid` application; the - calling conventions can be used interchangeably as necessary. In - :term:`traversal` based applications, such as this tutorial, the context - is used frequently within the body of a view method, so it makes sense to - use the two-argument syntax in this application. However, in :term:`url - dispatch` based applications, the context object is rarely used in the - view body itself, so within code that uses URL-dispatch-only, it's common - to define views as callables that accept only a request to avoid the - visual "noise". - -We're going to define several :term:`view callable` functions then -wire them into :app:`Pyramid` using some :term:`view -configuration` via :term:`ZCML`. +Conventionally, :term:`view callable` objects are defined within a +``views.py`` module in an :app:`Pyramid` application. There is nothing +automagically special about the filename ``views.py``. Files implementing +views often have ``view`` in their filenames (or may live in a Python +subpackage of your application package named ``views``), but this is only by +convention. A project may have many views throughout its codebase in +arbitrarily-named files. In this application, however, we'll be continuing +to use the ``views.py`` module, because there's no reason to break +convention. + +A :term:`view callable` in a :app:`Pyramid` application is typically a simple +Python function that accepts a single parameter: :term:`request`. A view +callable is assumed to return a :term:`response` object. + +However, a :app:`Pyramid` view can also be defined as callable which accepts +*two* arguments: a :term:`context` and a :term:`request`. In :term:`url +dispatch` based applications, the context resource is rarely used in the view +body itself, so within code that uses URL-dispatch-only, it's common to +define views as callables that accept only a ``request`` to avoid the visual +"noise" of a ``context`` argument. This application, however, uses +:term:`traversal` to map URLs to a context :term:`resource`, and since our +:term:`resource tree` also represents our application's "domain model", we're +often interested in the context, because it represents the persistent storage +of our application. For this reason, having ``context`` in the callable +argument list is not "noise" to us; instead it's actually rather important +within the view code we'll define in this application. + +The single-arg (``request`` -only) or two-arg (``context`` and ``request``) +calling conventions will work in any :app:`Pyramid` application for any view; +they can be used interchangeably as necessary. We'll be using the +two-argument ``(context, request)`` view callable argument list syntax in +this application. + +We're going to define several :term:`view callable` functions then wire them +into :app:`Pyramid` using some :term:`view configuration`. The source code for this tutorial stage can be browsed via `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/views/ @@ -32,146 +45,159 @@ The source code for this tutorial stage can be browsed via Adding View Functions ===================== -We're going to add four :term:`view callable` functions to our -``views.py`` module. One view (named ``view_wiki``) will display the -wiki itself (it will answer on the root URL), another named -``view_page`` will display an individual page, another named -``add_page`` will allow a page to be added, and a final view named -``edit_page`` will allow a page to be edited. - -.. note:: - - There is nothing automagically special about the filename - ``views.py``. A project may have many views throughout its codebase - in arbitrarily-named files. Files implementing views often have - ``view`` in their filenames (or may live in a Python subpackage of - your application package named ``views``), but this is only by - convention. +We're going to add four :term:`view callable` functions to our ``views.py`` +module. One view named ``view_wiki`` will display the wiki itself (it will +answer on the root URL), another named ``view_page`` will display an +individual page, another named ``add_page`` will allow a page to be added, +and a final view named ``edit_page`` will allow a page to be edited. The ``view_wiki`` view function ------------------------------- The ``view_wiki`` function will be configured to respond as the default view -of a ``Wiki`` model object. It always redirects to the ``Page`` object named -"FrontPage". It returns an instance of the +callable for a Wiki resource. We'll provide it with a ``@view_config`` +decorator which names the class ``tutorial.models.Wiki`` as its context. +This means that when a Wiki resource is the context, and no :term:`view name` +exists in the request, this view will be used. The view configuration +associated with ``view_wiki`` does not use a ``renderer`` because the view +callable always returns a :term:`response` object rather than a dictionary. +No renderer is necessary when a view returns a response object. + +The ``view_wiki`` view callable always redirects to the URL of a Page +resource named "FrontPage". To do so, it returns an instance of the :class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement -the WebOb :term:`response` interface), and the -:func:`pyramid.url.resource_url` API. :func:`pyramid.url.resource_url` -constructs a URL to the ``FrontPage`` page resource -(e.g. ``http://localhost:6543/FrontPage``), and uses it as the "location" of -the HTTPFound response, forming an HTTP redirect. +the WebOb :term:`response` interface). The :func:`pyramid.url.resource_url` +API. :func:`pyramid.url.resource_url` constructs a URL to the ``FrontPage`` +page resource (e.g. ``http://localhost:6543/FrontPage``), and uses it as the +"location" of the HTTPFound response, forming an HTTP redirect. The ``view_page`` view function ------------------------------- -The ``view_page`` function will be configured to respond as the -default view of a ``Page`` object. The ``view_page`` function renders -the :term:`ReStructuredText` body of a page (stored as the ``data`` -attribute of the context passed to the view; the context will be a -Page object) as HTML. Then it substitutes an HTML anchor for each -*WikiWord* reference in the rendered HTML using a compiled regular +The ``view_page`` function will be configured to respond as the default view +of a Page resource. We'll provide it with a ``@view_config`` decorator which +names the class ``tutorial.models.Wiki`` as its context. This means that +when a Page resource is the context, and no :term:`view name` exists in the +request, this view will be used. We inform :app:`Pyramid` this view will use +the ``templates/view.pt`` template file as a ``renderer``. + +The ``view_page`` function generates the :term:`ReStructuredText` body of a +page (stored as the ``data`` attribute of the context passed to the view; the +context will be a Page resource) as HTML. Then it substitutes an HTML anchor +for each *WikiWord* reference in the rendered HTML using a compiled regular expression. The curried function named ``check`` is used as the first argument to -``wikiwords.sub``, indicating that it should be called to provide a -value for each WikiWord match found in the content. If the wiki (our -page's ``__parent__``) already contains a page with the matched -WikiWord name, the ``check`` function generates a view link to be used -as the substitution value and returns it. If the wiki does not -already contain a page with with the matched WikiWord name, the -function generates an "add" link as the substitution value and returns +``wikiwords.sub``, indicating that it should be called to provide a value for +each WikiWord match found in the content. If the wiki (our page's +``__parent__``) already contains a page with the matched WikiWord name, the +``check`` function generates a view link to be used as the substitution value +and returns it. If the wiki does not already contain a page with with the +matched WikiWord name, the function generates an "add" link as the +substitution value and returns it. + +As a result, the ``content`` variable is now a fully formed bit of HTML +containing various view and add links for WikiWords based on the content of +our current page resource. + +We then generate an edit URL (because it's easier to do here than in the +template), and we wrap up a number of arguments in a dictionary and return it. -As a result, the ``content`` variable is now a fully formed bit of -HTML containing various view and add links for WikiWords based on the -content of our current page object. - -We then generate an edit URL (because it's easier to do here than in -the template), and we wrap up a number of arguments in a dictionary -and return it. - -The arguments we wrap into a dictionary include ``page``, ``content``, -and ``edit_url``. As a result, the *template* associated with this -view callable will be able to use these names to perform various -rendering tasks. The template associated with this view callable will -be a template which lives in ``templates/view.pt``, which we'll -associate with this view via the :term:`view configuration` which -lives in the ``configure.zcml`` file. - -Note the contrast between this view callable and the ``view_wiki`` -view callable. In the ``view_wiki`` view callable, we return a -:term:`response` object. In the ``view_page`` view callable, we -return a *dictionary*. It is *always* fine to return a -:term:`response` object from a :app:`Pyramid` view. Returning a -dictionary is allowed only when there is a :term:`renderer` associated -with the view callable in the view configuration. +The arguments we wrap into a dictionary include ``page``, ``content``, and +``edit_url``. As a result, the *template* associated with this view callable +(via ``renderer=`` in its configuration) will be able to use these names to +perform various rendering tasks. The template associated with this view +callable will be a template which lives in ``templates/view.pt``. + +Note the contrast between this view callable and the ``view_wiki`` view +callable. In the ``view_wiki`` view callable, we unconditionally return a +:term:`response` object. In the ``view_page`` view callable, we return a +*dictionary*. It is *always* fine to return a :term:`response` object from a +:app:`Pyramid` view. Returning a dictionary is allowed only when there is a +:term:`renderer` associated with the view callable in the view configuration. The ``add_page`` view function ------------------------------ -The ``add_page`` function will be invoked when a user clicks on a -WikiWord which isn't yet represented as a page in the system. The -``check`` function within the ``view_page`` view generates URLs to -this view. It also acts as a handler for the form that is generated -when we want to add a page object. The ``context`` of the -``add_page`` view is always a Wiki object (*not* a Page object). - -The request :term:`subpath` in :app:`Pyramid` is the sequence of -names that are found *after* the view name in the URL segments given -in the ``PATH_INFO`` of the WSGI request as the result of -:term:`traversal`. If our add view is invoked via, -e.g. ``http://localhost:6543/add_page/SomeName``, the :term:`subpath` -will be a tuple: ``('SomeName',)``. - -The add view takes the zeroth element of the subpath (the wiki page -name), and aliases it to the name attribute in order to know the name -of the page we're trying to add. +The ``add_page`` function will be configured to respond when the context +resource is a Wiki and the :term:`view name` is ``add_page``. We'll provide +it with a ``@view_config`` decorator which names the string ``add_page`` as +its :term:`view name` (via name=), the class ``tutorial.models.Wiki`` as its +context, and the renderer named ``templates/edit.pt``. This means that when +a Wiki resource is the context, and a :term:`view name` named ``add_page`` +exists as the result of traversal, this view will be used. We inform +:app:`Pyramid` this view will use the ``templates/edit.pt`` template file as +a ``renderer``. We share the same template between add and edit views, thus +``edit.pt`` instead of ``add.pt``. + +The ``add_page`` function will be invoked when a user clicks on a WikiWord +which isn't yet represented as a page in the system. The ``check`` function +within the ``view_page`` view generates URLs to this view. It also acts as a +handler for the form that is generated when we want to add a page resource. +The ``context`` of the ``add_page`` view is always a Wiki resource (*not* a +Page resource). + +The request :term:`subpath` in :app:`Pyramid` is the sequence of names that +are found *after* the :term:`view name` in the URL segments given in the +``PATH_INFO`` of the WSGI request as the result of :term:`traversal`. If our +add view is invoked via, e.g. ``http://localhost:6543/add_page/SomeName``, +the :term:`subpath` will be a tuple: ``('SomeName',)``. + +The add view takes the zeroth element of the subpath (the wiki page name), +and aliases it to the name attribute in order to know the name of the page +we're trying to add. If the view rendering is *not* a result of a form submission (if the -expression ``'form.submitted' in request.params`` is ``False``), the -view renders a template. To do so, it generates a "save url" which -the template use as the form post URL during rendering. We're lazy -here, so we're trying to use the same template (``templates/edit.pt``) -for the add view as well as the page edit view. To do so, we create a -dummy Page object in order to satisfy the edit form's desire to have -*some* page object exposed as ``page``, and we'll render the template -to a response. - -If the view rendering *is* a result of a form submission (if the -expression ``'form.submitted' in request.params`` is ``True``), we -scrape the page body from the form data, create a Page object using -the name in the subpath and the page body, and save it into "our -context" (the wiki) using the ``__setitem__`` method of the -context. We then redirect back to the ``view_page`` view (the default -view for a page) for the newly created page. +expression ``'form.submitted' in request.params`` is ``False``), the view +renders a template. To do so, it generates a "save url" which the template +use as the form post URL during rendering. We're lazy here, so we're trying +to use the same template (``templates/edit.pt``) for the add view as well as +the page edit view. To do so, we create a dummy Page resource object in +order to satisfy the edit form's desire to have *some* page object exposed as +``page``, and we'll render the template to a response. + +If the view rendering *is* a result of a form submission (if the expression +``'form.submitted' in request.params`` is ``True``), we scrape the page body +from the form data, create a Page object using the name in the subpath and +the page body, and save it into "our context" (the Wiki) using the +``__setitem__`` method of the context. We then redirect back to the +``view_page`` view (the default view for a page) for the newly created page. The ``edit_page`` view function ------------------------------- -The ``edit_page`` function will be invoked when a user clicks the -"Edit this Page" button on the view form. It renders an edit form but -it also acts as the handler for the form it renders. The ``context`` -of the ``edit_page`` view will *always* be a Page object (never a Wiki -object). +The ``edit_page`` function will be configured to respond when the context is +a Page resource and the :term:`view name` is ``edit_page``. We'll provide it +with a ``@view_config`` decorator which names the string ``edit_page`` as its +:term:`view name` (via ``name=``), the class ``tutorial.models.Page`` as its +context, and the renderer named ``templates/edit.pt``. This means that when +a Page resource is the context, and a :term:`view name` exists as the result +of traverasal named ``edit_page``, this view will be used. We inform +:app:`Pyramid` this view will use the ``templates/edit.pt`` template file as +a ``renderer``. + +The ``edit_page`` function will be invoked when a user clicks the "Edit this +Page" button on the view form. It renders an edit form but it also acts as +the form post view callable for the form it renders. The ``context`` of the +``edit_page`` view will *always* be a Page resource (never a Wiki resource). If the view execution is *not* a result of a form submission (if the -expression ``'form.submitted' in request.params`` is ``False``), the -view simply renders the edit form, passing the request, the page -object, and a save_url which will be used as the action of the -generated form. - -If the view execution *is* a result of a form submission (if the -expression ``'form.submitted' in request.params`` is ``True``), the -view grabs the ``body`` element of the request parameter and sets it -as the ``data`` attribute of the page context. It then redirects to -the default view of the context (the page), which will always be the -``view_page`` view. +expression ``'form.submitted' in request.params`` is ``False``), the view +simply renders the edit form, passing the request, the page resource, and a +save_url which will be used as the action of the generated form. + +If the view execution *is* a result of a form submission (if the expression +``'form.submitted' in request.params`` is ``True``), the view grabs the +``body`` element of the request parameter and sets it as the ``data`` +attribute of the page context. It then redirects to the default view of the +context (the page), which will always be the ``view_page`` view. Viewing the Result of Our Edits to ``views.py`` =============================================== -The result of all of our edits to ``views.py`` will leave it looking -like this: +The result of all of our edits to ``views.py`` will leave it looking like +this: .. literalinclude:: src/views/tutorial/views.py :linenos: @@ -181,20 +207,24 @@ Adding Templates ================ Most view callables we've added expected to be rendered via a -:term:`template`. Each template is a :term:`Chameleon` template. The -default templating system in :app:`Pyramid` is a variant of -:term:`ZPT` provided by Chameleon. These templates will live in the -``templates`` directory of our tutorial package. +:term:`template`. The default templating systems in :app:`Pyramid` are +:term:`Chameleon` and :term:`Mako`. Chameleon is a variant of :term:`ZPT`, +which is an XML-based templating language. Mako is a non-XML-based +templating language. Because we had to pick one, we chose Chameleon for this +tutorial. + +The templates we create will live in the ``templates`` directory of our +tutorial package. Chameleon templates must have a ``.pt`` extension to be +recognized as such. The ``view.pt`` Template ------------------------ -The ``view.pt`` template is used for viewing a single wiki page. It -is used by the ``view_page`` view function. It should have a div that -is "structure replaced" with the ``content`` value provided by the -view. It should also have a link on the rendered page that points at -the "edit" URL (the URL which invokes the ``edit_page`` view for the -page being viewed). +The ``view.pt`` template is used for viewing a single Page. It is used by +the ``view_page`` view function. It should have a div that is "structure +replaced" with the ``content`` value provided by the view. It should also +have a link on the rendered page that points at the "edit" URL (the URL which +invokes the ``edit_page`` view for the page being viewed). Once we're done with the ``view.pt`` template, it will look a lot like the below: @@ -203,61 +233,59 @@ the below: :linenos: :language: xml -.. note:: The names available for our use in a template are always - those that are present in the dictionary returned by the view - callable. But our templates make use of a ``request`` object that - none of our tutorial views return in their dictionary. This value - appears as if "by magic". However, ``request`` is one of several - names that are available "by default" in a template when a template - renderer is used. See :ref:`chameleon_template_renderers` for more - information about other names that are available by default in a - template when a Chameleon template is used as a renderer. +.. note:: The names available for our use in a template are always those that + are present in the dictionary returned by the view callable. But our + templates make use of a ``request`` object that none of our tutorial views + return in their dictionary. This value appears as if "by magic". + However, ``request`` is one of several names that are available "by + default" in a template when a template renderer is used. See + :ref:`chameleon_template_renderers` for more information about other names + that are available by default in a template when a template is used as a + renderer. The ``edit.pt`` Template ------------------------ -The ``edit.pt`` template is used for adding and editing a wiki page. -It is used by the ``add_page`` and ``edit_page`` view functions. It -should display a page containing a form that POSTs back to the -"save_url" argument supplied by the view. The form should have a -"body" textarea field (the page data), and a submit button that has -the name "form.submitted". The textarea in the form should be filled -with any existing page data when it is rendered. +The ``edit.pt`` template is used for adding and editing a Page. It is used +by the ``add_page`` and ``edit_page`` view functions. It should display a +page containing a form that POSTs back to the "save_url" argument supplied by +the view. The form should have a "body" textarea field (the page data), and +a submit button that has the name "form.submitted". The textarea in the form +should be filled with any existing page data when it is rendered. -Once we're done with the ``edit.pt`` template, it will look a lot like -the below: +Once we're done with the ``edit.pt`` template, it will look a lot like the +below: .. literalinclude:: src/views/tutorial/templates/edit.pt :linenos: :language: xml -Static Resources ----------------- +Static Assets +------------- -Our templates name a single static resource named ``style.css``. We need to +Our templates name a single static asset named ``style.css``. We need to create this and place it in a file named ``style.css`` within our package's ``static`` directory. This file is a little too long to replicate within the body of this guide, however it is available `online <http://github.com/Pylons/pyramid/blob/master/docs/tutorials/wiki/src/views/tutorial/static/style.css>`_. This CSS file will be accessed via -e.g. ``http://localhost:6543/static/style.css`` by virtue of the ``static`` -directive we've defined in the ``configure.zcml`` file. Any number and type -of static resources can be placed in this directory (or subdirectories) and -are just referred to by URL within templates. +e.g. ``http://localhost:6543/static/style.css`` by virtue of the call to +``add_static_view`` directive we've made in the ``__init__`` file. Any +number and type of static assets can be placed in this directory (or +subdirectories) and are just referred to by URL within templates. Testing the Views ================= -We'll modify our ``tests.py`` file, adding tests for each view -function we added above. As a result, we'll *delete* the -``ViewTests`` test in the file, and add four other test classes: -``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and -``EditPageTests``. These test the ``view_wiki``, ``view_page``, -``add_page``, and ``edit_page`` views respectively. +We'll modify our ``tests.py`` file, adding tests for each view function we +added above. As a result, we'll *delete* the ``ViewTests`` test in the file, +and add four other test classes: ``ViewWikiTests``, ``ViewPageTests``, +``AddPageTests``, and ``EditPageTests``. These test the ``view_wiki``, +``view_page``, ``add_page``, and ``edit_page`` views respectively. -Once we're done with the ``tests.py`` module, it will look a lot like -the below: +Once we're done with the ``tests.py`` module, it will look a lot like the +below: .. literalinclude:: src/views/tutorial/tests.py :linenos: @@ -266,9 +294,9 @@ the below: Running the Tests ================= -We can run these tests by using ``setup.py test`` in the same way we -did in :ref:`running_tests`. Assuming our shell's current working -directory is the "tutorial" distribution directory: +We can run these tests by using ``setup.py test`` in the same way we did in +:ref:`running_tests`. Assuming our shell's current working directory is the +"tutorial" distribution directory: On UNIX: @@ -292,123 +320,28 @@ The expected result looks something like: OK -Mapping Views to URLs in ``configure.zcml`` -=========================================== - -The ``configure.zcml`` file contains ``view`` declarations which serve -to map URLs (via :term:`traversal`) to view functions. This is also -known as :term:`view configuration`. You'll need to add four ``view`` -declarations to ``configure.zcml``. - -#. Add a declaration which maps the "Wiki" class in our ``models.py`` - file to the view named ``view_wiki`` in our ``views.py`` file with - no view name. This is the default view for a Wiki. It does not - use a ``renderer`` because the ``view_wiki`` view callable always - returns a *response* object rather than a dictionary. - -#. Add a declaration which maps the "Wiki" class in our ``models.py`` - file to the view named ``add_page`` in our ``views.py`` file with - the view name ``add_page``. Associate this view with the - ``templates/edit.pt`` template file via the ``renderer`` attribute. - This view will use the :term:`Chameleon` ZPT renderer configured - with the ``templates/edit.pt`` template to render non-*response* - return values from the ``add_page`` view. This is the add view for - a new Page. - -#. Add a declaration which maps the "Page" class in our ``models.py`` - file to the view named ``view_page`` in our ``views.py`` file with - no view name. Associate this view with the ``templates/view.pt`` - template file via the ``renderer`` attribute. This view will use - the :term:`Chameleon` ZPT renderer configured with the - ``templates/view.pt`` template to render non-*response* return - values from the ``view_page`` view. This is the default view for a - Page. - -#. Add a declaration which maps the "Page" class in our ``models.py`` - file to the view named ``edit_page`` in our ``views.py`` file with - the view name ``edit_page``. Associate this view with the - ``templates/edit.pt`` template file via the ``renderer`` attribute. - This view will use the :term:`Chameleon` ZPT renderer configured - with the ``templates/edit.pt`` template to render non-*response* - return values from the ``edit_page`` view. This is the edit view - for a page. - -As a result of our edits, the ``configure.zcml`` file should look -something like so: - -.. literalinclude:: src/views/tutorial/configure.zcml - :linenos: - :language: xml - -Examining ``development.ini`` -============================= - -Let's take a look at our ``development.ini`` file. The contents of the -file are as follows: - -.. literalinclude:: src/models/development.ini - :linenos: - :language: ini - -The WSGI Pipeline ------------------ - -Within ``development.ini``, note the existence of a ``[pipeline:main]`` -section which specifies our WSGI pipeline. This "pipeline" will be -served up as our WSGI application. As far as the WSGI server is -concerned the pipeline *is* our application. Simpler configurations -don't use a pipeline: instead they expose a single WSGI application as -"main". Our setup is more complicated, so we use a pipeline. - -``egg:repoze.zodbconn#closer`` is at the "top" of the pipeline. This -is a piece of middleware which closes the ZODB connection opened by -the PersistentApplicationFinder at the end of the request. - -``egg:repoze.tm#tm`` is the second piece of middleware in the -pipeline. This commits a transaction near the end of the request -unless there's an exception raised. - -Adding an Element to the Pipeline ---------------------------------- - -Let's add a piece of middleware to the WSGI pipeline: -``egg:Paste#evalerror`` middleware which displays debuggable errors in -the browser while you're developing (not recommended for deployment). -Let's insert evalerror into the pipeline right below -"egg:repoze.zodbconn#closer", making our resulting ``development.ini`` -file look like so: - -.. literalinclude:: src/views/development.ini - :linenos: - :language: ini - Viewing the Application in a Browser ==================================== -Once we've set up the WSGI pipeline properly, we can finally examine -our application in a browser. The views we'll try are as follows: +Once we've completed our edits, we can finally examine our application in a +browser. The views we'll try are as follows: -- Visiting ``http://localhost:6543/`` in a browser invokes the - ``view_wiki`` view. This always redirects to the ``view_page`` view - of the FrontPage page object. +- Visiting ``http://localhost:6543/`` in a browser invokes the ``view_wiki`` + view. This always redirects to the ``view_page`` view of the ``FrontPage`` + Page resource. - Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes - the ``view_page`` view of the front page page object. This is + the ``view_page`` view of the front page resource. This is because it's the *default view* (a view without a ``name``) for Page - objects. + resources. - Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser - invokes the edit view for the front page object. + invokes the edit view for the ``FrontPage`` Page resource. - Visiting ``http://localhost:6543/add_page/SomePageName`` in a - browser invokes the add view for a page. + browser invokes the add view for a Page. - To generate an error, visit ``http://localhost:6543/add_page`` which will generate an ``IndexError`` for the expression ``request.subpath[0]``. You'll see an interactive traceback - facility provided by evalerror. - - - - - + facility provided by :term:`WebError`. diff --git a/docs/tutorials/wiki/index.rst b/docs/tutorials/wiki/index.rst index 68f724902..589935047 100644 --- a/docs/tutorials/wiki/index.rst +++ b/docs/tutorials/wiki/index.rst @@ -22,7 +22,6 @@ tutorial can be browsed at basiclayout definingmodels definingviews - viewdecorators authorization distributing diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index cb05611b7..82265170d 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -251,7 +251,8 @@ assumptions: - you are willing to use :term:`traversal` to map URLs to code. -- you want to use :term:`ZCML` to perform configuration. +- you want to use imperative code plus a :term:`scan` to perform + configuration. .. note:: diff --git a/docs/tutorials/wiki/src/authorization/development.ini b/docs/tutorials/wiki/src/authorization/development.ini index a102b721b..6f4c33d93 100644 --- a/docs/tutorials/wiki/src/authorization/development.ini +++ b/docs/tutorials/wiki/src/authorization/development.ini @@ -4,12 +4,14 @@ reload_templates = true debug_authorization = false debug_notfound = false debug_routematch = false +debug_templates = true +default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] pipeline = + egg:WebError#evalerror egg:repoze.zodbconn#closer - egg:Paste#evalerror egg:repoze.tm#tm tutorial @@ -17,3 +19,29 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py index 742627a3f..3e9266754 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py @@ -1,6 +1,11 @@ -from pyramid.config import Configurator from repoze.zodbconn.finder import PersistentApplicationFinder + +from pyramid.config import Configurator +from pyramid.authentication import AuthTktAuthenticationPolicy +from pyramid.authorization import ACLAuthorizationPolicy + from tutorial.models import appmaker +from tutorial.security import groupfinder def main(global_config, **settings): """ This function returns a WSGI application. @@ -8,6 +13,9 @@ def main(global_config, **settings): It is usually called by the PasteDeploy framework during ``paster serve``. """ + authn_policy = AuthTktAuthenticationPolicy(secret='sosecret', + callback=groupfinder) + authz_policy = ACLAuthorizationPolicy() zodb_uri = settings.get('zodb_uri') if zodb_uri is None: raise ValueError("No 'zodb_uri' in application configuration.") @@ -15,6 +23,9 @@ def main(global_config, **settings): finder = PersistentApplicationFinder(zodb_uri, appmaker) def get_root(request): return finder(request.environ) - config = Configurator(root_factory=get_root, settings=settings) - config.load_zcml('configure.zcml') + config = Configurator(root_factory=get_root, settings=settings, + authentication_policy=authn_policy, + authorization_policy=authz_policy) + config.add_static_view('static', 'tutorial:static') + config.scan('tutorial') return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/authorization/tutorial/configure.zcml b/docs/tutorials/wiki/src/authorization/tutorial/configure.zcml deleted file mode 100644 index d0e65516e..000000000 --- a/docs/tutorials/wiki/src/authorization/tutorial/configure.zcml +++ /dev/null @@ -1,25 +0,0 @@ -<configure xmlns="http://pylonshq.com/pyramid"> - - <!-- this must be included for the view declarations to work --> - <include package="pyramid.includes" /> - - <scan package="."/> - - <view - view=".login.login" - renderer="templates/login.pt" - context="pyramid.exceptions.Forbidden"/> - - <authtktauthenticationpolicy - secret="sosecret" - callback=".security.groupfinder" - /> - - <aclauthorizationpolicy/> - - <static - name="static" - path="static" - /> - -</configure> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/login.py b/docs/tutorials/wiki/src/authorization/tutorial/login.py index a1194feb0..463db71a6 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/login.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/login.py @@ -1,15 +1,16 @@ from pyramid.httpexceptions import HTTPFound -from pyramid.view import view_config -from pyramid.url import resource_url - from pyramid.security import remember from pyramid.security import forget +from pyramid.view import view_config +from pyramid.url import resource_url -from tutorial.models import Wiki from tutorial.security import USERS -@view_config(context=Wiki, name='login', renderer='templates/login.pt') +@view_config(context='tutorial.models.Wiki', name='login', + renderer='templates/login.pt') +@view_config(context='pyramid.exceptions.Forbidden', + renderer='templates/login.pt') def login(request): login_url = resource_url(request.context, request, 'login') referrer = request.url @@ -36,7 +37,7 @@ def login(request): password = password, ) -@view_config(context=Wiki, name='logout') +@view_config(context='tutorial.models.Wiki', name='logout') def logout(request): headers = forget(request) return HTTPFound(location = resource_url(request.context, request), diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views.py index 3143ab552..183cb2a8d 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/views.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/views.py @@ -3,22 +3,20 @@ import re from pyramid.httpexceptions import HTTPFound from pyramid.url import resource_url - -from pyramid.security import authenticated_userid - from pyramid.view import view_config +from pyramid.security import authenticated_userid from tutorial.models import Page -from tutorial.models import Wiki # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") -@view_config(context=Wiki, permission='view') +@view_config(context='tutorial.models.Wiki', permission='view') def view_wiki(context, request): return HTTPFound(location = resource_url(context, request, 'FrontPage')) -@view_config(context=Page, renderer='templates/view.pt', permission='view') +@view_config(context='tutorial.models.Page', + renderer='templates/view.pt', permission='view') def view_page(context, request): wiki = context.__parent__ @@ -41,7 +39,8 @@ def view_page(context, request): return dict(page = context, content = content, edit_url = edit_url, logged_in = logged_in) -@view_config(context=Wiki, name='add_page', renderer='templates/edit.pt', +@view_config(name='add_page', context='tutorial.models.Wiki', + renderer='templates/edit.pt', permission='edit') def add_page(context, request): name = request.subpath[0] @@ -61,7 +60,8 @@ def add_page(context, request): return dict(page = page, save_url = save_url, logged_in = logged_in) -@view_config(context=Page, name='edit_page', renderer='templates/edit.pt', +@view_config(name='edit_page', context='tutorial.models.Page', + renderer='templates/edit.pt', permission='edit') def edit_page(context, request): if 'form.submitted' in request.params: diff --git a/docs/tutorials/wiki/src/basiclayout/CHANGES.txt b/docs/tutorials/wiki/src/basiclayout/CHANGES.txt index ffa255da8..35a34f332 100644 --- a/docs/tutorials/wiki/src/basiclayout/CHANGES.txt +++ b/docs/tutorials/wiki/src/basiclayout/CHANGES.txt @@ -1,4 +1,4 @@ 0.0 --- -- Initial version +- Initial version diff --git a/docs/tutorials/wiki/src/basiclayout/development.ini b/docs/tutorials/wiki/src/basiclayout/development.ini index fdae922e9..6f4c33d93 100644 --- a/docs/tutorials/wiki/src/basiclayout/development.ini +++ b/docs/tutorials/wiki/src/basiclayout/development.ini @@ -4,10 +4,13 @@ reload_templates = true debug_authorization = false debug_notfound = false debug_routematch = false +debug_templates = true +default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] pipeline = + egg:WebError#evalerror egg:repoze.zodbconn#closer egg:repoze.tm#tm tutorial @@ -16,3 +19,29 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki/src/basiclayout/setup.cfg b/docs/tutorials/wiki/src/basiclayout/setup.cfg index 3d7ea6e23..23b2ad983 100644 --- a/docs/tutorials/wiki/src/basiclayout/setup.cfg +++ b/docs/tutorials/wiki/src/basiclayout/setup.cfg @@ -25,4 +25,3 @@ domain = tutorial input_file = tutorial/locale/tutorial.pot output_dir = tutorial/locale previous = true - diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py index a0de6ec81..7fb15b782 100644 --- a/docs/tutorials/wiki/src/basiclayout/setup.py +++ b/docs/tutorials/wiki/src/basiclayout/setup.py @@ -19,9 +19,8 @@ setup(name='tutorial', description='tutorial', long_description=README + '\n\n' + CHANGES, classifiers=[ - "Intended Audience :: Developers", - "Framework :: Pylons", "Programming Language :: Python", + "Framework :: Pylons", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], @@ -32,8 +31,8 @@ setup(name='tutorial', packages=find_packages(), include_package_data=True, zip_safe=False, - install_requires=requires, - tests_require=requires, + install_requires = requires, + tests_require= requires, test_suite="tutorial", entry_points = """\ [paste.app_factory] @@ -41,3 +40,4 @@ setup(name='tutorial', """, paster_plugins=['pyramid'], ) + diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py index 8325865c0..a9f776980 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py @@ -13,6 +13,6 @@ def main(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) - config.load_zcml('configure.zcml') + config.add_static_view('static', 'tutorial:static') + config.scan('tutorial') return config.make_wsgi_app() - diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/configure.zcml b/docs/tutorials/wiki/src/basiclayout/tutorial/configure.zcml deleted file mode 100644 index ab7fd6fbe..000000000 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/configure.zcml +++ /dev/null @@ -1,17 +0,0 @@ -<configure xmlns="http://pylonshq.com/pyramid"> - - <!-- this must be included for the view declarations to work --> - <include package="pyramid.includes" /> - - <view - context=".models.MyModel" - view=".views.my_view" - renderer="templates/mytemplate.pt" - /> - - <static - name="static" - path="static" - /> - -</configure> diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt index a5a0dd214..6ad23d44f 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt @@ -8,57 +8,67 @@ <link rel="shortcut icon" href="${request.application_url}/static/favicon.ico" /> <link rel="stylesheet" href="${request.application_url}/static/pylons.css" type="text/css" media="screen" charset="utf-8" /> <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Nobile:regular,italic,bold,bolditalic&subset=latin" type="text/css" media="screen" charset="utf-8" /> + <!--[if !IE 7]> + <style type="text/css"> + #wrap {display:table;height:100%} + </style> + <![endif]--> </head> <body> - <div id="header"> - <div class="header">The Pyramid Web Application Development Framework</div> - </div> - <div id="top"> - <div class="top align-center"> - <img src="${request.application_url}/static/logo.png" width="300" height="80"/> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid web application development framework. - </p> + <div id="wrap"> + <div id="header"> + <div class="header">The Pyramid Web Application Development Framework</div> </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <h3>Search Pyramid documentation</h3> - <form method="get" action="http://docs.pylonshq.com/pyramid/dev/search.html"> - <input type="text" id="q" name="q" value="" /> - <input type="submit" id="x" value="Search" /> - </form> + <div id="top"> + <div class="top align-center"> + <img src="${request.application_url}/static/logo.png" width="300" height="80"/> + <p class="app-welcome"> + Welcome to <span class="app-name">${project}</span>, an application generated by<br/> + the Pyramid web application development framework. + </p> </div> - <div id="right" class="align-left"> - <h3>Pyramid links</h3> - <ul class="links"> - <li> - <a href="http://pylonshq.com">Pylons Website</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#narrative-documentation">Narrative Documentation</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#api-documentation">API Documentation</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#tutorials">Tutorials</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#change-history">Change History</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#sample-applications">Sample Applications</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#support-and-development">Support and Development</a> - </li> - <li> - <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> - </li> - </ul> + </div> + <div id="bottom"> + <div class="bottom"> + <div id="left" class="align-right"> + <h3>Search Pyramid documentation</h3> + <form method="get" action="http://docs.pylonshq.com/pyramid/dev/search.html"> + <input type="text" id="q" name="q" value="" /> + <input type="submit" id="x" value="Search" /> + </form> + </div> + <div id="right" class="align-left"> + <h3>Pyramid links</h3> + <ul class="links"> + <li> + <a href="http://pylonshq.com">Pylons Website</a> + </li> + <li> + <a href="http://docs.pylonshq.com/">The Pylons Project Documentation</a> + </li> + <li> + <a href="http://docs.pylonshq.com/pyramid/dev/#narrative-documentation">Narrative Documentation</a> + </li> + <li> + <a href="http://docs.pylonshq.com/pyramid/dev/#api-documentation">API Documentation</a> + </li> + <li> + <a href="http://docs.pylonshq.com/pyramid/dev/#tutorials">Tutorials</a> + </li> + <li> + <a href="http://docs.pylonshq.com/pyramid/dev/#change-history">Change History</a> + </li> + <li> + <a href="http://docs.pylonshq.com/pyramid/dev/#sample-applications">Sample Applications</a> + </li> + <li> + <a href="http://docs.pylonshq.com/pyramid/dev/#support-and-development">Support and Development</a> + </li> + <li> + <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> + </li> + </ul> + </div> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py index 26ee6671e..0a3d507a0 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py @@ -16,3 +16,4 @@ class ViewTests(unittest.TestCase): request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'tutorial') + diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py index 93d619d83..555f49e6d 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py @@ -1,2 +1,6 @@ +from pyramid.view import view_config +from tutorial.models import MyModel + +@view_config(context=MyModel, renderer='tutorial:templates/mytemplate.pt') def my_view(request): return {'project':'tutorial'} diff --git a/docs/tutorials/wiki/src/models/development.ini b/docs/tutorials/wiki/src/models/development.ini index fdae922e9..6f4c33d93 100644 --- a/docs/tutorials/wiki/src/models/development.ini +++ b/docs/tutorials/wiki/src/models/development.ini @@ -4,10 +4,13 @@ reload_templates = true debug_authorization = false debug_notfound = false debug_routematch = false +debug_templates = true +default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] pipeline = + egg:WebError#evalerror egg:repoze.zodbconn#closer egg:repoze.tm#tm tutorial @@ -16,3 +19,29 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py index cf0d14b2d..bf0f683bf 100644 --- a/docs/tutorials/wiki/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py @@ -4,9 +4,6 @@ from tutorial.models import appmaker def main(global_config, **settings): """ This function returns a WSGI application. - - It is usually called by the PasteDeploy framework during - ``paster serve``. """ zodb_uri = settings.get('zodb_uri') if zodb_uri is None: @@ -16,6 +13,7 @@ def main(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) - config.load_zcml('configure.zcml') + config.add_static_view('static', 'tutorial:static') + config.scan('tutorial') return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/models/tutorial/configure.zcml b/docs/tutorials/wiki/src/models/tutorial/configure.zcml deleted file mode 100644 index 38675eb13..000000000 --- a/docs/tutorials/wiki/src/models/tutorial/configure.zcml +++ /dev/null @@ -1,17 +0,0 @@ -<configure xmlns="http://pylonshq.com/pyramid"> - - <!-- this must be included for the view declarations to work --> - <include package="pyramid.includes" /> - - <view - context=".models.Wiki" - view=".views.my_view" - renderer="templates/mytemplate.pt" - /> - - <static - name="static" - path="static" - /> - -</configure> diff --git a/docs/tutorials/wiki/src/models/tutorial/views.py b/docs/tutorials/wiki/src/models/tutorial/views.py index 93d619d83..2346602c9 100644 --- a/docs/tutorials/wiki/src/models/tutorial/views.py +++ b/docs/tutorials/wiki/src/models/tutorial/views.py @@ -1,2 +1,5 @@ +from pyramid.view import view_config + +@view_config(renderer='tutorial:templates/mytemplate.pt') def my_view(request): return {'project':'tutorial'} diff --git a/docs/tutorials/wiki/src/viewdecorators/CHANGES.txt b/docs/tutorials/wiki/src/viewdecorators/CHANGES.txt deleted file mode 100644 index ffa255da8..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/CHANGES.txt +++ /dev/null @@ -1,4 +0,0 @@ -0.0 ---- - -- Initial version diff --git a/docs/tutorials/wiki/src/viewdecorators/README.txt b/docs/tutorials/wiki/src/viewdecorators/README.txt deleted file mode 100644 index d41f7f90f..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/README.txt +++ /dev/null @@ -1,4 +0,0 @@ -tutorial README - - - diff --git a/docs/tutorials/wiki/src/viewdecorators/development.ini b/docs/tutorials/wiki/src/viewdecorators/development.ini deleted file mode 100644 index a102b721b..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/development.ini +++ /dev/null @@ -1,19 +0,0 @@ -[app:tutorial] -use = egg:tutorial -reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false -zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 - -[pipeline:main] -pipeline = - egg:repoze.zodbconn#closer - egg:Paste#evalerror - egg:repoze.tm#tm - tutorial - -[server:main] -use = egg:Paste#http -host = 0.0.0.0 -port = 6543 diff --git a/docs/tutorials/wiki/src/viewdecorators/setup.cfg b/docs/tutorials/wiki/src/viewdecorators/setup.cfg deleted file mode 100644 index 3d7ea6e23..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/setup.cfg +++ /dev/null @@ -1,28 +0,0 @@ -[nosetests] -match=^test -nocapture=1 -cover-package=tutorial -with-coverage=1 -cover-erase=1 - -[compile_catalog] -directory = tutorial/locale -domain = tutorial -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = tutorial/locale/tutorial.pot -width = 80 - -[init_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale - -[update_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale -previous = true - diff --git a/docs/tutorials/wiki/src/viewdecorators/setup.py b/docs/tutorials/wiki/src/viewdecorators/setup.py deleted file mode 100644 index 5ee1333bc..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/setup.py +++ /dev/null @@ -1,44 +0,0 @@ -import os - -from setuptools import setup, find_packages - -here = os.path.abspath(os.path.dirname(__file__)) -README = open(os.path.join(here, 'README.txt')).read() -CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() - -requires = [ - 'pyramid', - 'repoze.zodbconn', - 'repoze.tm', - 'ZODB3', - 'WebError', - 'docutils', - ] - -setup(name='tutorial', - version='0.0', - description='tutorial', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Intended Audience :: Developers", - "Framework :: Pylons", - "Programming Language :: Python", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web pylons pyramid', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - install_requires=requires, - tests_require=requires, - test_suite="tutorial", - entry_points = """\ - [paste.app_factory] - main = tutorial:main - """, - paster_plugins = ['pyramid'], - ) diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/__init__.py b/docs/tutorials/wiki/src/viewdecorators/tutorial/__init__.py deleted file mode 100644 index cf0d14b2d..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -from pyramid.config import Configurator -from repoze.zodbconn.finder import PersistentApplicationFinder -from tutorial.models import appmaker - -def main(global_config, **settings): - """ This function returns a WSGI application. - - It is usually called by the PasteDeploy framework during - ``paster serve``. - """ - zodb_uri = settings.get('zodb_uri') - if zodb_uri is None: - raise ValueError("No 'zodb_uri' in application configuration.") - - finder = PersistentApplicationFinder(zodb_uri, appmaker) - def get_root(request): - return finder(request.environ) - config = Configurator(root_factory=get_root, settings=settings) - config.load_zcml('configure.zcml') - return config.make_wsgi_app() - diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/configure.zcml b/docs/tutorials/wiki/src/viewdecorators/tutorial/configure.zcml deleted file mode 100644 index be5b84c43..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/configure.zcml +++ /dev/null @@ -1,13 +0,0 @@ -<configure xmlns="http://pylonshq.com/pyramid"> - - <!-- this must be included for the view declarations to work --> - <include package="pyramid.includes" /> - - <scan package="."/> - - <static - name="static" - path="static" - /> - -</configure> diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/models.py b/docs/tutorials/wiki/src/viewdecorators/tutorial/models.py deleted file mode 100644 index 9761856c6..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/models.py +++ /dev/null @@ -1,22 +0,0 @@ -from persistent import Persistent -from persistent.mapping import PersistentMapping - -class Wiki(PersistentMapping): - __name__ = None - __parent__ = None - -class Page(Persistent): - def __init__(self, data): - self.data = data - -def appmaker(zodb_root): - if not 'app_root' in zodb_root: - app_root = Wiki() - frontpage = Page('This is the front page') - app_root['FrontPage'] = frontpage - frontpage.__name__ = 'FrontPage' - frontpage.__parent__ = app_root - zodb_root['app_root'] = app_root - import transaction - transaction.commit() - return zodb_root['app_root'] diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/favicon.ico b/docs/tutorials/wiki/src/viewdecorators/tutorial/static/favicon.ico Binary files differdeleted file mode 100644 index 71f837c9e..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/favicon.ico +++ /dev/null diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/logo.png b/docs/tutorials/wiki/src/viewdecorators/tutorial/static/logo.png Binary files differdeleted file mode 100644 index 88f5d9865..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/logo.png +++ /dev/null diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/pylons.css b/docs/tutorials/wiki/src/viewdecorators/tutorial/static/pylons.css deleted file mode 100644 index c153be07f..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/pylons.css +++ /dev/null @@ -1,73 +0,0 @@ -html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */ -vertical-align:baseline;background:transparent;} -body{line-height:1;} -ol,ul{list-style:none;} -blockquote,q{quotes:none;} -blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;} -/* remember to define focus styles! */ -:focus{outline:0;} -/* remember to highlight inserts somehow! */ -ins{text-decoration:none;} -del{text-decoration:line-through;} -/* tables still need 'cellspacing="0"' in the markup */ -table{border-collapse:collapse;border-spacing:0;} -/* restyling */ -sub{vertical-align:sub;font-size:smaller;line-height:normal;} -sup{vertical-align:super;font-size:smaller;line-height:normal;} -/* lists */ -ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;} -ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;} -li{display:list-item;} -/* nested lists have no top/bottom margins */ -ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;} -/* 2 deep unordered lists use a circle */ -ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;} -/* 3 deep (or more) unordered lists use a square */ -ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;} -.hidden{display:none;} -p{line-height:1.5em;} -h1{font-size:1.75em;/* 28px */ -line-height:1.7em;font-family:helvetica,verdana;} -h2{font-size:1.5em;/* 24px */ -line-height:1.7em;font-family:helvetica,verdana;} -h3{font-size:1.25em;/* 20px */ -line-height:1.7em;font-family:helvetica,verdana;} -h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} -html,body{width:100%;height:100%;} -body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} -a{color:#1b61d6;text-decoration:none;} -a:hover{color:#e88f00;text-decoration:underline;} -body h1, -body h2, -body h3, -body h4, -body h5, -body h6{font-family:"Nobile","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#144fb2;font-style:normal;} -#wrap {min-height: 100%;} -#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;} -#header{background-color:#e88f00;top:0;font-size:14px;} -#footer{background-color:#000000;bottom:0;position: relative;margin-top:-40px;clear:both;} -.header,.footer{width:700px;margin-right:auto;margin-left:auto;} -.wrapper{width:100%} -#top,#bottom{width:100%;} -#top{color:#888;background-color:#eee;height:300px;border-bottom:2px solid #ddd;} -#bottom{color:#222;background-color:#ffffff;overflow:hidden;padding-bottom:80px;} -.top,.bottom{width:700px;margin-right:auto;margin-left:auto;} -.top{padding-top:100px;} -.app-welcome{margin-top:25px;} -.app-name{color:#000000;font-weight:bold;} -.bottom{padding-top:50px;} -#left{width:325px;float:left;padding-right:25px;} -#right{width:325px;float:right;padding-left:25px;} -.align-left{text-align:left;} -.align-right{text-align:right;} -.align-center{text-align:center;} -ul.links{margin:0;padding:0;} -ul.links li{list-style-type:none;font-size:14px;} -form{border-style:none;} -fieldset{border-style:none;} -input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;} -input[type=text]{} -input[type=submit]{background-color:#ddd;font-weight:bold;} -/*Opera Fix*/ -body:before {content:"";height:100%;float:left;width:0;margin-top:-32767px;} diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/style.css b/docs/tutorials/wiki/src/viewdecorators/tutorial/static/style.css deleted file mode 100644 index cad87e0d4..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/style.css +++ /dev/null @@ -1,109 +0,0 @@ -html, body { - color: black; - background-color: #ddd; - font: x-small "Lucida Grande", "Lucida Sans Unicode", geneva, sans-serif; - margin: 0; - padding: 0; -} - -td, th {padding:3px;border:none;} -tr th {text-align:left;background-color:#f0f0f0;color:#333;} -tr.odd td {background-color:#edf3fe;} -tr.even td {background-color:#fff;} - -#header { - height: 80px; - width: 777px; - background: blue URL('../images/header_inner.png') no-repeat; - border-left: 1px solid #aaa; - border-right: 1px solid #aaa; - margin: 0 auto 0 auto; -} - -a.link, a, a.active { - color: #369; -} - - -#main_content { - color: black; - font-size: 127%; - background-color: white; - width: 757px; - margin: 0 auto 0 auto; - border-left: 1px solid #aaa; - border-right: 1px solid #aaa; - padding: 10px; -} - -#sidebar { - border: 1px solid #aaa; - background-color: #eee; - margin: 0.5em; - padding: 1em; - float: right; - width: 200px; - font-size: 88%; -} - -#sidebar h2 { - margin-top: 0; -} - -#sidebar ul { - margin-left: 1.5em; - padding-left: 0; -} - -h1,h2,h3,h4,h5,h6,#getting_started_steps { - font-family: "Century Schoolbook L", Georgia, serif; - font-weight: bold; -} - -h2 { - font-size: 150%; -} - -#footer { - border: 1px solid #aaa; - border-top: 0px none; - color: #999; - background-color: white; - padding: 10px; - font-size: 80%; - text-align: center; - width: 757px; - margin: 0 auto 1em auto; -} - -.code { - font-family: monospace; -} - -span.code { - font-weight: bold; - background: #eee; -} - -#status_block { - margin: 0 auto 0.5em auto; - padding: 15px 10px 15px 55px; - background: #cec URL('../images/ok.png') left center no-repeat; - border: 1px solid #9c9; - width: 450px; - font-size: 120%; - font-weight: bolder; -} - -.notice { - margin: 0.5em auto 0.5em auto; - padding: 15px 10px 15px 55px; - width: 450px; - background: #eef URL('../images/info.png') left center no-repeat; - border: 1px solid #cce; -} - -.fielderror { - color: red; - font-weight: bold; -} diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/edit.pt deleted file mode 100644 index 525bd43df..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/edit.pt +++ /dev/null @@ -1,30 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html - xmlns="http://www.w3.org/1999/xhtml" - xmlns:tal="http://xml.zope.org/namespaces/tal"> - -<head> - <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> - <title>Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki) Editing: ${page.__name__}</title> - <link rel="stylesheet" type="text/css" - href="${request.application_url}/static/style.css" /> -</head> - -<body> - -<div class="main_content"> - <div style="float:right; width: 10em;"> Viewing - <span tal:replace="page.__name__">Page Name Goes Here</span> <br/> - You can return to the <a href="${request.application_url}">FrontPage</a>. - </div> - - <div> - <form action="${save_url}" method="post"> - <textarea name="body" tal:content="page.data" rows="10" cols="60"/> - <input type="submit" name="form.submitted" value="Save"/> - </form> - </div> -</div> -</body> -</html> diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/mytemplate.pt deleted file mode 100644 index a5a0dd214..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/mytemplate.pt +++ /dev/null @@ -1,69 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"> -<head> - <title>The Pyramid Web Application Development Framework</title> - <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> - <meta name="keywords" content="python web application" /> - <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.application_url}/static/favicon.ico" /> - <link rel="stylesheet" href="${request.application_url}/static/pylons.css" type="text/css" media="screen" charset="utf-8" /> - <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Nobile:regular,italic,bold,bolditalic&subset=latin" type="text/css" media="screen" charset="utf-8" /> -</head> -<body> - <div id="header"> - <div class="header">The Pyramid Web Application Development Framework</div> - </div> - <div id="top"> - <div class="top align-center"> - <img src="${request.application_url}/static/logo.png" width="300" height="80"/> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid web application development framework. - </p> - </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <h3>Search Pyramid documentation</h3> - <form method="get" action="http://docs.pylonshq.com/pyramid/dev/search.html"> - <input type="text" id="q" name="q" value="" /> - <input type="submit" id="x" value="Search" /> - </form> - </div> - <div id="right" class="align-left"> - <h3>Pyramid links</h3> - <ul class="links"> - <li> - <a href="http://pylonshq.com">Pylons Website</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#narrative-documentation">Narrative Documentation</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#api-documentation">API Documentation</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#tutorials">Tutorials</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#change-history">Change History</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#sample-applications">Sample Applications</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#support-and-development">Support and Development</a> - </li> - <li> - <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> - </li> - </ul> - </div> - </div> - </div> - <div id="footer"> - <div class="footer">© Copyright 2008-2010, Agendaless Consulting.</div> - </div> -</body> -</html>
\ No newline at end of file diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/view.pt b/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/view.pt deleted file mode 100644 index 0fc68aa2f..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/view.pt +++ /dev/null @@ -1,26 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html - xmlns="http://www.w3.org/1999/xhtml" - xmlns:tal="http://xml.zope.org/namespaces/tal"> - -<head> - <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> - <title>${page.__name__} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title> - <link rel="stylesheet" type="text/css" - href="${request.application_url}/static/style.css" /> -</head> - -<body> - -<div class="main_content"> -<div style="float:right; width: 10em;"> Viewing -<span tal:replace="page.__name__">Page Name Goes Here</span> <br/> -You can return to the <a href="${request.application_url}">FrontPage</a>. -</div> - -<div tal:replace="structure content">Page text goes here.</div> -<p><a tal:attributes="href edit_url" href="">Edit this page</a></p> -</div> - -</body></html> diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/tests.py b/docs/tutorials/wiki/src/viewdecorators/tutorial/tests.py deleted file mode 100644 index aaf753816..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/tests.py +++ /dev/null @@ -1,124 +0,0 @@ -import unittest - -from pyramid import testing - -class PageModelTests(unittest.TestCase): - - def _getTargetClass(self): - from tutorial.models import Page - return Page - - def _makeOne(self, data=u'some data'): - return self._getTargetClass()(data=data) - - def test_constructor(self): - instance = self._makeOne() - self.assertEqual(instance.data, u'some data') - -class WikiModelTests(unittest.TestCase): - - def _getTargetClass(self): - from tutorial.models import Wiki - return Wiki - - def _makeOne(self): - return self._getTargetClass()() - - def test_it(self): - wiki = self._makeOne() - self.assertEqual(wiki.__parent__, None) - self.assertEqual(wiki.__name__, None) - -class AppmakerTests(unittest.TestCase): - def _callFUT(self, zodb_root): - from tutorial.models import appmaker - return appmaker(zodb_root) - - def test_it(self): - root = {} - self._callFUT(root) - self.assertEqual(root['app_root']['FrontPage'].data, - 'This is the front page') - -class ViewWikiTests(unittest.TestCase): - def test_it(self): - from tutorial.views import view_wiki - context = testing.DummyResource() - request = testing.DummyRequest() - response = view_wiki(context, request) - self.assertEqual(response.location, 'http://example.com/FrontPage') - -class ViewPageTests(unittest.TestCase): - def _callFUT(self, context, request): - from tutorial.views import view_page - return view_page(context, request) - - def test_it(self): - wiki = testing.DummyResource() - wiki['IDoExist'] = testing.DummyResource() - context = testing.DummyResource(data='Hello CruelWorld IDoExist') - context.__parent__ = wiki - context.__name__ = 'thepage' - request = testing.DummyRequest() - info = self._callFUT(context, request) - self.assertEqual(info['page'], context) - self.assertEqual( - info['content'], - '<div class="document">\n' - '<p>Hello <a href="http://example.com/add_page/CruelWorld">' - 'CruelWorld</a> ' - '<a href="http://example.com/IDoExist/">' - 'IDoExist</a>' - '</p>\n</div>\n') - self.assertEqual(info['edit_url'], - 'http://example.com/thepage/edit_page') - - -class AddPageTests(unittest.TestCase): - def _callFUT(self, context, request): - from tutorial.views import add_page - return add_page(context, request) - - def test_it_notsubmitted(self): - from pyramid.url import resource_url - context = testing.DummyResource() - request = testing.DummyRequest() - request.subpath = ['AnotherPage'] - info = self._callFUT(context, request) - self.assertEqual(info['page'].data,'') - self.assertEqual(info['save_url'], - resource_url( - context, request, 'add_page', 'AnotherPage')) - - def test_it_submitted(self): - context = testing.DummyResource() - request = testing.DummyRequest({'form.submitted':True, - 'body':'Hello yo!'}) - request.subpath = ['AnotherPage'] - self._callFUT(context, request) - page = context['AnotherPage'] - self.assertEqual(page.data, 'Hello yo!') - self.assertEqual(page.__name__, 'AnotherPage') - self.assertEqual(page.__parent__, context) - -class EditPageTests(unittest.TestCase): - def _callFUT(self, context, request): - from tutorial.views import edit_page - return edit_page(context, request) - - def test_it_notsubmitted(self): - from pyramid.url import resource_url - context = testing.DummyResource() - request = testing.DummyRequest() - info = self._callFUT(context, request) - self.assertEqual(info['page'], context) - self.assertEqual(info['save_url'], - resource_url(context, request, 'edit_page')) - - def test_it_submitted(self): - context = testing.DummyResource() - request = testing.DummyRequest({'form.submitted':True, - 'body':'Hello yo!'}) - response = self._callFUT(context, request) - self.assertEqual(response.location, 'http://example.com/') - self.assertEqual(context.data, 'Hello yo!') diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/views.py b/docs/tutorials/wiki/src/viewdecorators/tutorial/views.py deleted file mode 100644 index c8ac46edf..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/views.py +++ /dev/null @@ -1,62 +0,0 @@ -from docutils.core import publish_parts -import re - -from pyramid.httpexceptions import HTTPFound -from pyramid.url import resource_url -from pyramid.view import view_config - -from tutorial.models import Page -from tutorial.models import Wiki - -# regular expression used to find WikiWords -wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") - -@view_config(context=Wiki) -def view_wiki(context, request): - return HTTPFound(location = resource_url(context, request, 'FrontPage')) - -@view_config(context=Page, renderer='templates/view.pt') -def view_page(context, request): - wiki = context.__parent__ - - def check(match): - word = match.group(1) - if word in wiki: - page = wiki[word] - view_url = resource_url(page, request) - return '<a href="%s">%s</a>' % (view_url, word) - else: - add_url = request.application_url + '/add_page/' + word - return '<a href="%s">%s</a>' % (add_url, word) - - content = publish_parts(context.data, writer_name='html')['html_body'] - content = wikiwords.sub(check, content) - edit_url = resource_url(context, request, 'edit_page') - return dict(page = context, content = content, edit_url = edit_url) - -@view_config(context=Wiki, name='add_page', renderer='templates/edit.pt') -def add_page(context, request): - name = request.subpath[0] - if 'form.submitted' in request.params: - body = request.params['body'] - page = Page(body) - page.__name__ = name - page.__parent__ = context - context[name] = page - return HTTPFound(location = resource_url(page, request)) - save_url = resource_url(context, request, 'add_page', name) - page = Page('') - page.__name__ = name - page.__parent__ = context - return dict(page = page, save_url = save_url) - -@view_config(context=Page, name='edit_page', renderer='templates/edit.pt') -def edit_page(context, request): - if 'form.submitted' in request.params: - context.data = request.params['body'] - return HTTPFound(location = resource_url(context, request)) - - return dict(page = context, - save_url = resource_url(context, request, 'edit_page')) - - diff --git a/docs/tutorials/wiki/src/views/development.ini b/docs/tutorials/wiki/src/views/development.ini index a102b721b..6f4c33d93 100644 --- a/docs/tutorials/wiki/src/views/development.ini +++ b/docs/tutorials/wiki/src/views/development.ini @@ -4,12 +4,14 @@ reload_templates = true debug_authorization = false debug_notfound = false debug_routematch = false +debug_templates = true +default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] pipeline = + egg:WebError#evalerror egg:repoze.zodbconn#closer - egg:Paste#evalerror egg:repoze.tm#tm tutorial @@ -17,3 +19,29 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py index cf0d14b2d..91f7c2624 100644 --- a/docs/tutorials/wiki/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py @@ -4,9 +4,6 @@ from tutorial.models import appmaker def main(global_config, **settings): """ This function returns a WSGI application. - - It is usually called by the PasteDeploy framework during - ``paster serve``. """ zodb_uri = settings.get('zodb_uri') if zodb_uri is None: @@ -16,6 +13,6 @@ def main(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) - config.load_zcml('configure.zcml') + config.add_static_view('static', 'tutorial:static') + config.scan('tutorial') return config.make_wsgi_app() - diff --git a/docs/tutorials/wiki/src/views/tutorial/configure.zcml b/docs/tutorials/wiki/src/views/tutorial/configure.zcml deleted file mode 100644 index c1b1d6ce8..000000000 --- a/docs/tutorials/wiki/src/views/tutorial/configure.zcml +++ /dev/null @@ -1,36 +0,0 @@ -<configure xmlns="http://pylonshq.com/pyramid"> - - <!-- this must be included for the view declarations to work --> - <include package="pyramid.includes" /> - - <static - name="static" - path="static" - /> - - <view - context=".models.Wiki" - view=".views.view_wiki" - /> - - <view - context=".models.Wiki" - name="add_page" - view=".views.add_page" - renderer="templates/edit.pt" - /> - - <view - context=".models.Page" - view=".views.view_page" - renderer="templates/view.pt" - /> - - <view - context=".models.Page" - name="edit_page" - view=".views.edit_page" - renderer="templates/edit.pt" - /> - -</configure> diff --git a/docs/tutorials/wiki/src/views/tutorial/views.py b/docs/tutorials/wiki/src/views/tutorial/views.py index 8437fdc51..c96bc2e9c 100644 --- a/docs/tutorials/wiki/src/views/tutorial/views.py +++ b/docs/tutorials/wiki/src/views/tutorial/views.py @@ -3,15 +3,19 @@ import re from pyramid.httpexceptions import HTTPFound from pyramid.url import resource_url +from pyramid.view import view_config from tutorial.models import Page # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") +@view_config(context='tutorial.models.Wiki') def view_wiki(context, request): return HTTPFound(location = resource_url(context, request, 'FrontPage')) +@view_config(context='tutorial.models.Page', + renderer='tutorial:templates/view.pt') def view_page(context, request): wiki = context.__parent__ @@ -29,7 +33,9 @@ def view_page(context, request): content = wikiwords.sub(check, content) edit_url = resource_url(context, request, 'edit_page') return dict(page = context, content = content, edit_url = edit_url) - + +@view_config(name='add_page', context='tutorial.models.Wiki', + renderer='tutorial:templates/edit.pt') def add_page(context, request): name = request.subpath[0] if 'form.submitted' in request.params: @@ -44,7 +50,9 @@ def add_page(context, request): page.__name__ = name page.__parent__ = context return dict(page = page, save_url = save_url) - + +@view_config(name='edit_page', context='tutorial.models.Page', + renderer='tutorial:templates/edit.pt') def edit_page(context, request): if 'form.submitted' in request.params: context.data = request.params['body'] diff --git a/docs/tutorials/wiki/viewdecorators.rst b/docs/tutorials/wiki/viewdecorators.rst deleted file mode 100644 index c2f068d86..000000000 --- a/docs/tutorials/wiki/viewdecorators.rst +++ /dev/null @@ -1,240 +0,0 @@ -========================================================== -Using View Decorators Rather than ZCML ``view`` directives -========================================================== - -So far we've been using :term:`ZCML` to map model types to views. -It's often easier to use the :class:`pyramid.view.view_config` view -decorator to do this mapping. Using view decorators provides better -locality of reference for the mapping, because you can see which model -types and view names the view will serve right next to the view -function itself. In this mode, however, you lose the ability for some -views to be overridden "from the outside" (by someone using your -application as a framework, as explained in the -:ref:`extending_chapter`). Since this application is not meant to be -a framework, it makes sense for us to switch over to using view -decorators. - -Adding View Decorators -====================== - -We're going to import the :class:`pyramid.view.view_config` callable. -This callable can be used as a function, class, or method decorator. -We'll use it to decorate our ``view_wiki``, ``view_page``, -``add_page`` and ``edit_page`` view functions. - -The :class:`pyramid.view.view_config` callable accepts a number of -arguments: - -``context`` - - The model type which the :term:`context` of our view will be, in our - case a class. - -``name`` - - The name of the view. - -``renderer`` - - The renderer (usually a *template name*) that will be used when the - view returns a non-:term:`response` object. - -There are other arguments which this callable accepts, but these are -the ones we're going to use. - -The ``view_wiki`` view function -------------------------------- - -The decorator above the ``view_wiki`` function will be: - -.. ignore-next-block -.. code-block:: python - :linenos: - - @view_config(context=Wiki) - -This indicates that the view is for the Wiki class and has the *empty* -view_name (indicating the :term:`default view` for the Wiki class). -After injecting this decorator, we can now *remove* the following from -our ``configure.zcml`` file: - -.. code-block:: xml - :linenos: - - <view - context=".models.Wiki" - view=".views.view_wiki" - /> - -Our new decorator takes its place. - -The ``view_page`` view function -------------------------------- - -The decorator above the ``view_page`` function will be: - -.. ignore-next-block -.. code-block:: python - :linenos: - - @view_config(context=Page, renderer='templates/view.pt') - -This indicates that the view is for the Page class and has the *empty* -view_name (indicating the :term:`default view` for the Page class). -After injecting this decorator, we can now *remove* the following from -our ``configure.zcml`` file: - -.. code-block:: xml - :linenos: - - <view - context=".models.Page" - view=".views.view_page" - renderer="templates/view.pt" - /> - -Our new decorator takes its place. - -The ``add_page`` view function ------------------------------- - -The decorator above the ``add_page`` function will be: - -.. ignore-next-block -.. code-block:: python - :linenos: - - @view_config(context=Wiki, name='add_page', renderer='templates/edit.pt') - -This indicates that the view is for the Wiki class and has the -``add_page`` view_name. After injecting this decorator, we can now -*remove* the following from our ``configure.zcml`` file: - -.. code-block:: xml - :linenos: - - <view - context=".models.Wiki" - name="add_page" - view=".views.add_page" - renderer="templates/edit.pt" - /> - -Our new decorator takes its place. - -The ``edit_page`` view function -------------------------------- - -The decorator above the ``edit_page`` function will be: - -.. ignore-next-block -.. code-block:: python - :linenos: - - @view_config(context=Page, name='edit_page', renderer='templates/edit.pt') - -This indicates that the view is for the Page class and has the -``edit_page`` view_name. After injecting this decorator, we can now -*remove* the following from our ``configure.zcml`` file: - -.. code-block:: xml - :linenos: - - <view - context=".models.Page" - name="edit_page" - view=".views.edit_page" - renderer="templates/edit.pt" - /> - -Our new decorator takes its place. - -Adding a Scan Directive -======================= - -In order for our decorators to be recognized, we must add a bit of -boilerplate to our ``configure.zcml`` file which tells -:app:`Pyramid` to kick off a :term:`scan` at startup time. Add the -following tag anywhere beneath the ``<include -package="pyramid.includes">`` tag but before the ending -``</configure>`` tag within ``configure.zcml``: - -.. code-block:: xml - :linenos: - - <scan package="."/> - -Viewing the Result of Our Edits to ``views.py`` -=============================================== - -The result of all of our edits to ``views.py`` will leave it looking -like this: - -.. literalinclude:: src/viewdecorators/tutorial/views.py - :linenos: - :language: python - -Viewing the Results of Our Edits to ``configure.zcml`` -====================================================== - -The result of all of our edits to ``configure.zcml`` will leave it -looking like this: - -.. literalinclude:: src/viewdecorators/tutorial/configure.zcml - :linenos: - :language: xml - -Running the Tests -================= - -We can run these tests by using ``setup.py test`` in the same way we -did in :ref:`running_tests`. Assuming our shell's current working -directory is the "tutorial" distribution directory: - -On UNIX: - -.. code-block:: text - - $ ../bin/python setup.py test -q - -On Windows: - -.. code-block:: text - - c:\bigfntut\tutorial> ..\Scripts\python setup.py test -q - -Hopefully nothing will have changed. The expected result looks -something like: - -.. code-block:: text - - ......... - ---------------------------------------------------------------------- - Ran 9 tests in 0.203s - - OK - -Viewing the Application in a Browser -==================================== - -Once we've set up the WSGI pipeline properly, we can finally examine -our application in a browser. We'll make sure that we didn't break -any views by trying each of them. - -- Visiting ``http://localhost:6543/`` in a - browser invokes the ``view_wiki`` view. This always redirects to - the ``view_page`` view of the FrontPage page object. - -- Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes - the ``view_page`` view of the front page page object. This is - because it's the *default view* (a view without a ``name``) for Page - objects. - -- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser - invokes the edit view for the front page object. - -- Visiting ``http://localhost:6543/add_page/SomePageName`` in a - browser invokes the add view for a page. - - - |
