summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2010-12-28 21:02:36 -0500
committerChris McDonough <chrism@plope.com>2010-12-28 21:02:36 -0500
commitfcfc5aebc259fa34d8d2313adde7c2f57bab2e53 (patch)
treec52e454f30ca7453da814e57635accc302d61b2b
parente8f26928bf5c8fb8490a72436718cedf8fe19281 (diff)
parent02c43fe07f1ffe0cc27e539618ed8d96014cddee (diff)
downloadpyramid-fcfc5aebc259fa34d8d2313adde7c2f57bab2e53.tar.gz
pyramid-fcfc5aebc259fa34d8d2313adde7c2f57bab2e53.tar.bz2
pyramid-fcfc5aebc259fa34d8d2313adde7c2f57bab2e53.zip
Merge branch 'master' into viewderiver
-rw-r--r--CHANGES.txt62
-rw-r--r--CONTRIBUTORS.txt3
-rw-r--r--TODO.txt26
-rw-r--r--docs/api.rst1
-rw-r--r--docs/api/flash.rst36
-rw-r--r--docs/api/interfaces.rst1
-rw-r--r--docs/api/view.rst1
-rw-r--r--docs/conf.py2
-rw-r--r--docs/designdefense.rst1701
-rw-r--r--docs/glossary.rst9
-rw-r--r--docs/index.rst5
-rw-r--r--docs/latexindex.rst9
-rw-r--r--docs/narr/advconfig.rst395
-rw-r--r--docs/narr/assets.rst397
-rw-r--r--docs/narr/csrf.rst63
-rw-r--r--docs/narr/declarative.rst220
-rw-r--r--docs/narr/extending.rst421
-rw-r--r--docs/narr/flash.rst128
-rw-r--r--docs/narr/hooks.rst668
-rw-r--r--docs/narr/i18n.rst5
-rw-r--r--docs/narr/project.rst5
-rw-r--r--docs/narr/resources.rst13
-rw-r--r--docs/narr/static.rst258
-rw-r--r--docs/narr/urldispatch.rst38
-rw-r--r--docs/tutorials/wiki/authorization.rst236
-rw-r--r--docs/tutorials/wiki/basiclayout.rst192
-rw-r--r--docs/tutorials/wiki/definingmodels.rst173
-rw-r--r--docs/tutorials/wiki/definingviews.rst497
-rw-r--r--docs/tutorials/wiki/index.rst1
-rw-r--r--docs/tutorials/wiki/installation.rst3
-rw-r--r--docs/tutorials/wiki/src/authorization/development.ini30
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/__init__.py17
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/configure.zcml25
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/login.py13
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/views.py16
-rw-r--r--docs/tutorials/wiki/src/basiclayout/CHANGES.txt2
-rw-r--r--docs/tutorials/wiki/src/basiclayout/development.ini29
-rw-r--r--docs/tutorials/wiki/src/basiclayout/setup.cfg1
-rw-r--r--docs/tutorials/wiki/src/basiclayout/setup.py8
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py4
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/configure.zcml17
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt104
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/tests.py1
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/views.py4
-rw-r--r--docs/tutorials/wiki/src/models/development.ini29
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/__init__.py6
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/configure.zcml17
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/views.py3
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/CHANGES.txt4
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/README.txt4
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/development.ini19
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/setup.cfg28
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/setup.py44
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/tutorial/__init__.py21
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/tutorial/configure.zcml13
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/tutorial/models.py22
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/tutorial/static/favicon.icobin1406 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/tutorial/static/logo.pngbin6641 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/tutorial/static/pylons.css73
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/tutorial/static/style.css109
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/tutorial/templates/edit.pt30
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/tutorial/templates/mytemplate.pt69
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/tutorial/templates/view.pt26
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/tutorial/tests.py124
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/tutorial/views.py62
-rw-r--r--docs/tutorials/wiki/src/views/development.ini30
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/__init__.py7
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/configure.zcml36
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/views.py12
-rw-r--r--docs/tutorials/wiki/viewdecorators.rst240
-rw-r--r--pyramid/config.py128
-rw-r--r--pyramid/flash.py32
-rw-r--r--pyramid/httpexceptions.py66
-rw-r--r--pyramid/interfaces.py68
-rw-r--r--pyramid/paster.py106
-rw-r--r--pyramid/paster_templates/zodb/+package+/__init__.py_tmpl4
-rw-r--r--pyramid/paster_templates/zodb/+package+/configure.zcml17
-rw-r--r--pyramid/paster_templates/zodb/+package+/views.py_tmpl4
-rw-r--r--pyramid/session.py36
-rw-r--r--pyramid/tests/test_config.py40
-rw-r--r--pyramid/tests/test_flash.py81
-rw-r--r--pyramid/tests/test_paster.py158
-rw-r--r--pyramid/tests/test_session.py89
-rw-r--r--pyramid/tests/test_static.py2
-rw-r--r--pyramid/url.py5
-rw-r--r--pyramid/view.py6
-rw-r--r--setup.py1
87 files changed, 3856 insertions, 3855 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 9fb26b589..643e0ed7b 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,23 @@
Next release
============
+Bug Fixes
+---------
+
+- The ``proutes`` command tried too hard to resolve the view for printing,
+ resulting in exceptions when an exceptional root factory was encountered.
+ Instead of trying to resolve the view, if it cannot, it will now just print
+ ``<unknown>``.
+
+1.0a8 (2010-12-27)
+==================
+
+Bug Fixes
+---------
+
+- The name ``registry`` was not available in the ``paster pshell``
+ environment under IPython.
+
Features
--------
@@ -12,6 +29,23 @@ Features
- Added flash messaging, as described in the "Flash Messaging" narrative
documentation chapter.
+- Added CSRF token generation, as described in the narrative chapter entitled
+ "Preventing Cross-Site Request Forgery Attacks".
+
+- Prevent misunderstanding of how the ``view`` and ``view_permission``
+ arguments to add_route work by raising an exception during configuration if
+ view-related arguments exist but no ``view`` argument is passed.
+
+- Add ``paster proute`` command which displays a summary of the routing
+ table. See the narrative documentation section within the "URL Dispatch"
+ chapter entitled "Displaying All Application Routes".
+
+Paster Templates
+----------------
+
+- The ``pyramid_zodb`` Paster template no longer employs ZCML. Instead, it
+ is based on scanning.
+
Documentation
-------------
@@ -38,6 +72,34 @@ Documentation
- Added a "Flash Messaging" narrative documentation chapter.
+- Added a narrative chapter entitled "Preventing Cross-Site Request Forgery
+ Attacks".
+
+- Changed the "ZODB + Traversal Wiki Tutorial" based on changes to
+ ``pyramid_zodb`` Paster template.
+
+- Added "Advanced Configuration" narrative chapter which documents how to
+ deal with configuration conflicts, two-phase configuration, ``include`` and
+ ``commit``.
+
+- Fix API documentation rendering for ``pyramid.view.static``
+
+- Add "Pyramid Provides More Than One Way to Do It" to Design Defense
+ documentation.
+
+- Changed "Static Assets" narrative chapter: clarify that ``name`` represents
+ a prefix unless it's a URL, added an example of a root-relative static view
+ fallback for URL dispatch, added an example of creating a simple view that
+ returns the body of a file.
+
+- Move ZCML usage in Hooks chapter to Declarative Configuration chapter.
+
+- Merge "Static Assets" chapter into the "Assets" chapter.
+
+- Added narrative documentation section within the "URL Dispatch" chapter
+ entitled "Displaying All Application Routes" (for ``paster proutes``
+ command).
+
1.0a7 (2010-12-20)
==================
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 40234836e..b48e769a1 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -114,3 +114,6 @@ Contributors
- Blaise Laflamme, 2010/11/09
- Chris Rossi, 2010/11/10
+
+- Casey Duncan, 2010/12/27
+
diff --git a/TODO.txt b/TODO.txt
index f90c52538..f81c78387 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -4,21 +4,24 @@ Pyramid TODOs
Must-Have (before 1.0)
----------------------
-- Narrative docs for ``Configurator.include`` and ``Configurator.commit``.
+- Write a "Whats New" (delta from BFG 1.3)
- Consider deprecations for ``model`` and ``resource`` APIs.
Should-Have
-----------
-- Add docs for httpexceptions module for each webob.exc class that inherits
- from WSGIHTTPException.
+- Add notes about renderer response attrs to request docs.
-- translationdir ZCML directive use of ``path_spec`` should maybe die.
+- Add an example of using a cascade to serve static assets from the root.
+
+- Explore static file return from handler action using wsgiapp2 + fileapp.
-- Add CRSF token creation/checking machinery (only "should have" vs. "must
- have" because I'm not sure it belongs in Pyramid.. it definitely must exist
- in formgen libraries, and *might* belong in Pyramid).
+- Static (URL-generation only) routes.
+
+- Add narrative docs for wsgiapp and wsgiapp2.
+
+- translationdir ZCML directive use of ``path_spec`` should maybe die.
- Change "Cleaning up After a Request" in the urldispatch chapter to
use ``request.add_response_callback``.
@@ -26,11 +29,13 @@ Should-Have
- ``decorator=`` parameter to view_config. This would replace the existing
_map_view "decorator" if it existed.
+- Provide a response_set_cookie method on the request for rendered responses
+ that can be used as input to response.set_cookie?
+
Nice-to-Have
------------
-- Use ``@register_view`` instead of ``@view_config`` and change view docs to
- use "view registration" instead of "view configuration".
+- Better "Extending" chapter.
- Try to make test suite pass on IronPython.
@@ -85,9 +90,6 @@ Nice-to-Have
action = '^foo$'
mypackage.views.MyView.foo_GET
-- Provide a response_cookies attribute on the request for rendered
- responses that can be used as input to response.set_cookie.
-
- Raise an exception when a value in response_headerlist is not a
string or decide to encode.
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&amp;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
deleted file mode 100644
index 71f837c9e..000000000
--- a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/favicon.ico
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/logo.png b/docs/tutorials/wiki/src/viewdecorators/tutorial/static/logo.png
deleted file mode 100644
index 88f5d9865..000000000
--- a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/logo.png
+++ /dev/null
Binary files differ
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&amp;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.
-
-
-
diff --git a/pyramid/config.py b/pyramid/config.py
index f6746a20e..d31011aa1 100644
--- a/pyramid/config.py
+++ b/pyramid/config.py
@@ -1746,13 +1746,14 @@ class Configurator(object):
for info in view_info:
self.add_view(**info)
- if view:
+ if view_context is None:
+ view_context = view_for
if view_context is None:
- view_context = view_for
- if view_context is None:
- view_context = for_
- view_permission = view_permission or permission
- view_renderer = view_renderer or renderer
+ view_context = for_
+ view_permission = view_permission or permission
+ view_renderer = view_renderer or renderer
+
+ if view:
self.add_view(
permission=view_permission,
context=view_context,
@@ -1762,6 +1763,25 @@ class Configurator(object):
renderer=view_renderer,
attr=view_attr,
)
+ else:
+ # prevent mistakes due to misunderstanding of how hybrid calls to
+ # add_route and add_view interact
+ if view_attr:
+ raise ConfigurationError(
+ 'view_attr argument not permitted without view '
+ 'argument')
+ if view_context:
+ raise ConfigurationError(
+ 'view_context argument not permitted without view '
+ 'argument')
+ if view_permission:
+ raise ConfigurationError(
+ 'view_permission argument not permitted without view '
+ 'argument')
+ if view_renderer:
+ raise ConfigurationError(
+ 'view_renderer argument not permitted without '
+ 'view argument')
mapper = self.get_routes_mapper()
@@ -2149,20 +2169,19 @@ class Configurator(object):
""" Add a view used to render static assets such as images
and CSS files.
- The ``name`` argument is a string representing :term:`view
- name` of the view which is registered. It may alternately be
- a *url prefix*.
+ The ``name`` argument is a string representing an
+ application-relative local URL prefix. It may alternately be a full
+ URL.
- The ``path`` argument is the path on disk where the static
- files reside. This can be an absolute path, a
- package-relative path, or a :term:`asset specification`.
+ The ``path`` argument is the path on disk where the static files
+ reside. This can be an absolute path, a package-relative path, or a
+ :term:`asset specification`.
The ``cache_max_age`` keyword argument is input to set the
- ``Expires`` and ``Cache-Control`` headers for static assets
- served. Note that this argument has no effect when the
- ``name`` is a *url prefix*. By default, this argument is
- ``None``, meaning that no particular Expires or Cache-Control
- headers are set in the response.
+ ``Expires`` and ``Cache-Control`` headers for static assets served.
+ Note that this argument has no effect when the ``name`` is a *url
+ prefix*. By default, this argument is ``None``, meaning that no
+ particular Expires or Cache-Control headers are set in the response.
The ``permission`` keyword argument is used to specify the
:term:`permission` required by a user to execute the static view. By
@@ -2176,67 +2195,62 @@ class Configurator(object):
*Usage*
- The ``add_static_view`` function is typically used in
- conjunction with the :func:`pyramid.url.static_url`
- function. ``add_static_view`` adds a view which renders a
- static asset when some URL is visited;
- :func:`pyramid.url.static_url` generates a URL to that
- asset.
+ The ``add_static_view`` function is typically used in conjunction
+ with the :func:`pyramid.url.static_url` function.
+ ``add_static_view`` adds a view which renders a static asset when
+ some URL is visited; :func:`pyramid.url.static_url` generates a URL
+ to that asset.
- The ``name`` argument to ``add_static_view`` is usually a
- :term:`view name`. When this is the case, the
- :func:`pyramid.url.static_url` API will generate a URL
- which points to a Pyramid view, which will serve up a set of
- assets that live in the package itself. For example:
+ The ``name`` argument to ``add_static_view`` is usually a :term:`view
+ name`. When this is the case, the :func:`pyramid.url.static_url` API
+ will generate a URL which points to a Pyramid view, which will serve
+ up a set of assets that live in the package itself. For example:
.. code-block:: python
add_static_view('images', 'mypackage:images/')
- Code that registers such a view can generate URLs to the view
- via :func:`pyramid.url.static_url`:
+ Code that registers such a view can generate URLs to the view via
+ :func:`pyramid.url.static_url`:
.. code-block:: python
static_url('mypackage:images/logo.png', request)
- When ``add_static_view`` is called with a ``name`` argument
- that represents a simple view name, as it is above, subsequent
- calls to :func:`pyramid.url.static_url` with paths that
- start with the ``path`` argument passed to ``add_static_view``
- will generate a URL something like ``http://<Pyramid app
- URL>/images/logo.png``, which will cause the ``logo.png`` file
- in the ``images`` subdirectory of the ``mypackage`` package to
- be served.
-
- ``add_static_view`` can alternately be used with a ``name``
- argument which is a *URL*, causing static assets to be
- served from an external webserver. This happens when the
- ``name`` argument is a URL (detected as any string with a
- slash in it). In this mode, the ``name`` is used as the URL
- prefix when generating a URL using
- :func:`pyramid.url.static_url`. For example, if
- ``add_static_view`` is called like so:
+ When ``add_static_view`` is called with a ``name`` argument that
+ represents a URL prefix, as it is above, subsequent calls to
+ :func:`pyramid.url.static_url` with paths that start with the
+ ``path`` argument passed to ``add_static_view`` will generate a URL
+ something like ``http://<Pyramid app URL>/images/logo.png``, which
+ will cause the ``logo.png`` file in the ``images`` subdirectory of
+ the ``mypackage`` package to be served.
+
+ ``add_static_view`` can alternately be used with a ``name`` argument
+ which is a *URL*, causing static assets to be served from an external
+ webserver. This happens when the ``name`` argument is a fully
+ qualified URL (e.g. starts with ``http://`` or similar). In this
+ mode, the ``name`` is used as the prefix of the full URL when
+ generating a URL using :func:`pyramid.url.static_url`. For example,
+ if ``add_static_view`` is called like so:
.. code-block:: python
add_static_view('http://example.com/images', 'mypackage:images/')
- Subsequently, the URLs generated by
- :func:`pyramid.url.static_url` for that static view will be
- prefixed with ``http://example.com/images``:
+ Subsequently, the URLs generated by :func:`pyramid.url.static_url`
+ for that static view will be prefixed with
+ ``http://example.com/images``:
.. code-block:: python
static_url('mypackage:images/logo.png', request)
- When ``add_static_view`` is called 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
- ``add_static_view`` will generate a URL something like
- ``http://example.com/logo.png``. The external webserver
- listening on ``example.com`` must be itself configured to
+ When ``add_static_view`` is called 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 ``add_static_view`` will generate a URL
+ something like ``http://example.com/logo.png``. The external
+ webserver listening on ``example.com`` must be itself configured to
respond properly to such a request.
See :ref:`static_assets_section` for more information.
diff --git a/pyramid/flash.py b/pyramid/flash.py
deleted file mode 100644
index 9e9f44634..000000000
--- a/pyramid/flash.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from zope.interface import implements
-
-from pyramid.interfaces import IFlashMessages
-
-# flash message categories
-DEBUG = 'debug' # development messages
-INFO = 'info' # informational messages
-SUCCESS = 'success' # a message indicating success
-WARNING = 'warning' # not an error, but not a success
-ERROR = 'error' # an action was unsuccessful
-
-class FlashMessages(dict):
- implements(IFlashMessages)
- def custom(self, name):
- messages = self.get(name, [])
- return messages
-
- def debug(self):
- return self.get(DEBUG, [])
-
- def info(self):
- return self.get(INFO, [])
-
- def success(self):
- return self.get(SUCCESS, [])
-
- def warning(self):
- return self.get(WARNING, [])
-
- def error(self):
- return self.get(ERROR, [])
-
diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py
index 130c3ee8d..6d05f9475 100644
--- a/pyramid/httpexceptions.py
+++ b/pyramid/httpexceptions.py
@@ -1,68 +1,4 @@
-"""HTTP Exceptions.
-
-HTTP Exceptions can be returned from handlers and views (they are
-valid :term:`Response` objects).
-
-All HTTP exceptions are sub-classes of HTTPException, with additional
-sub-classes for each of the major types of HTTP response. For example,
-all 200-class HTTP exceptions sub-class HTTPOk, which sub-classes
-HTTPException.
-
-A status_map dict is also provided, which allows for key based access
-to exception objects by the HTTP status code.
-
-The exceptions are ordered into a class hierarchy based on status code
-divisions to allow for capturing of various types of HTTP exceptions
-as well::
-
- Exception
- HTTPException
- HTTPOk
- * 200 - HTTPOk
- * 201 - HTTPCreated
- * 202 - HTTPAccepted
- * 203 - HTTPNonAuthoritativeInformation
- * 204 - HTTPNoContent
- * 205 - HTTPResetContent
- * 206 - HTTPPartialContent
- HTTPRedirection
- * 300 - HTTPMultipleChoices
- * 301 - HTTPMovedPermanently
- * 302 - HTTPFound
- * 303 - HTTPSeeOther
- * 304 - HTTPNotModified
- * 305 - HTTPUseProxy
- * 306 - Unused (not implemented, obviously)
- * 307 - HTTPTemporaryRedirect
- HTTPError
- HTTPClientError
- * 400 - HTTPBadRequest
- * 401 - HTTPUnauthorized
- * 402 - HTTPPaymentRequired
- * 403 - HTTPForbidden
- * 404 - HTTPNotFound
- * 405 - HTTPMethodNotAllowed
- * 406 - HTTPNotAcceptable
- * 407 - HTTPProxyAuthenticationRequired
- * 408 - HTTPRequestTimeout
- * 409 - HTTPConflict
- * 410 - HTTPGone
- * 411 - HTTPLengthRequired
- * 412 - HTTPPreconditionFailed
- * 413 - HTTPRequestEntityTooLarge
- * 414 - HTTPRequestURITooLong
- * 415 - HTTPUnsupportedMediaType
- * 416 - HTTPRequestRangeNotSatisfiable
- * 417 - HTTPExpectationFailed
- HTTPServerError
- * 500 - HTTPInternalServerError
- * 501 - HTTPNotImplemented
- * 502 - HTTPBadGateway
- * 503 - HTTPServiceUnavailable
- * 504 - HTTPGatewayTimeout
- * 505 - HTTPVersionNotSupported
-
-"""
+from webob.exc import __doc__
from webob.exc import status_map
# Parent classes
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index 3ab833be8..32359ca94 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -120,34 +120,6 @@ class ITemplateRenderer(IRenderer):
accepts arbitrary keyword arguments and returns a string or
unicode object """
-class IFlashMessages(Interface):
- """ Dictionary-like object which maps flash category names to lists of
- flash messages. Also supports an API for obtaining classes of flash
- message lists."""
- def custom(name):
- """ Return a sequence of custom-category flash messages or an empty
- list if no messages of this custom category existed in the queue."""
-
- def debug():
- """ Return a sequence of flash.DEBUG category flash messages or an
- empty list if no flash.DEBUG messages existed in the queue."""
-
- def info():
- """ Return a sequence of flash.INFO category flash messages or an
- empty list if no flash.INFO messages existed in the queue."""
-
- def success():
- """ Return a sequence of flash.SUCCESS category flash messages or an
- empty list if no flash.SUCCESS messages existed in the queue."""
-
- def warning():
- """ Return a sequence of flash.WARNING category flash messages or an
- empty list if no flash.WARNING messages existed in the queue."""
-
- def error():
- """ Return a sequence of flash.ERROR category flash messages or an
- empty list if no flash.ERROR messages existed in the queue."""
-
# internal interfaces
class IRequest(Interface):
@@ -488,17 +460,35 @@ class ISession(Interface):
the sessioning machinery to notice the mutation of the
internal dictionary."""
- def flash(msg, category='info', queue_name=''):
- """ Push a flash message onto the stack related to the category and
- queue name. Multiple flash message queues can be managed by passing
- an optional ``queue_name``. Default category names are 'debug',
- 'info', 'success', 'warning', and 'error' (these have constant names
- importable from the ``pyramid.flash`` module). A custom category
- name is also permitted."""
-
- def unflash(queue_name=''):
- """ Pop a queue from the flash message storage. This method returns
- an object which implements ``pyramid.interfaces.IFlashMessages``"""
+ def flash(msg, queue='', allow_duplicate=True):
+ """ Push a flash message onto the end of the flash queue represented
+ by ``queue``. An alternate flash message queue can used by passing
+ an optional ``queue``, which must be a string. If
+ ``allow_duplicate`` is false, if the ``msg`` already exists in the
+ queue, it will not be readded."""
+
+ def pop_flash(queue=''):
+ """ Pop a queue from the flash storage. The queue is removed from
+ flash storage after this message is called. The queue is returned;
+ it is a list of flash messages added by
+ :meth:`pyramid.interfaces.ISesssion.flash`"""
+
+ def peek_flash(queue=''):
+ """ Peek at a queue in the flash storage. The queue remains in
+ flash storage after this message is called. The queue is returned;
+ it is a list of flash messages added by
+ :meth:`pyramid.interfaces.ISesssion.flash`
+ """
+
+ def new_csrf_token(self):
+ """ Create and set into the session a new, random cross-site request
+ forgery protection token. Return the token. It will be a string."""
+
+ def get_csrf_token(self):
+ """ Get the CSRF token previously added to the session via
+ ``new_csrf_token``, and return the token. If no CSRF token exists,
+ the value returned will be ``None``.
+ """
# mapping methods
diff --git a/pyramid/paster.py b/pyramid/paster.py
index bd0e13413..5efbae51c 100644
--- a/pyramid/paster.py
+++ b/pyramid/paster.py
@@ -63,7 +63,22 @@ def get_app(config_file, name, loadapp=loadapp):
return app
_marker = object()
-class PShellCommand(Command):
+
+class PCommand(Command):
+ get_app = staticmethod(get_app) # hook point
+ get_root = staticmethod(get_root) # hook point
+ group_name = 'pyramid'
+ interact = (interact,) # for testing
+ loadapp = (loadapp,) # for testing
+ verbose = 3
+
+ def __init__(self, *arg, **kw):
+ # needs to be in constructor to support Jython (used to be at class
+ # scope as ``usage = '\n' + __doc__``.
+ self.usage = '\n' + self.__doc__
+ Command.__init__(self, *arg, **kw)
+
+class PShellCommand(PCommand):
"""Open an interactive shell with a :app:`Pyramid` app loaded.
This command accepts two positional arguments:
@@ -88,7 +103,6 @@ class PShellCommand(Command):
min_args = 2
max_args = 2
- group_name = 'pyramid'
parser = Command.standard_parser(simulate=True)
parser.add_option('-d', '--disable-ipython',
@@ -96,18 +110,6 @@ class PShellCommand(Command):
dest='disable_ipython',
help="Don't use IPython even if it is available")
- interact = (interact,) # for testing
- loadapp = (loadapp,) # for testing
- get_app = staticmethod(get_app) # hook point
- get_root = staticmethod(get_root) # hook point
- verbose = 3
-
- def __init__(self, *arg, **kw):
- # needs to be in constructor to support Jython (used to be at class
- # scope as ``usage = '\n' + __doc__``.
- self.usage = '\n' + self.__doc__
- Command.__init__(self, *arg, **kw)
-
def command(self, IPShell=_marker):
if IPShell is _marker:
try: #pragma no cover
@@ -121,18 +123,88 @@ class PShellCommand(Command):
self.logging_file_config(config_file)
app = self.get_app(config_file, section_name, loadapp=self.loadapp[0])
root, closer = self.get_root(app)
+ shell_globals = {'root':root, 'registry':app.registry}
if IPShell is not None and not self.options.disable_ipython:
try:
- shell = IPShell(argv=[], user_ns={'root':root})
+ shell = IPShell(argv=[], user_ns=shell_globals)
shell.IP.BANNER = shell.IP.BANNER + '\n\n' + banner
shell.mainloop()
finally:
closer()
else:
try:
- self.interact[0](banner,
- local={'root':root,'registry':app.registry})
+ self.interact[0](banner, local=shell_globals)
finally:
closer()
BFGShellCommand = PShellCommand # b/w compat forever
+
+class PRoutesCommand(PCommand):
+ """Print all URL dispatch routes used by a Pyramid application in the
+ order in which they are evaluated. Each route includes the name of the
+ route, the pattern of the route, and the view callable which will be
+ invoked when the route is matched.
+
+ This command accepts two positional arguments:
+
+ ``config_file`` -- specifies the PasteDeploy config file to use
+ for the interactive shell.
+
+ ``section_name`` -- specifies the section name in the PasteDeploy
+ config file that represents the application.
+
+ Example::
+
+ $ paster proutes myapp.ini main
+
+ .. note:: You should use a ``section_name`` that refers to the
+ actual ``app`` section in the config file that points at
+ your Pyramid app without any middleware wrapping, or this
+ command will almost certainly fail.
+ """
+ summary = "Print all URL dispatch routes related to a Pyramid application"
+ min_args = 2
+ max_args = 2
+ stdout = sys.stdout
+
+ parser = Command.standard_parser(simulate=True)
+
+ def _get_mapper(self, app):
+ from pyramid.config import Configurator
+ registry = app.registry
+ config = Configurator(registry = registry)
+ return config.get_routes_mapper()
+
+ def out(self, msg): # pragma: no cover
+ print msg
+
+ def command(self):
+ from pyramid.request import Request
+ from pyramid.interfaces import IRouteRequest
+ from pyramid.interfaces import IViewClassifier
+ from pyramid.interfaces import IView
+ from zope.interface import Interface
+ from zope.interface import providedBy
+ config_file, section_name = self.args
+ app = self.get_app(config_file, section_name, loadapp=self.loadapp[0])
+ registry = app.registry
+ mapper = self._get_mapper(app)
+ if mapper is not None:
+ routes = mapper.get_routes()
+ fmt = '%-15s %-30s %-25s'
+ if not routes:
+ return
+ self.out(fmt % ('Name', 'Pattern', 'View'))
+ self.out(
+ fmt % ('-'*len('Name'), '-'*len('Pattern'), '-'*len('View')))
+ for route in routes:
+ request_iface = registry.queryUtility(IRouteRequest,
+ name=route.name)
+ view_callable = None
+ if (request_iface is None) or (route.factory is not None):
+ self.out(fmt % (route.name, route.pattern, '<unknown>'))
+ else:
+ view_callable = registry.adapters.lookup(
+ (IViewClassifier, request_iface, Interface),
+ IView, name='', default=None)
+ self.out(fmt % (route.name, route.pattern, view_callable))
diff --git a/pyramid/paster_templates/zodb/+package+/__init__.py_tmpl b/pyramid/paster_templates/zodb/+package+/__init__.py_tmpl
index 5c8f64f63..2fe3496aa 100644
--- a/pyramid/paster_templates/zodb/+package+/__init__.py_tmpl
+++ b/pyramid/paster_templates/zodb/+package+/__init__.py_tmpl
@@ -6,7 +6,6 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
zodb_uri = settings.get('zodb_uri')
- zcml_file = settings.get('configure_zcml', 'configure.zcml')
if zodb_uri is None:
raise ValueError("No 'zodb_uri' in application configuration.")
@@ -14,5 +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(zcml_file)
+ config.add_static_view('static', '{{package}}:static')
+ config.scan('{{package}}')
return config.make_wsgi_app()
diff --git a/pyramid/paster_templates/zodb/+package+/configure.zcml b/pyramid/paster_templates/zodb/+package+/configure.zcml
deleted file mode 100644
index e5715718a..000000000
--- a/pyramid/paster_templates/zodb/+package+/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/pyramid/paster_templates/zodb/+package+/views.py_tmpl b/pyramid/paster_templates/zodb/+package+/views.py_tmpl
index 12ed8832d..d4a1147c6 100644
--- a/pyramid/paster_templates/zodb/+package+/views.py_tmpl
+++ b/pyramid/paster_templates/zodb/+package+/views.py_tmpl
@@ -1,2 +1,6 @@
+from pyramid.view import view_config
+from {{package}}.models import MyModel
+
+@view_config(context=MyModel, renderer='{{package}}:templates/mytemplate.pt')
def my_view(request):
return {'project':'{{project}}'}
diff --git a/pyramid/session.py b/pyramid/session.py
index 8c3bd1897..516815d99 100644
--- a/pyramid/session.py
+++ b/pyramid/session.py
@@ -10,15 +10,15 @@ except ImportError: # pragma: no cover
from webob import Response
-import hmac
+import base64
import binascii
+import hmac
import time
-import base64
+import os
from zope.interface import implements
from pyramid.interfaces import ISession
-from pyramid import flash
def manage_accessed(wrapped):
""" Decorator which causes a cookie to be set when a wrapped
@@ -170,15 +170,31 @@ def UnencryptedCookieSessionFactoryConfig(
# flash API methods
@manage_accessed
- def flash(self, msg, category=flash.INFO, queue_name=''):
- storage = self.setdefault('_f_' + queue_name, {})
- category = storage.setdefault(category, [])
- category.append(msg)
+ def flash(self, msg, queue='', allow_duplicate=True):
+ storage = self.setdefault('_f_' + queue, [])
+ if allow_duplicate or (msg not in storage):
+ storage.append(msg)
+
+ @manage_accessed
+ def pop_flash(self, queue=''):
+ storage = self.pop('_f_' + queue, [])
+ return storage
+
+ @manage_accessed
+ def peek_flash(self, queue=''):
+ storage = self.get('_f_' + queue, [])
+ return storage
+
+ # CSRF API methods
+ @manage_accessed
+ def new_csrf_token(self):
+ token = os.urandom(20).encode('hex')
+ self['_csrft_'] = token
+ return token
@manage_accessed
- def unflash(self, queue_name=''):
- storage = self.pop('_f_' + queue_name, {})
- return flash.FlashMessages(storage)
+ def get_csrf_token(self):
+ return self.get('_csrft_', None)
# non-API methods
def _set_cookie(self, response):
diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py
index 812bfffa3..8ba4a4520 100644
--- a/pyramid/tests/test_config.py
+++ b/pyramid/tests/test_config.py
@@ -2331,6 +2331,46 @@ class ConfiguratorTests(unittest.TestCase):
route = config.add_route('name', 'pattern', pregenerator='123')
self.assertEqual(route.pregenerator, '123')
+ def test_add_route_no_view_with_view_attr(self):
+ config = self._makeOne(autocommit=True)
+ from pyramid.exceptions import ConfigurationError
+ try:
+ config.add_route('name', '/pattern', view_attr='abc')
+ except ConfigurationError:
+ pass
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_add_route_no_view_with_view_context(self):
+ config = self._makeOne(autocommit=True)
+ from pyramid.exceptions import ConfigurationError
+ try:
+ config.add_route('name', '/pattern', view_context=DummyContext)
+ except ConfigurationError:
+ pass
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_add_route_no_view_with_view_permission(self):
+ config = self._makeOne(autocommit=True)
+ from pyramid.exceptions import ConfigurationError
+ try:
+ config.add_route('name', '/pattern', view_permission='edit')
+ except ConfigurationError:
+ pass
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_add_route_no_view_with_view_renderer(self):
+ config = self._makeOne(autocommit=True)
+ from pyramid.exceptions import ConfigurationError
+ try:
+ config.add_route('name', '/pattern', view_renderer='json')
+ except ConfigurationError:
+ pass
+ else: # pragma: no cover
+ raise AssertionError
+
def test__override_not_yet_registered(self):
from pyramid.interfaces import IPackageOverrides
package = DummyPackage('package')
diff --git a/pyramid/tests/test_flash.py b/pyramid/tests/test_flash.py
deleted file mode 100644
index cce01d45b..000000000
--- a/pyramid/tests/test_flash.py
+++ /dev/null
@@ -1,81 +0,0 @@
-import unittest
-
-class TestFlashMessages(unittest.TestCase):
- def _getTargetClass(self):
- from pyramid.flash import FlashMessages
- return FlashMessages
-
- def _makeOne(self, *arg, **kw):
- cls = self._getTargetClass()
- return cls(*arg, **kw)
-
- def test_class_conforms(self):
- from zope.interface.verify import verifyClass
- from pyramid.interfaces import IFlashMessages
- verifyClass(IFlashMessages, self._getTargetClass())
-
- def test_instance_conforms(self):
- from zope.interface.verify import verifyObject
- from pyramid.interfaces import IFlashMessages
- messages = self._makeOne()
- verifyObject(IFlashMessages, messages)
-
- def test_debug_filled(self):
- from pyramid import flash
- expected = ['one', 'two']
- messages = self._makeOne({flash.DEBUG:expected})
- self.assertEqual(messages.debug(), expected)
-
- def test_debug_empty(self):
- messages = self._makeOne()
- self.assertEqual(messages.debug(), [])
-
- def test_info_filled(self):
- from pyramid import flash
- expected = ['one', 'two']
- messages = self._makeOne({flash.INFO:expected})
- self.assertEqual(messages.info(), expected)
-
- def test_info_empty(self):
- messages = self._makeOne()
- self.assertEqual(messages.info(), [])
-
- def test_success_filled(self):
- from pyramid import flash
- expected = ['one', 'two']
- messages = self._makeOne({flash.SUCCESS:expected})
- self.assertEqual(messages.success(), expected)
-
- def test_success_empty(self):
- messages = self._makeOne()
- self.assertEqual(messages.success(), [])
-
- def test_warning_filled(self):
- from pyramid import flash
- expected = ['one', 'two']
- messages = self._makeOne({flash.WARNING:expected})
- self.assertEqual(messages.warning(), expected)
-
- def test_warning_empty(self):
- messages = self._makeOne()
- self.assertEqual(messages.warning(), [])
-
- def test_error_filled(self):
- from pyramid import flash
- expected = ['one', 'two']
- messages = self._makeOne({flash.ERROR:expected})
- self.assertEqual(messages.error(), expected)
-
- def test_error_empty(self):
- messages = self._makeOne()
- self.assertEqual(messages.error(), [])
-
- def test_custom_filled(self):
- expected = ['one', 'two']
- messages = self._makeOne({'custom':expected})
- self.assertEqual(messages.custom('custom'), expected)
-
- def test_custom_empty(self):
- messages = self._makeOne()
- self.assertEqual(messages.custom('custom'), [])
-
diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py
index 41e6dc441..35349b7c7 100644
--- a/pyramid/tests/test_paster.py
+++ b/pyramid/tests/test_paster.py
@@ -50,7 +50,8 @@ class TestPShellCommand(unittest.TestCase):
pushed = app.threadlocal_manager.pushed[0]
self.assertEqual(pushed['registry'], dummy_registry)
self.assertEqual(pushed['request'].registry, dummy_registry)
- self.assertEqual(dummy_shell_factory.shell.local_ns,{'root':dummy_root})
+ self.assertEqual(dummy_shell_factory.shell.local_ns,
+ {'root':dummy_root, 'registry':dummy_registry})
self.assertEqual(dummy_shell_factory.shell.global_ns, {})
self.failUnless('\n\n' in dummy_shell_factory.shell.IP.BANNER)
self.assertEqual(len(app.threadlocal_manager.popped), 1)
@@ -110,6 +111,144 @@ class TestPShellCommand(unittest.TestCase):
self.failUnless(interact.banner)
self.assertEqual(apps, [app])
+class TestPRoutesCommand(unittest.TestCase):
+ def _getTargetClass(self):
+ from pyramid.paster import PRoutesCommand
+ return PRoutesCommand
+
+ def _makeOne(self):
+ return self._getTargetClass()('proutes')
+
+ def test_no_routes(self):
+ command = self._makeOne()
+ mapper = DummyMapper()
+ command._get_mapper = lambda *arg: mapper
+ L = []
+ command.out = L.append
+ app = DummyApp()
+ loadapp = DummyLoadApp(app)
+ command.loadapp = (loadapp,)
+ command.args = ('/foo/bar/myapp.ini', 'myapp')
+ result = command.command()
+ self.assertEqual(result, None)
+ self.assertEqual(L, [])
+
+ def test_single_route_no_route_registered(self):
+ command = self._makeOne()
+ route = DummyRoute('a', '/a')
+ mapper = DummyMapper(route)
+ command._get_mapper = lambda *arg: mapper
+ L = []
+ command.out = L.append
+ app = DummyApp()
+ loadapp = DummyLoadApp(app)
+ command.loadapp = (loadapp,)
+ command.args = ('/foo/bar/myapp.ini', 'myapp')
+ result = command.command()
+ self.assertEqual(result, None)
+ self.assertEqual(len(L), 3)
+ self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>'])
+
+ def test_single_route_no_views_registered(self):
+ from zope.interface import Interface
+ from pyramid.registry import Registry
+ from pyramid.interfaces import IRouteRequest
+ registry = Registry()
+ def view():pass
+ class IMyRoute(Interface):
+ pass
+ registry.registerUtility(IMyRoute, IRouteRequest, name='a')
+ command = self._makeOne()
+ route = DummyRoute('a', '/a')
+ mapper = DummyMapper(route)
+ command._get_mapper = lambda *arg: mapper
+ L = []
+ command.out = L.append
+ app = DummyApp()
+ app.registry = registry
+ loadapp = DummyLoadApp(app)
+ command.loadapp = (loadapp,)
+ command.args = ('/foo/bar/myapp.ini', 'myapp')
+ result = command.command()
+ self.assertEqual(result, None)
+ self.assertEqual(len(L), 3)
+ self.assertEqual(L[-1].split()[:3], ['a', '/a', 'None'])
+
+ def test_single_route_one_view_registered(self):
+ from zope.interface import Interface
+ from pyramid.registry import Registry
+ from pyramid.interfaces import IRouteRequest
+ from pyramid.interfaces import IViewClassifier
+ from pyramid.interfaces import IView
+ registry = Registry()
+ def view():pass
+ class IMyRoute(Interface):
+ pass
+ registry.registerAdapter(view,
+ (IViewClassifier, IMyRoute, Interface),
+ IView, '')
+ registry.registerUtility(IMyRoute, IRouteRequest, name='a')
+ command = self._makeOne()
+ route = DummyRoute('a', '/a')
+ mapper = DummyMapper(route)
+ command._get_mapper = lambda *arg: mapper
+ L = []
+ command.out = L.append
+ app = DummyApp()
+ app.registry = registry
+ loadapp = DummyLoadApp(app)
+ command.loadapp = (loadapp,)
+ command.args = ('/foo/bar/myapp.ini', 'myapp')
+ result = command.command()
+ self.assertEqual(result, None)
+ self.assertEqual(len(L), 3)
+ self.assertEqual(L[-1].split()[:4], ['a', '/a', '<function', 'view'])
+
+ def test_single_route_one_view_registered_with_factory(self):
+ from zope.interface import Interface
+ from pyramid.registry import Registry
+ from pyramid.interfaces import IRouteRequest
+ from pyramid.interfaces import IViewClassifier
+ from pyramid.interfaces import IView
+ registry = Registry()
+ def view():pass
+ class IMyRoot(Interface):
+ pass
+ class IMyRoute(Interface):
+ pass
+ registry.registerAdapter(view,
+ (IViewClassifier, IMyRoute, IMyRoot),
+ IView, '')
+ registry.registerUtility(IMyRoute, IRouteRequest, name='a')
+ command = self._makeOne()
+ def factory(request): pass
+ route = DummyRoute('a', '/a', factory=factory)
+ mapper = DummyMapper(route)
+ command._get_mapper = lambda *arg: mapper
+ L = []
+ command.out = L.append
+ app = DummyApp()
+ app.registry = registry
+ loadapp = DummyLoadApp(app)
+ command.loadapp = (loadapp,)
+ command.args = ('/foo/bar/myapp.ini', 'myapp')
+ result = command.command()
+ self.assertEqual(result, None)
+ self.assertEqual(len(L), 3)
+ self.assertEqual(L[-1].split()[:3], ['a', '/a', '<unknown>'])
+
+ def test__get_mapper(self):
+ from pyramid.registry import Registry
+ from pyramid.urldispatch import RoutesMapper
+ command = self._makeOne()
+ registry = Registry()
+ class App: pass
+ app = App()
+ app.registry = registry
+ result = command._get_mapper(app)
+ self.assertEqual(result.__class__, RoutesMapper)
+
+
class TestGetApp(unittest.TestCase):
def _callFUT(self, config_file, section_name, loadapp):
from pyramid.paster import get_app
@@ -124,6 +263,8 @@ class TestGetApp(unittest.TestCase):
self.assertEqual(loadapp.section_name, 'myapp')
self.assertEqual(loadapp.relative_to, os.getcwd())
self.assertEqual(result, app)
+
+
class Dummy:
pass
@@ -148,7 +289,7 @@ class DummyIPShell(object):
dummy_root = Dummy()
class DummyRegistry(object):
- def queryUtility(self, iface, default=None):
+ def queryUtility(self, iface, default=None, name=''):
return default
dummy_registry = DummyRegistry()
@@ -187,3 +328,16 @@ class DummyThreadLocalManager:
def pop(self):
self.popped.append(True)
+class DummyMapper(object):
+ def __init__(self, *routes):
+ self.routes = routes
+
+ def get_routes(self):
+ return self.routes
+
+class DummyRoute(object):
+ def __init__(self, name, pattern, factory=None):
+ self.name = name
+ self.pattern = pattern
+ self.factory = factory
+
diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py
index bfaa6cd97..0e88b28cd 100644
--- a/pyramid/tests/test_session.py
+++ b/pyramid/tests/test_session.py
@@ -117,55 +117,66 @@ class TestUnencryptedCookieSession(unittest.TestCase):
session = self._makeOne(request)
session.flash('msg1')
session.flash('msg2')
- self.assertEqual(dict(session['_f_']),
- {'info':['msg1', 'msg2']})
+ self.assertEqual(session['_f_'], ['msg1', 'msg2'])
def test_flash_mixed(self):
- from pyramid import flash
request = testing.DummyRequest()
session = self._makeOne(request)
- session.flash('warn1', flash.WARNING)
- session.flash('warn2', flash.WARNING)
- session.flash('err1', flash.ERROR)
- session.flash('err2', flash.ERROR)
- self.assertEqual(dict(session['_f_']),
- {flash.WARNING:['warn1', 'warn2'],
- flash.ERROR:['err1', 'err2']})
-
- def test_flash_with_nondefault_queue(self):
- from pyramid import flash
+ session.flash('warn1', 'warn')
+ session.flash('warn2', 'warn')
+ session.flash('err1', 'error')
+ session.flash('err2', 'error')
+ self.assertEqual(session['_f_warn'], ['warn1', 'warn2'])
+
+ def test_pop_flash_default_queue(self):
+ request = testing.DummyRequest()
+ session = self._makeOne(request)
+ queue = ['one', 'two']
+ session['_f_'] = queue
+ result = session.pop_flash()
+ self.assertEqual(result, queue)
+ self.assertEqual(session.get('_f_'), None)
+
+ def test_pop_flash_nodefault_queue(self):
+ request = testing.DummyRequest()
+ session = self._makeOne(request)
+ queue = ['one', 'two']
+ session['_f_error'] = queue
+ result = session.pop_flash('error')
+ self.assertEqual(result, queue)
+ self.assertEqual(session.get('_f_error'), None)
+
+ def test_peek_flash_default_queue(self):
+ request = testing.DummyRequest()
+ session = self._makeOne(request)
+ queue = ['one', 'two']
+ session['_f_'] = queue
+ result = session.peek_flash()
+ self.assertEqual(result, queue)
+ self.assertEqual(session.get('_f_'), queue)
+
+ def test_peek_flash_nodefault_queue(self):
request = testing.DummyRequest()
session = self._makeOne(request)
- session.flash('one_1', queue_name='one')
- session.flash('one_2', queue_name='one')
- session.flash('two_1', queue_name='two')
- session.flash('two_2', queue_name='two')
- self.assertEqual(dict(session['_f_one']),
- {flash.INFO:['one_1', 'one_2']})
- self.assertEqual(dict(session['_f_two']),
- {flash.INFO:['two_1', 'two_2']})
-
- def test_unflash_default_queue(self):
- from pyramid import flash
- from pyramid.interfaces import IFlashMessages
+ queue = ['one', 'two']
+ session['_f_error'] = queue
+ result = session.peek_flash('error')
+ self.assertEqual(result, queue)
+ self.assertEqual(session.get('_f_error'), queue)
+
+ def test_new_csrf_token(self):
request = testing.DummyRequest()
session = self._makeOne(request)
- storage = {flash.INFO:['one', 'two']}
- session['_f_'] = storage
- result = session.unflash()
- self.assertEqual(dict(result), storage)
- self.failUnless(IFlashMessages.providedBy(result))
-
- def test_unflash_nodefault_queue(self):
- from pyramid import flash
- from pyramid.interfaces import IFlashMessages
+ token = session.new_csrf_token()
+ self.assertEqual(token, session['_csrft_'])
+
+ def test_get_csrf_token(self):
request = testing.DummyRequest()
session = self._makeOne(request)
- storage = {flash.INFO:['one', 'two']}
- session['_f_one'] = storage
- result = session.unflash('one')
- self.assertEqual(dict(result), storage)
- self.failUnless(IFlashMessages.providedBy(result))
+ session['_csrft_'] = 'token'
+ token = session.get_csrf_token()
+ self.assertEqual(token, 'token')
+ self.failUnless('_csrft_' in session)
class Test_manage_accessed(unittest.TestCase):
def _makeOne(self, wrapped):
diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py
index f7ae6e1a5..33bd51d31 100644
--- a/pyramid/tests/test_static.py
+++ b/pyramid/tests/test_static.py
@@ -153,7 +153,7 @@ class TestPackageURLParser(unittest.TestCase):
self.failUnless('404 Not Found' in body)
self.assertEqual(sr.status, '404 Not Found')
-class TestStaticView(unittest.TestCase):
+class Test_static_view(unittest.TestCase):
def setUp(self):
cleanUp()
diff --git a/pyramid/url.py b/pyramid/url.py
index 4c2f5d393..e1eaaaa1e 100644
--- a/pyramid/url.py
+++ b/pyramid/url.py
@@ -227,7 +227,7 @@ def resource_url(resource, request, *elements, **kw):
a data structure with an ``.items()`` method that returns a
sequence of two-tuples (presumably a dictionary). This data
structure will be turned into a query string per the documentation
- of ``repoze.url.urlencode`` function. After the query data is
+ of ``pyramid.url.urlencode`` function. After the query data is
turned into a query string, a leading ``?`` is prepended, and the
resulting string is appended to the generated URL.
@@ -256,7 +256,8 @@ def resource_url(resource, request, *elements, **kw):
e.g. ``http://example.com?foo=1#bar``.
If the ``resource`` passed in has a ``__resource_url__`` method, it will
- be used to generate the URL that is returned by this function. See also
+ be used to generate the URL (scheme, host, port, path) that for the base
+ resource which is operated upon by this function. See also
:ref:`overriding_resource_url_generation`.
.. note:: If the :term:`resource` used is the result of a
diff --git a/pyramid/view.py b/pyramid/view.py
index 67329c363..3dc110863 100644
--- a/pyramid/view.py
+++ b/pyramid/view.py
@@ -19,10 +19,12 @@ from pyramid.interfaces import IView
from pyramid.interfaces import IViewClassifier
from pyramid.httpexceptions import HTTPFound
-from pyramid.static import static_view as static # B/C
+from pyramid.static import static_view
from pyramid.threadlocal import get_current_registry
-static = static # dont yet deprecate this (ever?)
+# Nast BW compat hack: dont yet deprecate this (ever?)
+class static(static_view): # only subclass for purposes of autodoc
+ __doc__ = static_view.__doc__
_marker = object()
diff --git a/setup.py b/setup.py
index f698f06ae..d58641487 100644
--- a/setup.py
+++ b/setup.py
@@ -88,6 +88,7 @@ setup(name='pyramid',
pylons_sqla=pyramid.paster:PylonsSQLAlchemyProjectTemplate
[paste.paster_command]
pshell=pyramid.paster:PShellCommand
+ proutes=pyramid.paster:PRoutesCommand
[console_scripts]
bfg2pyramid = pyramid.fixers.fix_bfg_imports:main
"""