From afa1cb6352fd39d8b0788c7708768aaca2974385 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Mon, 20 Dec 2010 23:33:08 -0700 Subject: promote parenthetical to an actual sentence, minor reword --- docs/narr/resources.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst index fe1f760bb..4c2897ac8 100644 --- a/docs/narr/resources.rst +++ b/docs/narr/resources.rst @@ -3,8 +3,8 @@ Resources A :term:`resource` is an object that represents a "place" in a tree related to your application. Every :app:`Pyramid` application has at least one -resource object: the :term:`root` resource (even if you don't define one -manually, a default root resource is created for you). The root resource is +resource object: the :term:`root` resource. Even if you don't define a +root resource manually, a default one is created for you. The root resource is the root of a :term:`resource tree`. A resource tree is a set of nested dictionary-like objects which you can use to represent your website's structure. -- cgit v1.2.3 From 14575f52880028537594eb06b35f2b39dd9818c9 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Mon, 20 Dec 2010 23:37:39 -0700 Subject: reword, reorder paragraph for clarity --- docs/narr/resources.rst | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst index 4c2897ac8..7b31a8260 100644 --- a/docs/narr/resources.rst +++ b/docs/narr/resources.rst @@ -1,21 +1,21 @@ Resources ========= -A :term:`resource` is an object that represents a "place" in a tree related -to your application. Every :app:`Pyramid` application has at least one -resource object: the :term:`root` resource. Even if you don't define a -root resource manually, a default one is created for you. The root resource is -the root of a :term:`resource tree`. A resource tree is a set of nested -dictionary-like objects which you can use to represent your website's -structure. +A :term:`resource` is an object that represents a "place" in a tree +related to your application. Every :app:`Pyramid` application has at +least one resource object: the :term:`root` resource. Even if you don't +define a root resource manually, a default one is created for you. The +root resource is the root of a :term:`resource tree`. A resource tree +is a set of nested dictionary-like objects which you can use to +represent your website's structure. In an application which uses :term:`traversal` to map URLs to code, the -resource tree structure is used heavily to map a URL to a :term:`view -callable`. :app:`Pyramid` will walk "up" the resource tree by traversing -through the nested dictionary structure of the tree when :term:`traversal` is -used in order to find a :term:`context` resource. Once a context resource is -found, the context resource and data in the request will be used to find a -:term:`view callable`. +resource tree structure is used heavily to map each URL to a :term:`view +callable`. When :term:`traversal` is used, :app:`Pyramid` will walk +through the resource tree by traversing through its nested dictionary +structure in order to find a :term:`context` resource. Once a context +resource is found, the context resource and data in the request will be +used to find a :term:`view callable`. In an application which uses :term:`URL dispatch`, the resource tree is only used indirectly, and is often "invisible" to the developer. In URL dispatch -- cgit v1.2.3 From 5f0a81a0fd40afcabd4e8f3185136f829193d4bc Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Mon, 20 Dec 2010 23:46:30 -0700 Subject: make it clear that leaf resource's __getitem__ must always raise KeyError --- docs/narr/resources.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst index 7b31a8260..6350f01ec 100644 --- a/docs/narr/resources.rst +++ b/docs/narr/resources.rst @@ -26,7 +26,7 @@ much less important in applications that use URL dispatch than applications that use traversal. In "Zope-like" :app:`Pyramid` applications, resource objects also often store -data persistently and offer methods related to mutating that persistent data. +data persistently, and offer methods related to mutating that persistent data. In these kinds of applications, resources not only represent the site structure of your website, but they become the :term:`domain model` of the application. @@ -72,8 +72,8 @@ tree: the container's ``__getitem__`` should return the sub-resource. - Leaf resources, which do not contain other resources, must not implement a - ``__getitem__``, or if they do, their ``__getitem__`` method must raise a - :exc:`KeyError`. + ``__getitem__``, or if they do, their ``__getitem__`` method must always + raise a :exc:`KeyError`. See :ref:`traversal_chapter` for more information about how traversal works against resource instances. -- cgit v1.2.3 From c661f30d1ad82baedb2847d5ba65b8f85311e9e5 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Tue, 21 Dec 2010 00:01:07 -0700 Subject: remove word typically to improve flow --- docs/narr/resources.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst index 6350f01ec..2485689a4 100644 --- a/docs/narr/resources.rst +++ b/docs/narr/resources.rst @@ -239,7 +239,7 @@ A slash is appended to all resource URLs when :func:`~pyramid.url.resource_url` is used to generate them in this simple manner, because resources are "places" in the hierarchy, and URLs are meant to be clicked on to be visited. Relative URLs that you include on HTML pages -rendered as the result of the default view of a resource are typically more +rendered as the result of the default view of a resource are more apt to be relative to these resources than relative to their parent. You can also pass extra elements to :func:`~pyramid.url.resource_url`: -- cgit v1.2.3 From 4a10119ecd49316b38c556ef720b515a0c5f7a22 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Tue, 21 Dec 2010 00:07:36 -0700 Subject: add missing space --- docs/narr/resources.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst index 2485689a4..69dbc0af6 100644 --- a/docs/narr/resources.rst +++ b/docs/narr/resources.rst @@ -399,7 +399,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 -- cgit v1.2.3 From a827e04ac308f8005f674bef01d70a842be708cf Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 23 Dec 2010 13:20:39 -0500 Subject: note __resource_url__ requirements --- docs/narr/resources.rst | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'docs/narr') diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst index f90b1eb12..ac88afdfd 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 --------------------------------- -- cgit v1.2.3 From 10fd8fe6bc26a7241542353032540cd4415ee9cc Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 24 Dec 2010 17:09:32 -0500 Subject: - Added "Advanced Configuration" narrative chapter which documents how to deal with configuration conflicts, two-phase configuration, ``include`` and ``commit``. --- docs/narr/advconfig.rst | 373 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 docs/narr/advconfig.rst (limited to 'docs/narr') diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst new file mode 100644 index 000000000..a4692e4c6 --- /dev/null +++ b/docs/narr/advconfig.rst @@ -0,0 +1,373 @@ +.. 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 + 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, , + None, None, None, None, None, False, None, None, None) + ('app.py', 14, '', 'config.add_view(hello_world)') + ('app.py', 17, '', '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`. + +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 can make the application we examined previously which +generated a conflict exception + +.. 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". + +.. _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. +:meth:`pyramid.config.Configurator.commit` has no effect when ``autocommit`` +is ``True`` either. + +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" 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. + +.. _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. + +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. + + -- cgit v1.2.3 From ef492de9cc24c573faaf12d654267480202fe645 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 24 Dec 2010 17:20:00 -0500 Subject: fix --- docs/narr/advconfig.rst | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst index a4692e4c6..2ed0a96ab 100644 --- a/docs/narr/advconfig.rst +++ b/docs/narr/advconfig.rst @@ -164,8 +164,9 @@ Using ``config.commit()`` You can manually commit a configuration by using the :meth:`pyramid.config.Configurator.commit` method between configuration -calls. For example, we can make the application we examined previously which -generated a conflict exception +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: @@ -229,6 +230,10 @@ 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 @@ -246,9 +251,12 @@ configurator constructor parameter: ``autocommit=True``. For example: config = Configurator(autocommit=True) When the ``autocommit`` parameter passed to the Configurator is ``True``, -conflict detection (and :ref:`twophase_config`) is disabled. +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`` either. +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 @@ -267,12 +275,12 @@ 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" 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. +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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From c1b032a5a31a23d28767d2fce66d6a0f5b835791 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 24 Dec 2010 17:31:58 -0500 Subject: internal ordering --- docs/narr/advconfig.rst | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'docs/narr') diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst index 2ed0a96ab..68e3796fe 100644 --- a/docs/narr/advconfig.rst +++ b/docs/narr/advconfig.rst @@ -378,4 +378,10 @@ The same is untrue when you use an *autocommitting* configurator (see 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. -- cgit v1.2.3 From ec0834c7554010f064e836588211f8c671b950be Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 24 Dec 2010 17:33:42 -0500 Subject: internal ordering --- docs/narr/advconfig.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst index 68e3796fe..e096ef863 100644 --- a/docs/narr/advconfig.rst +++ b/docs/narr/advconfig.rst @@ -371,7 +371,9 @@ Has the same result as: 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. +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 -- cgit v1.2.3 From 56db9ebbb2443f213cc38911c83de24a5abbb111 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 24 Dec 2010 22:26:16 -0500 Subject: - 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. --- docs/narr/static.rst | 262 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 158 insertions(+), 104 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/static.rst b/docs/narr/static.rst index 53564a632..e6be5fdff 100644 --- a/docs/narr/static.rst +++ b/docs/narr/static.rst @@ -15,18 +15,18 @@ 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``. +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 that the ``path`` provided to -:meth:`pyramid.config.Configurator.add_static_view` may be a fully qualified -:term:`asset specification`, or an *absolute path*. +.. note:: `~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 under the ``/static`` URL from the ``/var/www/static`` directory of -the computer which runs the :app:`Pyramid` application using an absolute -path. +: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: @@ -34,10 +34,30 @@ path. # 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`. +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``. + +No authorization is ever required for users to visit files served by a static +view added via :meth:`~pyramid.config.Configurator.add_static_view`. If you +need "static" resources to be protected by authentication services, see +:ref:`advanced_static`. + +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. + +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: @@ -45,22 +65,13 @@ qualified :term:`asset specification`. # 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 +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`. @@ -74,39 +85,22 @@ be fed a ``name`` argument which is ``http://example.com/images``: 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. +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:: - 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. + 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 @@ -175,7 +169,8 @@ the the ``path`` given may be ``mypackage:images``: .. code-block:: python :linenos: - config.add_static_view(name='http://example.com/images', path='mypackage:images') + 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 @@ -187,24 +182,61 @@ assets which begin with ``mypackage:images`` will be prefixed with 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, 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. +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 @@ -213,46 +245,68 @@ create an instance of the :class:`pyramid.view.static` class inside a 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. +.. 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 ``/`` using a configuration method in your +application's startup code. .. code-block:: python :linenos: - config.add_view('mypackage.static.static_view', name='static', - context='mypackage.resources.Root') + # .. every other add_route and/or add_handler declaration should come + # before this one, as it will, by default, catch all requests -In this case, ``mypackage.resources.Root`` refers to the class of your -:app:`Pyramid` application's resource tree. + config.add_route('catchall_static', '/*subpath', 'myapp.static.static_view') -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. +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. -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. +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. -.. warning:: +.. code-block:: python + :linenos: + + import os + from pyramid.view import view_config + 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: - 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" (``@@``). + config.add_route('favicon', '/favicon.ico', + view='myapp.views.favicon_view') +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. -- cgit v1.2.3 From 781f9e02e0433a77e78f07f28e5b7d2064e73af4 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 24 Dec 2010 22:56:15 -0500 Subject: fix permission discussion --- docs/narr/static.rst | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/static.rst b/docs/narr/static.rst index e6be5fdff..d4f6da76d 100644 --- a/docs/narr/static.rst +++ b/docs/narr/static.rst @@ -42,16 +42,24 @@ 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``. -No authorization is ever required for users to visit files served by a static -view added via :meth:`~pyramid.config.Configurator.add_static_view`. If you -need "static" resources to be protected by authentication services, see -:ref:`advanced_static`. - 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 -- cgit v1.2.3 From e1a7e0679759da628676f3c73c34875e9b2b6a43 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 26 Dec 2010 01:13:26 -0500 Subject: - Move ZCML usage in Hooks chapter to Declarative Configuration chapter. --- docs/narr/declarative.rst | 140 +++++++++++++- docs/narr/hooks.rst | 470 ++++++++++++++++++++-------------------------- 2 files changed, 338 insertions(+), 272 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/declarative.rst b/docs/narr/declarative.rst index deccb6c48..12deb90e7 100644 --- a/docs/narr/declarative.rst +++ b/docs/narr/declarative.rst @@ -1266,9 +1266,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: + + + +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 + +.. _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: + + + +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: + + + +Or to register a traverser for a specific resource type: -.. Todo -.. ---- +.. code-block:: xml + :linenos: + + + +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: + + + +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: + + + +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: + + -.. - hooks chapter still has topics for ZCML +See :ref:`adding_renderer_globals` for more information. diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 006f5d5cb..381da0d39 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: - + 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. +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 Imperative Configuration +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:`imperative configuration`, you can - replace the Forbidden view by using the - :meth:`pyramid.config.Configurator.add_view` method to - register an "exception view": +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. - -.. topic:: Using 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: +.. 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 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,6 +138,9 @@ 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. +For information about how to configure a forbidden view via :term:`ZCML`, see +:ref:`forbidden_zcml`. + .. index:: single: traverser @@ -189,25 +149,22 @@ Here's some sample code that implements a minimal forbidden view: 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. +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 +.. code-block:: python :linenos: - + from pyramid.interfaces import ITraverser + from zope.interface import Interface + from myapp.traversal import Traverser -In the example above, ``myapp.traversal.Traverser`` is assumed to be -a class that implements the following interface: + 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: @@ -241,31 +198,36 @@ a class that implements the following interface: """ 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 +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 -If the above stanza was added to a ``configure.zcml`` file, -:app:`Pyramid` would use the ``myapp.traversal.Traverser`` only + 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 ------------------------------------------------------------ @@ -276,25 +238,27 @@ 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``: +resource by adding a registerAdapter call for +:class:`pyramid.interfaces.IContextURL` to your application: -.. code-block:: xml +.. 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 asterisk following represents the type -of interface that must be possessed by the :term:`request` (in this -case, any interface, represented by asterisk). +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: @@ -317,28 +281,25 @@ The API that must be implemented by a class that provides 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 -`_ of -the :term:`Pylons` GitHub Pyramid repository. +The default context URL generator is available for perusal as the class +:class:`pyramid.traversal.TraversalContextURL` in the `traversal module +`_ of the +:term:`Pylons` GitHub Pyramid repository. .. _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 +311,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: - - - -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 +327,26 @@ method: config = Configurator() config.set_request_factory(MyRequest) +To use ZCML for the same purpose, see :ref:`changing_request_factory_zcml`. + .. _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 +357,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: - - - -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 +381,9 @@ 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`. + .. _beforerender_event: Using The Before Render Event @@ -472,8 +406,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 @@ -493,16 +427,16 @@ renderer global values exists in :ref:`adding_renderer_globals`. 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 +449,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* @@ -537,13 +471,13 @@ of a :class:`pyramid.events.NewRequest` event). 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 +497,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 @@ -598,22 +531,21 @@ 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: -- cgit v1.2.3 From d29fa9b439554f135e564df5808782fbcf046122 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 26 Dec 2010 01:20:09 -0500 Subject: remove unused import --- docs/narr/static.rst | 1 - 1 file changed, 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/static.rst b/docs/narr/static.rst index d4f6da76d..bf1e57de8 100644 --- a/docs/narr/static.rst +++ b/docs/narr/static.rst @@ -284,7 +284,6 @@ do so, do things "by hand". First define the view callable. :linenos: import os - from pyramid.view import view_config from webob import Response def favicon_view(request): -- cgit v1.2.3 From 74abc0fa8c4f63db0f2fef238e9ef8af16f3c8d3 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 26 Dec 2010 01:46:33 -0500 Subject: reorder chapter --- docs/narr/hooks.rst | 304 ++++++++++++++++++++++++++++------------------------ 1 file changed, 161 insertions(+), 143 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 381da0d39..238ac8328 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -142,149 +142,7 @@ For information about how to configure a forbidden view via :term:`ZCML`, see :ref:`forbidden_zcml`. .. 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 -`_ of the -:term:`Pylons` GitHub Pyramid repository. + single: request factory .. _changing_the_request_factory: @@ -329,6 +187,9 @@ already constructed a :term:`configurator` it can also be registered via the To use ZCML for the same purpose, see :ref:`changing_request_factory_zcml`. +.. index:: + single: renderer globals + .. _adding_renderer_globals: Adding Renderer Globals @@ -384,6 +245,9 @@ 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 @@ -422,6 +286,9 @@ 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 @@ -466,6 +333,9 @@ 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 @@ -525,6 +395,154 @@ 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 +`_ of the +:term:`Pylons` GitHub Pyramid repository. + +.. index:: + single: configuration decorator + .. _registering_configuration_decorators: Registering Configuration Decorators -- cgit v1.2.3 From 88b9ee766bf53ae1c46b8a1889674fea08053622 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 26 Dec 2010 15:57:28 -0500 Subject: - Merge "Static Assets" chapter into the "Assets" chapter. --- docs/narr/assets.rst | 345 ++++++++++++++++++++++++++++++++++++++++++++++++--- docs/narr/static.rst | 319 ----------------------------------------------- 2 files changed, 329 insertions(+), 335 deletions(-) delete mode 100644 docs/narr/static.rst (limited to 'docs/narr') diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index 1932e19ff..a49b401d0 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -37,29 +37,29 @@ Understanding Assets 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 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 :term:`pkg_resources` API to resolve the package name and asset name to an absolute (operating-system-specific) file name. It @@ -67,8 +67,321 @@ eventually passes this resolved absolute filesystem path to the Chameleon templating engine, which then uses it to load, parse, and execute the template file. -Package names often contain dots. For example, ``pyramid`` is a package. -Asset names usually look a lot like relative UNIX file paths. +.. 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:: `~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 ``/`` 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') + +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. + .. index:: pair: overriding; assets diff --git a/docs/narr/static.rst b/docs/narr/static.rst deleted file mode 100644 index bf1e57de8..000000000 --- a/docs/narr/static.rst +++ /dev/null @@ -1,319 +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 a directory of static files available at a name relative to -the application root URL, e.g. ``/static`` or as an external URL. - -.. note:: `~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 ``/`` 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') - -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. -- cgit v1.2.3 From a10437f0de8636b56bc8fc85220b01494d99888b Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 26 Dec 2010 16:57:42 -0500 Subject: wording --- docs/narr/assets.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index a49b401d0..27fbfe613 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -1,10 +1,11 @@ .. 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 @@ -26,19 +27,19 @@ 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: -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.renderers.render_to_response` API. For example, the -application might address the asset using the asset specification +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: -- cgit v1.2.3 From b33ae924870d29f1c1a4c57fde694050a535aba2 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 26 Dec 2010 17:11:57 -0500 Subject: wording --- docs/narr/assets.rst | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index 27fbfe613..84fe42186 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -11,17 +11,21 @@ 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 @@ -31,7 +35,7 @@ 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 Asset Specifications ---------------------------------- -- cgit v1.2.3 From cccde7d6fa003b621383286931540203b66f01f7 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 26 Dec 2010 17:46:02 -0500 Subject: explain relative asset spec --- docs/narr/assets.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index 84fe42186..be3325a11 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -72,6 +72,15 @@ 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. 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. + .. index:: single: add_static_view @@ -143,7 +152,7 @@ specification` as the ``path``: 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*. +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 -- cgit v1.2.3 From 4062f104319f471952657947c8eaa29e0622d1e3 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 26 Dec 2010 17:50:33 -0500 Subject: wording --- docs/narr/assets.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index be3325a11..50879ef22 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -75,11 +75,13 @@ 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. 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. +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 -- cgit v1.2.3 From 7420d3a6983a668b0f2e7ff22be30cff81e0e2b0 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 26 Dec 2010 21:57:09 -0500 Subject: wording --- docs/narr/assets.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index 50879ef22..f147426ce 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -66,11 +66,11 @@ two parts: The two parts are separated by the colon character. -: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. +: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 -- cgit v1.2.3 From d1432f4f3c48106252b292f19442bf591c554fa5 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 27 Dec 2010 00:57:34 -0500 Subject: - Reconcile "extending an existing application" chapter with existence of "advanced configuration" chapter. --- docs/narr/advconfig.rst | 6 + docs/narr/declarative.rst | 66 ++++++-- docs/narr/extending.rst | 420 +++++++++++++++++++++++++--------------------- 3 files changed, 289 insertions(+), 203 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst index e096ef863..f8b3ee191 100644 --- a/docs/narr/advconfig.rst +++ b/docs/narr/advconfig.rst @@ -118,6 +118,9 @@ 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -335,6 +338,9 @@ Instead, use :meth:`pyramid.config.Configuration.include`: 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 diff --git a/docs/narr/declarative.rst b/docs/narr/declarative.rst index 12deb90e7..6654c3dcd 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 @@ -163,6 +162,8 @@ configure your application; instead you need to use :term:`ZCML`. .. index:: single: ZCML conflict detection +.. _zcml_conflict_detection: + ZCML Conflict Detection ~~~~~~~~~~~~~~~~~~~~~~~ @@ -345,6 +346,8 @@ contain other directives. See also :ref:`configure_directive` and :ref:`word_on_xml_namespaces`. +.. _the_include_tag: + The ```` Tag ~~~~~~~~~~~~~~~~~~~~~ @@ -478,6 +481,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 ```` +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 ```` declarations in a +separate ZCML file, as ```` 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 ```` ZCML declaration in the overriding +package's ``configure.zcml``. + .. _zcml_scanning: Scanning via ZCML diff --git a/docs/narr/extending.rst b/docs/narr/extending.rst index 9802a01f6..48a098374 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,64 @@ 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 selectively including or +disincluding the configuration functions you've created from another 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 ```` directive. Views are -declarations made using the ZCML ```` 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 ```` 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 ```` declarations in -a separate ZCML file, as ```` 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 ```` directive). Views are declarations made using the +:meth:`pyramid.config.Configurator.add_view` method (or the ZCML ```` +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 +130,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 ```` 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. ````. - 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 ```` statement, e.g. ````. - -- 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 - ````. - -- 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 +221,44 @@ something like this: Overriding Views ~~~~~~~~~~~~~~~~~ -The ZCML ```` 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 -```` 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 ```` +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: - + 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 ```` -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 +268,27 @@ other page rendering. Overriding Routes ~~~~~~~~~~~~~~~~~ -Route setup is currently typically performed in a sequence of ordered -ZCML ```` 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 ```` 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` ```` declarations. Add -such ```` 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 -```` 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. -- cgit v1.2.3 From 5f0398b63c9f01a4a6ad0664e117b508f9e89ade Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 27 Dec 2010 00:58:55 -0500 Subject: wording --- docs/narr/extending.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/extending.rst b/docs/narr/extending.rst index 48a098374..524dcb2ac 100644 --- a/docs/narr/extending.rst +++ b/docs/narr/extending.rst @@ -99,8 +99,9 @@ You should do move the calls to ``add_view`` outside of the (non-reusable) 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 selectively including or -disincluding the configuration functions you've created from another package. +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 -- cgit v1.2.3 From cfb6c56c2f4ba9fc477de0c461f2b757f6f9d9c1 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 27 Dec 2010 01:01:33 -0500 Subject: remove unnecessary use of begin/end --- docs/narr/declarative.rst | 14 -------------- 1 file changed, 14 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/declarative.rst b/docs/narr/declarative.rst index 6654c3dcd..f36e55b29 100644 --- a/docs/narr/declarative.rst +++ b/docs/narr/declarative.rst @@ -47,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') @@ -82,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') @@ -98,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') @@ -225,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') @@ -270,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') @@ -288,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') @@ -545,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') -- cgit v1.2.3 From d30a83540d98bb803caca74b94ae75f88dd3154b Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 27 Dec 2010 01:05:14 -0500 Subject: remove inappropriate use of begin/end --- docs/narr/hooks.rst | 2 -- docs/narr/i18n.rst | 5 ----- 2 files changed, 7 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 238ac8328..2917b5254 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -621,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` -- cgit v1.2.3 From 90a327b2cd9b9e6b27688dadcdf8125f091f242d Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 27 Dec 2010 16:25:15 -0500 Subject: - 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". - Added narrative documentation section within the "URL Dispatch" chapter entitled "Displaying All Application Routes" (for ``paster proutes`` command). --- docs/narr/project.rst | 2 ++ docs/narr/urldispatch.rst | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) (limited to 'docs/narr') diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 36f2d6975..55a2711f3 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -256,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/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 / + home2 / + another /another None + static/ static/*subpath + catchall /*subpath + +``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 ---------- -- cgit v1.2.3 From b6f7b34c95ae96cb70d583211e40a8b4300dea17 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Wed, 29 Dec 2010 00:26:10 +0200 Subject: Typo: cam -> can. --- docs/narr/flash.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/flash.rst b/docs/narr/flash.rst index d41c2cdaf..0bffd6ddf 100644 --- a/docs/narr/flash.rst +++ b/docs/narr/flash.rst @@ -38,7 +38,7 @@ provide is not modified in any way. 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 cam +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. -- cgit v1.2.3 From 2c29ef9007681902575384157eea8b36d53cca30 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Wed, 29 Dec 2010 00:26:46 +0200 Subject: Rephrase sentence with no verb. --- docs/narr/flash.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/flash.rst b/docs/narr/flash.rst index 0bffd6ddf..ad50dc82b 100644 --- a/docs/narr/flash.rst +++ b/docs/narr/flash.rst @@ -49,7 +49,7 @@ default flash message queue. request.session.flash(msg, 'myappsqueue') -The ``allow_duplicate`` argument, which defaults to ``True``. If this is +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. -- cgit v1.2.3 From 87e5443d655b745ba7a5a675bdd52027a2240d11 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Wed, 29 Dec 2010 00:29:11 +0200 Subject: "one or more has" -> "one or more have". Sounds more natural to me, and Googling for "one or more: singular or plural" seems to agree. --- docs/narr/flash.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/flash.rst b/docs/narr/flash.rst index ad50dc82b..d009da4fb 100644 --- a/docs/narr/flash.rst +++ b/docs/narr/flash.rst @@ -56,7 +56,7 @@ present in the queue, it will not be added. Using the ``session.pop_flash`` Method -------------------------------------- -Once one or more messages has been added to a flash queue by the +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. -- cgit v1.2.3 From 133f1231b1288e20777b56c82d5c5b7730010eab Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Wed, 29 Dec 2010 00:36:21 +0200 Subject: Show the method signatures of pop_flash/peek_flash. The narrative referred to popping messages from a particular queue, but the code examples didn't show how to indicate which queue you were interested in, leaving the reader a bit confused. --- docs/narr/flash.rst | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'docs/narr') diff --git a/docs/narr/flash.rst b/docs/narr/flash.rst index d009da4fb..71c6cf305 100644 --- a/docs/narr/flash.rst +++ b/docs/narr/flash.rst @@ -63,6 +63,8 @@ queue and return it for use. To pop a particular queue of messages from the flash object, use the session object's ``pop_flash`` method. +.. method:: pop_flash(queue='') + .. code-block:: python :linenos: @@ -93,6 +95,8 @@ Once one or more messages has been added to a flash queue by the at that queue. Unlike ``session.pop_flash``, the queue is not popped from flash storage. +.. method:: peek_flash(queue='') + .. code-block:: python :linenos: -- cgit v1.2.3 From a025f36fe9c875332d68cfcf9fe9d6398c851c57 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Wed, 29 Dec 2010 00:59:57 +0200 Subject: Added missing :meth:. --- docs/narr/assets.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index f147426ce..3d3498e26 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -98,7 +98,7 @@ directory on a filesystem to an application user's browser. Use the 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:: `~pyramid.config.Configurator.add_static_view` cannot serve a +.. 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`. -- cgit v1.2.3 From 1e209fc65d62a86d15bf54fa2a91a6728d0ade9e Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Wed, 29 Dec 2010 01:04:15 +0200 Subject: Fix quoting. --- docs/narr/assets.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index 3d3498e26..f73ff231a 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -282,7 +282,7 @@ create such a circumstance, we suggest using the 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 +``.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. -- cgit v1.2.3 From 203e848985fca05263893132896b552ccbeaa4b0 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Wed, 29 Dec 2010 01:36:06 +0200 Subject: Markup fix. --- docs/narr/resources.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst index ac88afdfd..3962e9e00 100644 --- a/docs/narr/resources.rst +++ b/docs/narr/resources.rst @@ -404,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 -- cgit v1.2.3 From 5e2c481dda9c69477749693c3563255b94f25990 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Wed, 29 Dec 2010 01:41:55 +0200 Subject: Whitespace bigotry. --- docs/narr/resources.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst index 3962e9e00..8cf2cead2 100644 --- a/docs/narr/resources.rst +++ b/docs/narr/resources.rst @@ -533,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() @@ -568,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() @@ -596,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() -- cgit v1.2.3 From 2a922b4cf51d3fe083b33d375282c227f90a3e4e Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 29 Dec 2010 01:59:22 -0500 Subject: fix misleading example --- docs/narr/renderers.rst | 1 + 1 file changed, 1 insertion(+) (limited to 'docs/narr') diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index 3804fcf42..76e9562fa 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -22,6 +22,7 @@ response. For example: from pyramid.response import Response from pyramid.view import view_config + @view_config(renderer='json') def hello_world(request): return {'content':'Hello!'} -- cgit v1.2.3 From aca6c0df04533f98ad423fc2877276b69b1ba2e0 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 29 Dec 2010 02:01:29 -0500 Subject: resource->asset --- docs/narr/introduction.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 3ade3726c..c61ef21d4 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -63,7 +63,7 @@ A Sense of Fun Minimalism :app:`Pyramid` provides only the very basics: *URL to code - mapping*, *templating*, *security*, and *resources*. There is not + mapping*, *templating*, *security*, and *assets*. There is not much more to the framework than these pieces: you are expected to provide the rest. -- cgit v1.2.3 From ebceb116dd201ad7058e533bf133fe6dfede16d6 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 29 Dec 2010 02:10:01 -0500 Subject: git title right --- docs/narr/flash.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/flash.rst b/docs/narr/flash.rst index 71c6cf305..037bfc416 100644 --- a/docs/narr/flash.rst +++ b/docs/narr/flash.rst @@ -87,8 +87,8 @@ been popped. The object returned from ``pop_flash`` is a list. -Using the ``session.pop_flash`` Method --------------------------------------- +Using the ``session.peek_flash`` Method +--------------------------------------- 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" -- cgit v1.2.3 From 0e525ce7315e9962372267966eda6bc76ff99f02 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 29 Dec 2010 03:37:34 -0500 Subject: typo --- docs/narr/csrf.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/csrf.rst b/docs/narr/csrf.rst index 7586b0ed7..2f545fb4f 100644 --- a/docs/narr/csrf.rst +++ b/docs/narr/csrf.rst @@ -9,7 +9,7 @@ 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 +You can avoid most of these attacks by making sure that 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` -- cgit v1.2.3 From 581a401c26047a6cddb6521393de4030ce0a962a Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 29 Dec 2010 03:48:51 -0500 Subject: fix from mike --- docs/narr/urldispatch.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 4c601340f..0d28a0e96 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -533,12 +533,12 @@ neither predicates nor view configuration information. callables. Use custom predicates when no set of predefined predicates does what you need. Custom predicates can be combined with predefined predicates as necessary. Each custom predicate callable should accept two - arguments: ``context`` and ``request`` and should return either ``True`` or + arguments: ``info`` and ``request`` and should return either ``True`` or ``False`` after doing arbitrary evaluation of the context resource and/or the request. If all callables return ``True``, the associated route will be considered viable for a given request. If any custom predicate returns - ``False``, route matching continues. Note that the value ``context`` will - always be ``None`` when passed to a custom route predicate. + ``False``, route matching continues. See :ref:`custom_route_predicates` + for more information. **View-Related Arguments** -- cgit v1.2.3 From f4c5f1a60612749ef36aae01d9a3a559b6acdfff Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Fri, 31 Dec 2010 13:48:07 -0700 Subject: add Much ado about traversal chapter from Rob Miller, with light adaptations. Also remove some now redundant overview content in the Traversal chapter, which is now only details. --- docs/narr/muchadoabouttraversal.rst | 293 ++++++++++++++++++++++++++++++++++++ docs/narr/traversal.rst | 50 +++--- 2 files changed, 312 insertions(+), 31 deletions(-) create mode 100644 docs/narr/muchadoabouttraversal.rst (limited to 'docs/narr') diff --git a/docs/narr/muchadoabouttraversal.rst b/docs/narr/muchadoabouttraversal.rst new file mode 100644 index 000000000..52b6dd3a7 --- /dev/null +++ b/docs/narr/muchadoabouttraversal.rst @@ -0,0 +1,293 @@ +.. _much_ado_about_traversal_chapter: + +======================== +Much Ado About Traversal +======================== + +Introduction +------------ + +A lot of folks who have been using Pylons (and, therefore, Routes-based +URL matching) are being exposed for the first time, via :app:`Pyramid`, +to new ideas such as ":term:`traversal`" and ":term:`view lookup`" as a +way to route incoming HTTP requests to callable code. This has caused a +bit of consternation in some circles. Many think that traversal is hard +to understand. Others question its usefulness; URL matching has worked +for them so far, why should they even consider dealing with another +approach, one which doesn't fit their brain and which doesn't provide +any immediately obvious value? + +This chapter is an attempt to counter these opinions. Traversal and +view lookup *are* useful. There are some straightforward, real-world +use cases that are much more easily served by a traversal-based approach +than by a pattern-matching mechanism. Even if you haven't yet hit one +of these use cases yourself, understanding these new ideas is worth the +effort for any web developer so you know when you might want to use +them. Especially because (WARNING: Bold Assertion Ahead) these ideas +are *not* particularly hard to understand. In fact, :term:`traversal` +is a straightforward metaphor easily comprehended by anyone who's ever +used a run-of-the-mill file system with folders and files. + +.. note:: + + Those of you who are already familiar with traversal and view lookup + conceptually, may want to skip directly to the + :ref:`traversal_chapter` chapter, which discusses the technical + details. + +URL Matching +------------ + +Let's take a step back. The problem we're trying to solve is +simple. We have an HTTP request for a particular path that +has been routed to our web application. The requested path will +possibly invoke a specific callable function defined somewhere in our +app, or it may point to nothing in which case a 404 response should be +generated. What we're trying to do is figure out is which callable +function, if any, should be invoked for a given requested path. + +URL matching (or :term:`URL dispatch` in :app:`Pyramid` parlance) +approaches this problem by parsing the URL path and comparing the +results to a set of registered "patterns", defined by a set of regular +expressions, or some other URL path templating syntax. Each pattern is +mapped to a callable function somewhere; if the request path matches a +specific pattern, the associated function is called. If the request +path matches more than one pattern, some conflict resolution scheme is +used, usually a simple order precedence so that the first match will +take priority over any subsequent matches. If a request path doesn't +match any of the defined patterns, we've got a 404. + +Just in case it's not crystal clear, we'll give an example. Using +:app:`Pyramid`'s syntax, we might have a match pattern such as +``/{userid}/photos/{photoid}``, mapped to a ``photo_view()`` function +defined somewhere in our code. Then a request for a path such as +``/joeschmoe/photos/photo1`` would be a match, and the ``photo_view()`` +function would be invoked to handle the request. Similarly, +``/{userid}/blog/{year}/{month}/{postid}`` might map to a +``blog_post_view()`` function, so +``/joeschmoe/blog/2010/12/urlmatching`` would trigger the function, +which presumably would know how to find and render the ``urlmatching`` +blog post. + +Historical Refresher +-------------------- + +Okay, we've got :term:`URL dispatch` out of the way, soon we'll dig in +to the supposedly "harder to understand" idea of traversal. Before we +do, though, let's take a trip down memory lane. If you've been doing +web work for a while, you may remember a time when we didn't have these +fancy web frameworks. Instead, we had general purpose HTTP servers that +primarily served files off of a file system. The "root" of a given site +mapped to a particular folder somewhere on the file system. Each +segment of the request path represented a subdirectory. The final path +segment would be either a directory or a file, and once the server found +the right file it would package it up in an HTTP response and send it +back to the client. So serving up a request for +``/joeschmoe/photos/photo1`` literally meant that there was a +``joeschmoe`` folder somewhere, which contained a ``photos`` folder, +which in turn contained a ``photo1`` file. If at any point along the +way we find that there is not a folder or file matching the requested +path, we return a 404 response. + +As the web grew more dynamic, however, a little bit of extra +complexity was added. Technologies such as CGI and HTTP server +modules were developed. Files were still looked up on the file +system, but if the file ended with (for example) ``.cgi`` or ``.php``, +or if it lived in a special folder, instead of simply sending the file +to the client the server would read the file, execute it using an +interpreter of some sort, and then send the output from this process +to the client as the final result. The server configuration specified +which files would trigger some dynamic code, with the default case +being to just serve the static file. + +Traversal (aka Resource Location) +--------------------------------- + +You with me so far? Good. Because if you understand how serving +files from a file system works, then you pretty much understand +traversal. And if you understand that a server might do something +different based on what type of file a given request specifies, then +you pretty much understand view lookup. + +Wait... what!?! + +.. index:: + single: traversal overview + +The only difference between file system lookup and traversal is that a +file system lookup is stepping through nested directories and files in +a file system tree, while traversal is stepping through nested +dictionary-type objects in an object tree. Let's take a detailed look +at one of our example paths, so we can see what I mean: + +With ``/joeschmoe/photos/photo1``, we've got 4 segments: ``/``, +``joeschmoe/``, ``photos/`` and ``photo1``. With file system +lookup we have a root folder (``/``) containing a nested folder +(``joeschmoe``), which contains ANOTHER nested folder (``photos``), +which finally contains a JPG file ("photo1"). With traversal, we +have a dictionary-like root object. Asking for the ``joeschmoe`` key +gives us another dictionary-like object. Asking this in turn for the +``photos`` key gives us yet another mapping object, which finally +(hopefully) contains the resource that we're looking for within its +values, referenced by the ``photo1`` key. + +In pure Python terms, then, the traversal or "resource location" +portion of satisfying the ``/joeschmoe/photos/photo1`` request +will look like this:: + + get_root()['joeschmoe']['photos']['photo1'] + +Where ``get_root()`` is some function that returns our root traversal +resource. If all of the specified keys exist, then the returned object +will be the resource that is being requested, analogous to the JPG file +that was retrieved in the file system example. If a :exc:`KeyError` is +generated anywhere along the way, we get a 404. (Well, this isn't +precisely true, as you'll see when we learn about view lookup below, but +the basic idea holds.) + +What is a "resource"? +--------------------- + +Okay, okay... files on a file system I understand, you might say. But +what are these nested dictionary things? Where do these objects, these +"resources", live? What *are* they? + +Well, since :app:`Pyramid` is not a highly opinionated framework, there +is no restriction on how a resource is implemented; the developer can do +whatever he wants. One common pattern is to persist all of the +resources, including the root, in a database. The root object stores +the ids of all of its subresources, and provides a ``__getitem__`` +implementation that fetches them. So ``get_root()`` fetches the unique +root object, while ``get_root()['joeschmoe']`` returns a different +object, also stored in the database, which in turn has its own +subresources and ``__getitem__`` implementation, etc. These resources +could be persisted in a relational database, one of the many "NoSQL" +solutions that are becoming popular these days, or anywhere else, it +doesn't matter. As long as the returned objects provide the +dictionary-like API (i.e. as long as they have an appropriately +implemented ``__getitem__`` method) then traversal will work. + +In fact, you don't need a "database" at all. You could trivially +implement a set of objects with ``__getitem__`` methods that search +for files in specific directories, and thus precisely recreate the +older mechanism of having the URL path mapped directly to a folder +structure on the file system. Traversal is in fact a superset of file +system lookup. + +View Lookup +----------- + +At this point we're nearly there. We've covered traversal, which is +the process by which a specific resource is retrieved according to a +specific URL path. But what is this "view lookup" business? + +View lookup comes from a simple realization, namely, that there is more +than one possible action that you might want to take for a single +resource. With our photo example, for instance, you might want to view +the photo in a page, but you might also want to provide a way for the +user to edit the photo and any associated metadata. We'll call the +former the ``view`` view, and the latter will be the ``edit`` view +(Original, I know.) :app:`Pyramid` has a centralized view registry +where named views can be associated with specific resource types. So in +our example, we'll assume that we've registered ``view`` and ``edit`` +views for photo objects, and that we've specified the ``view`` view as +the default, so that ``/joeschmoe/photos/photo1/view`` and +``/joeschmoe/photos/photo1`` are equivalent. The edit view would +sensibly be provided by a request for ``/joeschmoe/photos/photo1/edit``. + +Hopefully it's clear that the first portion of the edit view's URL path +is going to resolve to the same resource as the non-edit version, +specifically the resource returned by +``get_root()['joeschmoe']['photos']['photo1']``. But traveral ends +there; the ``photo1`` resource doesn't have an ``edit`` key. In fact, +it might not even be a dictionary-like object, in which case +``photo1['edit']`` would be meaningless. When :app:`Pyramid`'s resource +location has resolved to a *leaf* resource but the entire request path +has not yet been expended, the next path segment is treated as a view +name. The registry is then checked to see if a view of the given name +has been specified for a resource of the given type. If so, the view +callable is invoked, with the resource passed in as the ``context`` +object; if not, we 404. + +This is a slight simplification, but to summarize you can think of a +request for ``/joeschmoe/photos/photo1/edit`` as ultimately converted +into the following piece of Python:: + + context = get_root()['joeschmoe']['photos']['photo1'] + view_callable = registry.get_view(context, 'edit') + view_callable(context, request) + +That's not too hard to conceptualize, is it? + +Use Cases +--------- + +Let's come back around to look at why we even care. Yes, maybe +traversal and view lookup isn't mind-bending rocket science. But URL +matching is easier to explain, and it's good enough, right? + +In some cases, yes, but certainly not in all cases. So far we've had +very structured URLs, where our paths have had a specific, small +number of pieces, like this:: + + /{userid}/{typename}/{objectid}[/{view_name}] + +In all of the examples thus far, we've hard coded the typename value, +assuming that we'd know at development time what names were going to +be used ("photos", "blog", etc.). But what if we don't know what +these names will be? Or, worse yet, what if we don't know *anything* +about the structure of the URLs inside a user's folder? We could be +writing a CMS where we want the end user to be able to arbitrarily add +content and other folders inside his folder. He might decide to nest +folders dozens of layers deep. How would you construct matching +patterns that could account for every possible combination of paths +that might develop? + +It may be possible, but it's tricky at best. And your matching +patterns are going to become quite complex very quickly as you try +to handle all of the edge cases. + +With traversal, however, it's straightforward. You want 20 layers of +nesting? No problem, :app:`Pyramid` will happily call ``__getitem__`` +as long as it needs to, until it runs out of path segments or until it +gets a :exc:`KeyError`. Each resource only needs to know how to fetch +its immediate children, the traversal algorithm takes care of the rest. + +The key advantage of traversal here is that the structure of the +resource tree can live in the database, and not in the code. It's +simple to let users modify the tree at runtime to set up their own +personalized directory structures. + +Another use case in which traversal shines is when there is a need to +support a context-dependent security policy. One example might be a +document management infrastructure for a large corporation, where +members of different departments have varying access levels to the +various other departments' files. Reasonably, even specific files +might need to be made available to specific individuals. Traversal +does well here because the idea of a resource context is baked right +into the code resolution and calling process. Resource objects can +store ACLs, which can be inherited and/or overridden by the +subresources. + +If each resource can thus generate a context-based ACL, then whenever +view code is attempting to perform a sensitive action, it can check +against that ACL to see whether the current user should be allowed to +perform the action. In this way you achieve so called "instance based" +or "row level" security which is considerably harder to model using a +traditional tabular approach. :app:`Pyramid` actively supports such a +scheme, and in fact if you register your views with guard permissions +and use an authorization policy, :app:`Pyramid` can check against a +resource's ACL when deciding whether or not the view itself is available +to the current user. + +In summary, there are entire classes of problems that are more easily +served by traversal and view lookup than by :term:`URL dispatch`. If +your problems aren't of this nature, great, stick with :term:`URL +dispatch`. But if you're using :app:`Pyramid` and you ever find that +you *do* need to support one of these use cases, you'll be glad you have +traversal in your toolkit. + +.. note:: + It is even possible to mix and match :term:`traversal` with + :term:`URL dispatch` in the same :app:`Pyramid` application. See the + :ref:`hybrid_chapter` chapter for details. diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index 2d7878265..e8949880c 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -3,34 +3,22 @@ Traversal ========= -:term:`Traversal` provides an alternative to using :term:`URL dispatch` to -map a URL to a :term:`view callable`. It is the act of locating a -:term:`context` resource by walking over a :term:`resource tree`, starting -from a :term:`root` resource, using a :term:`request` object as a source of -path information. Once a context resource is found, a view callable is -looked up and invoked. - -Using :term:`Traversal` to map a URL to code is optional. It is often less -easy to understand than URL dispatch, so if you're a rank beginner, it -probably makes sense to use URL dispatch to map URLs to code instead of -traversal. In that case, you can skip this chapter. - -.. index:: - single: traversal overview - -A High-Level Overview of Traversal ----------------------------------- - A :term:`traversal` uses the URL (Universal Resource Locator) to find a -:term:`resource`. This is done by mapping each segment of the path portion -of the URL into a set of nested dictionary-like objects called the -:term:`resource tree`. You might think of this as looking up files and -directories in a file system. Traversal walks down the path until it finds a -published "directory" or "file". The resource we find as the result of a -traversal becomes the :term:`context`. A separate :term:`view lookup` -subsystem is used to then find some view code willing "publish" the context +:term:`resource` located in a :term:`resource tree`, which is a set of +nested dictionary-like objects. Traversal is done by using each segment +of the path portion of the URL to navigate through the :term:`resource +tree`. You might think of this as looking up files and directories in a +file system. Traversal walks down the path until it finds a published +"directory" or "file". The resource we find as the result of a +traversal becomes the :term:`context`. Then, the :term:`view lookup` +subsystem is used to find some view code willing "publish" this resource. +Using :term:`Traversal` to map a URL to code is optional. It is often +less easy to understand than :term:`URL dispatch`, so if you're a rank +beginner, it probably makes sense to use URL dispatch to map URLs to +code instead of traversal. In that case, you can skip this chapter.` + .. index:: single: traversal details @@ -76,7 +64,7 @@ element cannot be resolved to a resource. In either case, a :term:`context` resource is chosen. Traversal "stops" when it either reaches a leaf level resource in your -resource tree or when the path segments implied by the URL "run out". The +resource tree or when the path segments from the URL "run out". The resource that traversal "stops on" becomes the :term:`context`. If at any point during traversal any resource in the tree doesn't have a ``__getitem__`` method, or if the ``__getitem__`` method of a resource raises @@ -88,11 +76,11 @@ The results of a :term:`traversal` also include a :term:`view name`. The segments "left over" in the path segment list popped by the traversal process *after* traversal finds a context resource. -The combination of the context resource and the :term:`view name` found via -traversal is used later in the same request by a separate :app:`Pyramid` -subsystem -- the :term:`view lookup` subsystem -- to find a :term:`view -callable` later within the same request. How :app:`Pyramid` performs view -lookup is explained within the :ref:`views_chapter` chapter. +The combination of the context resource and the :term:`view name` found +via traversal is used later in the same request by the :term:`view +lookup` subsystem to find a :term:`view callable`. How :app:`Pyramid` +performs view lookup is explained within the :ref:`views_chapter` +chapter. .. index:: single: object tree -- cgit v1.2.3 From f4e5b83c671307c23203ed94916ce04ea29b4329 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Fri, 31 Dec 2010 13:52:51 -0700 Subject: remove stray quote --- docs/narr/traversal.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index e8949880c..9226cf411 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -17,7 +17,7 @@ resource. Using :term:`Traversal` to map a URL to code is optional. It is often less easy to understand than :term:`URL dispatch`, so if you're a rank beginner, it probably makes sense to use URL dispatch to map URLs to -code instead of traversal. In that case, you can skip this chapter.` +code instead of traversal. In that case, you can skip this chapter. .. index:: single: traversal details -- cgit v1.2.3 From 26bb68f8f351ce8481ee066b5108274320794c58 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Fri, 31 Dec 2010 13:57:52 -0700 Subject: remove heading at top --- docs/narr/muchadoabouttraversal.rst | 3 --- 1 file changed, 3 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/muchadoabouttraversal.rst b/docs/narr/muchadoabouttraversal.rst index 52b6dd3a7..13d279a42 100644 --- a/docs/narr/muchadoabouttraversal.rst +++ b/docs/narr/muchadoabouttraversal.rst @@ -4,9 +4,6 @@ Much Ado About Traversal ======================== -Introduction ------------- - A lot of folks who have been using Pylons (and, therefore, Routes-based URL matching) are being exposed for the first time, via :app:`Pyramid`, to new ideas such as ":term:`traversal`" and ":term:`view lookup`" as a -- cgit v1.2.3 From 597a6d7a6cca0865e114359010d3e15f5fb036b1 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Fri, 31 Dec 2010 14:01:39 -0700 Subject: be more gender-neutral --- docs/narr/muchadoabouttraversal.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/muchadoabouttraversal.rst b/docs/narr/muchadoabouttraversal.rst index 13d279a42..fcefb9811 100644 --- a/docs/narr/muchadoabouttraversal.rst +++ b/docs/narr/muchadoabouttraversal.rst @@ -151,7 +151,7 @@ what are these nested dictionary things? Where do these objects, these Well, since :app:`Pyramid` is not a highly opinionated framework, there is no restriction on how a resource is implemented; the developer can do -whatever he wants. One common pattern is to persist all of the +whatever they want. One common pattern is to persist all of the resources, including the root, in a database. The root object stores the ids of all of its subresources, and provides a ``__getitem__`` implementation that fetches them. So ``get_root()`` fetches the unique -- cgit v1.2.3 From 9f33f3b04079bf70482227afbbbbe86e325a5b8a Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Fri, 31 Dec 2010 14:03:37 -0700 Subject: URL Matching to URL Dispatch --- docs/narr/muchadoabouttraversal.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/muchadoabouttraversal.rst b/docs/narr/muchadoabouttraversal.rst index fcefb9811..9bd829754 100644 --- a/docs/narr/muchadoabouttraversal.rst +++ b/docs/narr/muchadoabouttraversal.rst @@ -32,7 +32,7 @@ used a run-of-the-mill file system with folders and files. :ref:`traversal_chapter` chapter, which discusses the technical details. -URL Matching +URL Dispatch ------------ Let's take a step back. The problem we're trying to solve is -- cgit v1.2.3 From 9476cf70cb2eb27be79c1c973ffea70dd7eab808 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Fri, 31 Dec 2010 14:16:36 -0700 Subject: remove rendundant sentence that was redundant --- docs/narr/traversal.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index 9226cf411..fe0dc6bed 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -95,8 +95,7 @@ The Resource Tree When your application uses :term:`traversal` to resolve URLs to code, the application must supply a :term:`resource tree` to :app:`Pyramid`. The resource tree is a set of nested dictionary-like objects. The root of the -tree is represented by a :term:`root` resource. The tree is effectively a -nested set of dictionary-like objects. +tree is represented by a :term:`root` resource. In order to supply a root resource for an application, at system startup time, the :app:`Pyramid` :term:`Router` is configured with a callback known -- cgit v1.2.3 From e653d5db0e2bf20b25024eb9bdb55aa35b3f2012 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Fri, 31 Dec 2010 14:17:52 -0700 Subject: clarify --- docs/narr/traversal.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index fe0dc6bed..65e9ae3c7 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -125,7 +125,7 @@ constructor tells your :app:`Pyramid` application to call this root factory to generate a root resource whenever a request enters the application. This root factory is also known as the global root factory. A root factory can alternately be passed to the ``Configurator`` as a :term:`dotted Python name` -which refers to a root factory defined in a different module. +which can refer to a root factory defined in a different module. A root factory is passed a :term:`request` object and it is expected to return an object which represents the root of the resource tree. All -- cgit v1.2.3 From 45cc6fcd6a8e4c093e734f7fdbc69878d4df2bb4 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Fri, 31 Dec 2010 14:19:15 -0700 Subject: clarify that the default root resource is empty --- docs/narr/traversal.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index 65e9ae3c7..fa8d7d7ee 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -137,7 +137,7 @@ or another persistence mechanism. If no :term:`root factory` is passed to the :app:`Pyramid` :term:`Configurator` constructor, or the ``root_factory`` is specified as the value ``None``, a *default* root factory is used. The default root factory -always returns a resource that has no child resources. +always returns a resource that has no child resources; it is effectively empty. .. sidebar:: Emulating the Default Root Factory -- cgit v1.2.3 From ee50aec09576620537ff68895cfb81fd4663a45f Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Fri, 31 Dec 2010 18:24:18 -0700 Subject: Remove resource location chapter and move intro parts to url dispatch. The new much ado about traversal chapter takes care of selling traversal now --- docs/narr/firstapp.rst | 2 +- docs/narr/resourcelocation.rst | 103 ----------------------------------------- docs/narr/router.rst | 2 +- docs/narr/security.rst | 3 +- docs/narr/urldispatch.rst | 25 +++++++--- docs/narr/views.rst | 10 ++-- 6 files changed, 26 insertions(+), 119 deletions(-) delete mode 100644 docs/narr/resourcelocation.rst (limited to 'docs/narr') diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst index cb1e54b19..1a81134f5 100644 --- a/docs/narr/firstapp.rst +++ b/docs/narr/firstapp.rst @@ -10,7 +10,7 @@ more detail how it works. .. note:: If you're a "theory-first" kind of person, you might choose to read - :ref:`resourcelocation_chapter` and :ref:`views_chapter` before diving into + :ref:`urldispatch_chapter` and :ref:`views_chapter` before diving into the code that follows, but it's not necessary if -- like many programmers -- you're willing to "go with the flow". diff --git a/docs/narr/resourcelocation.rst b/docs/narr/resourcelocation.rst deleted file mode 100644 index 8ddc890ed..000000000 --- a/docs/narr/resourcelocation.rst +++ /dev/null @@ -1,103 +0,0 @@ -.. index:: - single: resource location - -.. _resourcelocation_chapter: - -Resource Location and View Lookup ---------------------------------- - -:app:`Pyramid` uses two separate but cooperating subsystems to find and -invoke :term:`view callable` code written by the application developer: -:term:`resource location` and :term:`view lookup`. - -- First, a :app:`Pyramid` :term:`resource location` subsystem is given a - :term:`request`; it is responsible for finding a :term:`resource` object - based on information present in the request. When a resource is found via - resource location, it becomes known as the :term:`context`. - -- Next, using the context resource found by :term:`resource location` and the - :term:`request`, :term:`view lookup` is then responsible for finding and - invoking a :term:`view callable`. A view callable is a specific bit of - code written and registered by the application developer which receives the - :term:`request` and which returns a :term:`response`. - -These two subsystems are used by :app:`Pyramid` serially: first, a -:term:`resource location` subsystem does its job. Then the result of -resource location is passed to the :term:`view lookup` subsystem. The view -lookup system finds a :term:`view callable` written by an application -developer, and invokes it. A view callable returns a :term:`response`. The -response is returned to the requesting user. - -There are two separate :term:`resource location` subsystems in -:app:`Pyramid`: :term:`traversal` and :term:`URL dispatch`. They can be used -separately or they can be combined. Three chapters which follow describe -:term:`resource location`: :ref:`traversal_chapter`, -:ref:`urldispatch_chapter` and :ref:`hybrid_chapter`. - -There is only one :term:`view lookup` subsystem present in :app:`Pyramid`. -Where appropriate, we will describe how view lookup interacts with context -finding. One chapter which follows describes :term:`view lookup`: -:ref:`views_chapter`. - -Should I Use Traversal or URL Dispatch for Resource Location? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When you use :app:`Pyramid`, you have a choice about how you'd like to -resolve URLs to code: you can use either :term:`traversal` or :term:`URL -dispatch`. The choice to use traversal vs. URL dispatch is largely -"religious". Since :app:`Pyramid` provides support for both approaches, you -can use either exclusively or combine them as you see fit. - -:term:`URL dispatch` is very straightforward. When you limit your -application to using URL dispatch, you know every URL that your application -might generate or respond to, all the URL matching elements are listed in a -single place, and you needn't think about :term:`resource location` or -:term:`view lookup` at all. - -URL dispatch can easily handle URLs such as -``http://example.com/members/Chris``, where it's assumed that each item -"below" ``members`` in the URL represents a single member in some system. -You just match everything "below" ``members`` to a particular :term:`view -callable`, e.g. ``/members/{memberid}``. - -However, URL dispatch is not very convenient if you'd like your URLs to -represent an arbitrary-depth hierarchy. For example, if you need to infer -the difference between sets of URLs such as these, where the ``document`` in -the first URL represents a PDF document, and ``/stuff/page`` in the second -represents an OpenOffice document in a "stuff" folder. - -.. code-block:: text - - http://example.com/members/Chris/document - http://example.com/members/Chris/stuff/page - -It takes more pattern matching assertions to be able to make hierarchies work -in URL-dispatch based systems, and some assertions just aren't possible. -URL-dispatch based systems just don't deal very well with URLs that represent -arbitrary-depth hierarchies. - -:term:`URL dispatch` tends to collapse the two steps of :term:`resource -location` and :term:`view lookup` into a single step. Thus, a URL can map -*directly* to a view callable. This makes URL dispatch easier to understand -than traversal, because traversal makes you understand how :term:`resource -location` works. But explicitly locating a resource provides extra -flexibility. For example, it makes it possible to protect your application -with declarative context-sensitive instance-level :term:`authorization`. - -Unlike URL dispatch, :term:`traversal` works well for URLs that represent -arbitrary-depth hierarchies. Since the path segments that compose a URL are -addressed separately, it becomes very easy to form URLs that represent -arbitrary depth hierarchies in a system that uses traversal. When you're -willing to treat your application resources as a tree that can be traversed, -it also becomes easy to provide "instance-level security": you just attach an -:term:`ACL` security declaration to each resource in the tree. This is not -nearly as easy to do when using URL dispatch. - -Traversal probably just doesn't make any sense when you possess completely -"square" data stored in a relational database because it requires the -construction and maintenance of a resource tree and requires that the -developer think about mapping URLs to code in terms of traversing that tree. - -We'll examine both :term:`URL dispatch` and :term:`traversal` in the next two -chapters. - diff --git a/docs/narr/router.rst b/docs/narr/router.rst index d3d5bd370..f9e98373c 100644 --- a/docs/narr/router.rst +++ b/docs/narr/router.rst @@ -133,6 +133,6 @@ processing? This is a very high-level overview that leaves out various details. For more detail about subsystems invoked by the :app:`Pyramid` router such as traversal, URL dispatch, views, and event processing, see -:ref:`resourcelocation_chapter`, :ref:`views_chapter`, and +:ref:`urldispatch_chapter`, :ref:`views_chapter`, and :ref:`events_chapter`. diff --git a/docs/narr/security.rst b/docs/narr/security.rst index c5262faa2..62a4727bc 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -18,8 +18,7 @@ works at a high level: :term:`resource location`. A context is located differently depending on whether the application uses :term:`traversal` or :term:`URL dispatch`, but a context is ultimately found in either case. See - :ref:`resourcelocation_chapter` for more information about resource - location. + the :ref:`urldispatch_chapter` chapter for more information. - A :term:`view callable` is located by :term:`view lookup` using the context as well as other attributes of the request. diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 0d28a0e96..d9228bf52 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -9,13 +9,24 @@ URL Dispatch :term:`URL dispatch` provides a simple way to map URLs :term:`view` code using a simple pattern matching language. An ordered set of patterns is checked one-by-one. If one of the patterns matches the path information -associated with a request, a particular :term:`view callable` is invoked. If -no route matches, :app:`Pyramid` falls back to trying to use -:term:`traversal` to map the current request to a :term:`view callable`. - -The presence of calls to the :meth:`pyramid.config.Configurator.add_route` -method within your application is a sign that you're using :term:`URL -dispatch`. +associated with a request, a particular :term:`view callable` is invoked. + +:term:`URL dispatch` is one of two ways to perform :term:`resource +location` in :app:`Pyramid`; the other way is using :term:`traversal`. +If no route is matched using :term:`URL dispatch`, :app:`Pyramid` falls +back to :term:`traversal` to handle the :term:`request`. + +It is the responsibility of the :term:`resource location` subsystem +(i.e., :term:`URL dispatch` or :term:`traversal`) to find the resource +object that is the :term:`context` of the :term:`request`. Once the +:term:`context` is determined, :term:`view lookup` is then responsible +for finding and invoking a :term:`view callable`. A view callable is a +specific bit of code, defined in your application, that receives the +:term:`request` and returns a :term:`response` object. + +Where appropriate, we will describe how view lookup interacts with +:term:`resource location`. The :ref:`views_chapter` describes the +details of :term:`view lookup`. High-Level Operational Overview ------------------------------- diff --git a/docs/narr/views.rst b/docs/narr/views.rst index ad28e48d4..81f3e644f 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -17,11 +17,11 @@ request made to your application. that implements a view *callable*, and the process of view *lookup*. -The chapter :ref:`resourcelocation_chapter` describes how, using information -from the :term:`request`, a :term:`context` resource is computed. But the -context resource itself isn't very useful without an associated :term:`view -callable`. A view callable returns a response to a user, often using the -context resource to do so. +The :ref:`urldispatch_chapter`, and :ref:`traversal_chapter` describes how, +using information from the :term:`request`, a :term:`context` resource is +computed. But the context resource itself isn't very useful without an +associated :term:`view callable`. A view callable returns a response to a +user, often using the context resource to do so. The job of actually locating and invoking the "best" :term:`view callable` is the job of the :term:`view lookup` subsystem. The view lookup subsystem -- cgit v1.2.3 From 096e80e94071d5066fd417517c8e5ec724fcc30d Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Fri, 31 Dec 2010 19:12:33 -0700 Subject: add missing word --- docs/narr/urldispatch.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index d9228bf52..7f2aee26c 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -6,10 +6,11 @@ URL Dispatch ============ -:term:`URL dispatch` provides a simple way to map URLs :term:`view` code -using a simple pattern matching language. An ordered set of patterns is -checked one-by-one. If one of the patterns matches the path information -associated with a request, a particular :term:`view callable` is invoked. +:term:`URL dispatch` provides a simple way to map URLs to :term:`view` +code using a simple pattern matching language. An ordered set of +patterns is checked one-by-one. If one of the patterns matches the path +information associated with a request, a particular :term:`view +callable` is invoked. :term:`URL dispatch` is one of two ways to perform :term:`resource location` in :app:`Pyramid`; the other way is using :term:`traversal`. -- cgit v1.2.3 From c76dc8e3d8cd8b419aeebff06e7b31abaf350561 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Fri, 31 Dec 2010 19:13:51 -0700 Subject: add word chapter --- docs/narr/urldispatch.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 7f2aee26c..5b8cb493e 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -25,9 +25,9 @@ for finding and invoking a :term:`view callable`. A view callable is a specific bit of code, defined in your application, that receives the :term:`request` and returns a :term:`response` object. -Where appropriate, we will describe how view lookup interacts with -:term:`resource location`. The :ref:`views_chapter` describes the -details of :term:`view lookup`. +Where appropriate, we will describe how view lookup interacts with +:term:`resource location`. The :ref:`views_chapter` chapter describes +the details of :term:`view lookup`. High-Level Operational Overview ------------------------------- -- cgit v1.2.3 From 51c5727393178461868a8072d921b7215c626257 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Fri, 31 Dec 2010 19:17:59 -0700 Subject: simplify route/traversal relationship. No need to repeat ourselves --- docs/narr/urldispatch.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 5b8cb493e..c2d778dd9 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -36,10 +36,9 @@ If route configuration is present in an application, the :app:`Pyramid` :term:`Router` checks every incoming request against an ordered set of URL matching patterns present in a *route map*. -If any route pattern matches the information in the :term:`request` provided -to :app:`Pyramid`, :app:`Pyramid` will shortcut :term:`traversal`, and will -invoke :term:`view lookup` using a :term:`context` resource generated by the -route match. +If any route pattern matches the information in the :term:`request`, +:app:`Pyramid` will invoke :term:`view lookup` using a :term:`context` +resource generated by the route match. However, if no route pattern matches the information in the :term:`request` provided to :app:`Pyramid`, it will fail over to using :term:`traversal` to -- cgit v1.2.3 From 9423c199cd87f15526b09a49b1c2bd4d029258ea Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Fri, 31 Dec 2010 19:20:32 -0700 Subject: don't repeat the version so many times --- docs/narr/urldispatch.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index c2d778dd9..c0b132a04 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -83,7 +83,7 @@ example: .. versionchanged:: 1.0a4 Prior to 1.0a4, routes allow for a marker starting with a ``:``, for - example ``/prefix/:one/:two``. Starting in 1.0a4, this style is deprecated + example ``/prefix/:one/:two``. This style is now deprecated in favor or ``{}`` usage which allows for additional functionality. .. index:: -- cgit v1.2.3 From f69e1986538ff12fbea36f378b9a2e74cfd11aaf Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Sat, 1 Jan 2011 12:17:07 -0700 Subject: clarify opening paragraph --- docs/narr/traversal.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index fa8d7d7ee..d1d695dce 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -9,10 +9,11 @@ nested dictionary-like objects. Traversal is done by using each segment of the path portion of the URL to navigate through the :term:`resource tree`. You might think of this as looking up files and directories in a file system. Traversal walks down the path until it finds a published -"directory" or "file". The resource we find as the result of a -traversal becomes the :term:`context`. Then, the :term:`view lookup` +resource, analogous to a file system "directory" or "file". The +resource found as the result of a traversal becomes the +:term:`context` of the :term:`request`. Then, the :term:`view lookup` subsystem is used to find some view code willing "publish" this -resource. +resource by generating a :term:`response`. Using :term:`Traversal` to map a URL to code is optional. It is often less easy to understand than :term:`URL dispatch`, so if you're a rank -- cgit v1.2.3 From 2ceb1f3064de5be4765a47800135400e992127ee Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Sat, 1 Jan 2011 12:20:23 -0700 Subject: simplify/clarify --- docs/narr/traversal.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index d1d695dce..bee9ce57b 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -26,11 +26,11 @@ code instead of traversal. In that case, you can skip this chapter. Traversal Details ----------------- -:term:`Traversal` is dependent on information in a :term:`request` object. -Every :term:`request` object contains URL path information in the -``PATH_INFO`` portion of the :term:`WSGI` environment. The ``PATH_INFO`` -portion of the WSGI environment is the portion of a request's URL following -the hostname and port number, but before any query string elements or +:term:`Traversal` is dependent on information in a :term:`request` +object. Every :term:`request` object contains URL path information in +the ``PATH_INFO`` portion of the :term:`WSGI` environment. The +``PATH_INFO`` string is the portion of a request's URL following the +hostname and port number, but before any query string elements or fragment element. For example the ``PATH_INFO`` portion of the URL ``http://example.com:8080/a/b/c?foo=1`` is ``/a/b/c``. -- cgit v1.2.3 From 234c0d612a82e9ae00a5c44953b0900520df5184 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Sat, 1 Jan 2011 12:25:24 -0700 Subject: reword for clarity --- docs/narr/traversal.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index bee9ce57b..291faf725 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -34,13 +34,13 @@ hostname and port number, but before any query string elements or fragment element. For example the ``PATH_INFO`` portion of the URL ``http://example.com:8080/a/b/c?foo=1`` is ``/a/b/c``. -Traversal treats the ``PATH_INFO`` segment of a URL as a sequence of path -segments. For example, the ``PATH_INFO`` string ``/a/b/c`` is converted to -the sequence ``['a', 'b', 'c']``. +Traversal treats the ``PATH_INFO`` segment of a URL as a sequence of +path segments. For example, the ``PATH_INFO`` string ``/a/b/c`` is +converted to the sequence ``['a', 'b', 'c']``. -After the path info is converted, a lookup is performed against the resource -tree for each path segment. Each lookup uses the ``__getitem__`` method of a -resource in the tree. +This path sequence is then used to descend through the :term:`resource +tree`, looking up a resource for each path segment. Each lookup uses the +``__getitem__`` method of a resource in the tree. For example, if the path info sequence is ``['a', 'b', 'c']``: -- cgit v1.2.3 From 1dff47ead5b1beedab1b8cbbab4ad7a93fc4c5d7 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Sat, 1 Jan 2011 12:58:25 -0700 Subject: clarify traversal details --- docs/narr/traversal.rst | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index 291faf725..2b40c4d94 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -44,33 +44,35 @@ tree`, looking up a resource for each path segment. Each lookup uses the For example, if the path info sequence is ``['a', 'b', 'c']``: -- :term:`Traversal` pops the first element (``a``) from the path segment - sequence and attempts to call the root resource's ``__getitem__`` method - using that value (``a``) as an argument; we'll presume it succeeds. +- :term:`Traversal` starts by acquiring the :term:`root` resource of the + application by calling the :term:`root factory`. The :term:`root factory` + can be configured to return whatever object is appropriate as the + traversal root of your application. -- When the root resource's ``__getitem__`` succeeds it will return another - resource, which we'll call "A". The :term:`context` temporarily becomes - the "A" resource. +- Next, the first element (``a``) is popped from the path segment + sequence and is used as a key to lookup the corresponding resource + in the root. This invokes the root resource's ``__getitem__`` method + using that value (``a``) as an argument. + +- If the root resource "contains" a resource with key ``a``, its + ``__getitem__`` method will return it. The :term:`context` temporarily + becomes the "A" resource. - The next segment (``b``) is popped from the path sequence, and the "A" resource's ``__getitem__`` is called with that value (``b``) as an argument; we'll presume it succeeds. -- When the "A" resource's ``__getitem__`` succeeds it will return another - resource, which we'll call "B". The :term:`context` temporarily becomes - the "B" resource. - -This process continues until the path segment sequence is exhausted or a path -element cannot be resolved to a resource. In either case, a :term:`context` -resource is chosen. - -Traversal "stops" when it either reaches a leaf level resource in your -resource tree or when the path segments from the URL "run out". The -resource that traversal "stops on" becomes the :term:`context`. If at any -point during traversal any resource in the tree doesn't have a -``__getitem__`` method, or if the ``__getitem__`` method of a resource raises -a :exc:`KeyError`, traversal ends immediately, and that resource becomes the -:term:`context`. +- The "A" resource's ``__getitem__`` returns another resource, which + we'll call "B". The :term:`context` temporarily becomes the "B" + resource. + +Traversal continues until the path segment sequence is exhausted or a +path element cannot be resolved to a resource. In either case, the +:term:`context` resource is the last object that the traversal +successfully resolved. If any resource found during traversal lacks a +``__getitem__`` method, or if its ``__getitem__`` method raises a +:exc:`KeyError`, traversal ends immediately, and that resource becomes +the :term:`context`. The results of a :term:`traversal` also include a :term:`view name`. The :term:`view name` is the *first* URL path segment in the set of ``PATH_INFO`` -- cgit v1.2.3 From f4b5b973c23ac629c7dd6a093a69ebdbc206b42a Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Sat, 1 Jan 2011 13:08:37 -0700 Subject: XXX try to clearly explain how the view name is derived during traversal, confirm this is sufficiently truthy XXX --- docs/narr/traversal.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index 2b40c4d94..d5f61fa1b 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -74,10 +74,11 @@ successfully resolved. If any resource found during traversal lacks a :exc:`KeyError`, traversal ends immediately, and that resource becomes the :term:`context`. -The results of a :term:`traversal` also include a :term:`view name`. The -:term:`view name` is the *first* URL path segment in the set of ``PATH_INFO`` -segments "left over" in the path segment list popped by the traversal process -*after* traversal finds a context resource. +The results of a :term:`traversal` also include a :term:`view name`. If +traversal ends before the path segment sequence is exhausted, the +:term:`view name` is the *next* remaining path segment element. If the +:term:`traversal` expends all of the path segments, then the :term:`view +name` is the empty string (`''`). The combination of the context resource and the :term:`view name` found via traversal is used later in the same request by the :term:`view -- cgit v1.2.3 From ad838b5d01980b156f763bea4cd655bf50a61783 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Sat, 1 Jan 2011 13:23:15 -0700 Subject: rework opening explanation of the resource tree for clarity --- docs/narr/traversal.rst | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index d5f61fa1b..c6da5e5ef 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -96,18 +96,20 @@ chapter. The Resource Tree ----------------- -When your application uses :term:`traversal` to resolve URLs to code, the -application must supply a :term:`resource tree` to :app:`Pyramid`. The -resource tree is a set of nested dictionary-like objects. The root of the -tree is represented by a :term:`root` resource. - -In order to supply a root resource for an application, at system startup -time, the :app:`Pyramid` :term:`Router` is configured with a callback known -as a :term:`root factory`. The root factory is supplied by the application -developer as the ``root_factory`` argument to the application's -:term:`Configurator`. - -Here's an example of a simple root factory: +The resource tree is a set of nested dictionary-like resource objects +that begins with a :term:`root` resource. In order to use +:term:`traversal` to resolve URLs to code, your application must supply +a :term:`resource tree` to :app:`Pyramid`. + +In order to supply a root resource for an application the :app:`Pyramid` +:term:`Router` is configured with a callback known as a :term:`root +factory`. The root factory is supplied by the application, at startup +time, as the ``root_factory`` argument to the :term:`Configurator`. + +The root factory is a Python callable that accepts a :term:`request` +object, and returns the root object of the :term:`resource tree`. A +function, or class is typically used as an application's root factory. +Here's an example of a simple root factory class: .. code-block:: python :linenos: -- cgit v1.2.3 From 419b2260ad97cbc9816b2fe075ef3d9acdb79581 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Sat, 1 Jan 2011 21:15:43 -0700 Subject: rework paragraphs discussifng root factot config --- docs/narr/traversal.rst | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index c6da5e5ef..0b0bb1d3e 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -126,24 +126,23 @@ passing it to an instance of a :term:`Configurator` named ``config``: config = Configurator(root_factory=Root) -Using the ``root_factory`` argument to a :class:`pyramid.config.Configurator` -constructor tells your :app:`Pyramid` application to call this root factory -to generate a root resource whenever a request enters the application. This -root factory is also known as the global root factory. A root factory can -alternately be passed to the ``Configurator`` as a :term:`dotted Python name` -which can refer to a root factory defined in a different module. - -A root factory is passed a :term:`request` object and it is expected to -return an object which represents the root of the resource tree. All -:term:`traversal` will begin at this root resource. Usually a root factory -for a traversal-based application will be more complicated than the above -``Root`` class; in particular it may be associated with a database connection -or another persistence mechanism. +The ``root_factory`` argument to the +:class:`pyramid.config.Configurator` constructor registers this root +factory to be called to generate a root resource whenever a request +enters the application. The root factory registered this way is also +known as the global root factory. A root factory can alternately be +passed to the ``Configurator`` as a :term:`dotted Python name` which can +refer to a root factory defined in a different module. If no :term:`root factory` is passed to the :app:`Pyramid` -:term:`Configurator` constructor, or the ``root_factory`` is specified as the -value ``None``, a *default* root factory is used. The default root factory -always returns a resource that has no child resources; it is effectively empty. +:term:`Configurator` constructor, or if the ``root_factory`` value +specified is ``None``, a *default* root factory is used. The default +root factory always returns a resource that has no child resources; it +is effectively empty. + +Usually a root factory for a traversal-based application will be more +complicated than the above ``Root`` class; in particular it may be +associated with a database connection or another persistence mechanism. .. sidebar:: Emulating the Default Root Factory -- cgit v1.2.3 From 3d187b491e2f8a2e4ac700f0b229d9d2bb92daa2 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 2 Jan 2011 04:50:43 -0500 Subject: typo, add example --- docs/narr/project.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 55a2711f3..d572256a0 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -958,12 +958,14 @@ To this: .. code-block:: python :linenos: - config.add_view('myproject.views.blogs.my_view', + config.add_view('myproject.views.blog.my_view', renderer='myproject:templates/mytemplate.pt') You can then continue to add files to the ``views`` directory, and refer to views or handler classes/functions within those files via the dotted name -passed as the first argument to ``add_view``. For example: +passed as the first argument to ``add_view``. For example, if you added a +file named ``anothermodule.py`` to the ``views`` subdirectory, and added a +view callable named ``my_view`` to it: .. code-block:: python :linenos: -- cgit v1.2.3 From 7bd0640cfb656b07d6a6ae4c91720eb9621d4748 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 2 Jan 2011 14:15:30 -0500 Subject: indirection is hard --- docs/narr/templates.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst index 437b823e9..7ef8e1923 100644 --- a/docs/narr/templates.rst +++ b/docs/narr/templates.rst @@ -628,7 +628,7 @@ application's configuration section, e.g.: .. code-block:: ini :linenos: - [app:main] + [app:MyProject] use = egg:MyProject#app debug_templates = true -- cgit v1.2.3 From fd9c5b97220e59436c5af59ffa467877fef0df2f Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 2 Jan 2011 14:28:53 -0500 Subject: provide a reference target for modifying package structure --- docs/narr/project.rst | 2 ++ 1 file changed, 2 insertions(+) (limited to 'docs/narr') diff --git a/docs/narr/project.rst b/docs/narr/project.rst index d572256a0..5e84a4fa7 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -910,6 +910,8 @@ example. See :ref:`testing_chapter` for more information about writing :app:`Pyramid` unit tests. +.. _modifying_package_structure: + Modifying Package Structure ---------------------------- -- cgit v1.2.3 From fa1dec9ec10e4aca0b1bb69c544e6dc64e297fdf Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Sun, 2 Jan 2011 20:15:38 -0700 Subject: add word chapters --- docs/narr/views.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/views.rst b/docs/narr/views.rst index 81f3e644f..e1a7c4679 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -17,11 +17,12 @@ request made to your application. that implements a view *callable*, and the process of view *lookup*. -The :ref:`urldispatch_chapter`, and :ref:`traversal_chapter` describes how, -using information from the :term:`request`, a :term:`context` resource is -computed. But the context resource itself isn't very useful without an -associated :term:`view callable`. A view callable returns a response to a -user, often using the context resource to do so. +The :ref:`urldispatch_chapter`, and :ref:`traversal_chapter` chapters +describes how, using information from the :term:`request`, a +:term:`context` resource is computed. But the context resource itself +isn't very useful without an associated :term:`view callable`. A view +callable returns a response to a user, often using the context resource +to do so. The job of actually locating and invoking the "best" :term:`view callable` is the job of the :term:`view lookup` subsystem. The view lookup subsystem -- cgit v1.2.3 From f9bd7aa61148fada7c0b36df96b5c42670d8c85e Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Sun, 2 Jan 2011 21:37:24 -0700 Subject: rework view callables intro --- docs/narr/views.rst | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/views.rst b/docs/narr/views.rst index e1a7c4679..39e40a23a 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -38,24 +38,24 @@ a detailed explanation of view lookup. View Callables -------------- -No matter how a view callable is eventually found, all view callables -used by :app:`Pyramid` must be constructed in the same way, and -must return the same kind of return value. - -Most view callables accept a single argument named ``request``. This -argument represents a :app:`Pyramid` :term:`Request` object. A request -object encapsulates a WSGI environment as represented to :app:`Pyramid` by -the upstream :term:`WSGI` server. - -In general, a view callable must return a :mod:`Pyramid` :term:`Response` -object. - -.. note:: The above statement, though it sounds definitive, isn't always - true. See :ref:`renderers_chapter` for information related to using a - :term:`renderer` to convert a non-Response view callable return value into - a Response object. - -View callables can be functions, instances, or classes. +View callables are, at the risk of sounding obvious, callable Python +objects. Specifically, view callables can be functions, classes, or +instances that implement an ``__call__`` method (making the +instance callable). + +View callables must, at a minimum, accept a single argument named +``request``. This argument represents a :app:`Pyramid` :term:`Request` +object. A request object encapsulates a WSGI environment provided to +:app:`Pyramid` by the upstream :term:`WSGI` server. As you might expect, +the request object contains everything your application needs to know +about the specific HTTP request being made. + +In general, a view callable must return a :mod:`Pyramid` +:term:`Response` object. If a view callable does not return a response +itself, it will typically be configured with a :term:`renderer` that +converts its response value into a :term:`Response` object. Using +renderers is the common way that templates are bound to view callables. +See the :ref:`renderers_chapter` chapter for details. .. index:: single: view calling convention -- cgit v1.2.3 From bb9a3783046db24b2fda6787c232d4168d256729 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Sun, 2 Jan 2011 21:46:24 -0700 Subject: rework paragraph about view callable return values and the possibility of renderers --- docs/narr/views.rst | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/views.rst b/docs/narr/views.rst index 39e40a23a..08ae5d5d8 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -50,12 +50,14 @@ object. A request object encapsulates a WSGI environment provided to the request object contains everything your application needs to know about the specific HTTP request being made. -In general, a view callable must return a :mod:`Pyramid` -:term:`Response` object. If a view callable does not return a response -itself, it will typically be configured with a :term:`renderer` that -converts its response value into a :term:`Response` object. Using -renderers is the common way that templates are bound to view callables. -See the :ref:`renderers_chapter` chapter for details. +A view callable's ultimate responsibility is to create a :mod:`Pyramid` +:term:`Response` object. This can be done by creating the response in +the view callable code and returning it directly. However, if a view +callable does not return a response itself, it can be configured to use +a :term:`renderer` that converts its return value into a +:term:`Response` object. Using renderers is the common way that +templates are used with view callables to generate markup. See the +:ref:`renderers_chapter` chapter for details. .. index:: single: view calling convention -- cgit v1.2.3 From 8492927535d6b16b2c087d3f0be1c98ba09592a9 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 3 Jan 2011 01:44:52 -0500 Subject: contributors --- docs/narr/muchadoabouttraversal.rst | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'docs/narr') diff --git a/docs/narr/muchadoabouttraversal.rst b/docs/narr/muchadoabouttraversal.rst index 9bd829754..cb1c6f73a 100644 --- a/docs/narr/muchadoabouttraversal.rst +++ b/docs/narr/muchadoabouttraversal.rst @@ -4,6 +4,11 @@ Much Ado About Traversal ======================== +.. note:: This chapter was adapted, with permission, from a blog post by `Rob + Miller `_, originally published at + `http://blog.nonsequitarian.org/2010/much-ado-about-traversal/ + `_. + A lot of folks who have been using Pylons (and, therefore, Routes-based URL matching) are being exposed for the first time, via :app:`Pyramid`, to new ideas such as ":term:`traversal`" and ":term:`view lookup`" as a -- cgit v1.2.3 From 8a1b50bc4027e25d5450cc6968f1f005af9d389f Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Sun, 2 Jan 2011 23:45:33 -0700 Subject: Split view chapter, move view config after templates, some reordering in view config --- docs/narr/firstapp.rst | 2 +- docs/narr/traversal.rst | 19 +- docs/narr/urldispatch.rst | 7 +- docs/narr/viewconfig.rst | 965 +++++++++++++++++++++++++++++++++++++++ docs/narr/views.rst | 1116 +-------------------------------------------- 5 files changed, 998 insertions(+), 1111 deletions(-) create mode 100644 docs/narr/viewconfig.rst (limited to 'docs/narr') diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst index 1a81134f5..3f1098da4 100644 --- a/docs/narr/firstapp.rst +++ b/docs/narr/firstapp.rst @@ -323,7 +323,7 @@ For more information about the API of a :term:`Configurator` object, see :class:`pyramid.config.Configurator` . For more information about :term:`view configuration`, see -:ref:`views_chapter`. +:ref:`view_config_chapter`. An example of using *declarative* configuration (:term:`ZCML`) instead of imperative configuration to create a similar "hello world" is available diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index 0b0bb1d3e..7c6280ba1 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -83,7 +83,7 @@ name` is the empty string (`''`). The combination of the context resource and the :term:`view name` found via traversal is used later in the same request by the :term:`view lookup` subsystem to find a :term:`view callable`. How :app:`Pyramid` -performs view lookup is explained within the :ref:`views_chapter` +performs view lookup is explained within the :ref:`view_config_chapter` chapter. .. index:: @@ -217,13 +217,14 @@ We'll provide a description of the algorithm, a diagram of how the algorithm works, and some example traversal scenarios that might help you understand how the algorithm operates against a specific resource tree. -We'll also talk a bit about :term:`view lookup`. The :ref:`views_chapter` -chapter discusses :term:`view lookup` in detail, and it is the canonical -source for information about views. Technically, :term:`view lookup` is a -:app:`Pyramid` subsystem that is separated from traversal entirely. However, -we'll describe the fundamental behavior of view lookup in the examples in the -next few sections to give you an idea of how traversal and view lookup -cooperate, because they are almost always used together. +We'll also talk a bit about :term:`view lookup`. The +:ref:`view_config_chapter` chapter discusses :term:`view lookup` in +detail, and it is the canonical source for information about views. +Technically, :term:`view lookup` is a :app:`Pyramid` subsystem that is +separated from traversal entirely. However, we'll describe the +fundamental behavior of view lookup in the examples in the next few +sections to give you an idea of how traversal and view lookup cooperate, +because they are almost always used together. .. index:: single: view name @@ -461,7 +462,7 @@ References A tutorial showing how :term:`traversal` can be used within a :app:`Pyramid` application exists in :ref:`bfg_wiki_tutorial`. -See the :ref:`views_chapter` chapter for detailed information about +See the :ref:`view_config_chapter` chapter for detailed information about :term:`view lookup`. The :mod:`pyramid.traversal` module contains API functions that deal with diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index c0b132a04..e64513a96 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -26,7 +26,7 @@ specific bit of code, defined in your application, that receives the :term:`request` and returns a :term:`response` object. Where appropriate, we will describe how view lookup interacts with -:term:`resource location`. The :ref:`views_chapter` chapter describes +:term:`resource location`. The :ref:`view_config_chapter` chapter describes the details of :term:`view lookup`. High-Level Operational Overview @@ -95,7 +95,7 @@ Route Configuration That Names a View Callable When a route configuration declaration names a ``view`` attribute, the value of the attribute will reference a :term:`view callable`. This view callable will be invoked when the route matches. A view callable, as described in -:ref:`views_chapter`, is developer-supplied code that "does stuff" as the +:ref:`view_chapter`, is developer-supplied code that "does stuff" as the result of a request. For more information about how to create view callables, see :ref:`views_chapter`. @@ -865,7 +865,8 @@ The ``mypackage.views`` module referred to above might look like so: The view has access to the matchdict directly via the request, and can access variables within it that match keys present as a result of the route pattern. -See :ref:`views_chapter` for more information about views. +See :ref:`views_chapter`, and :ref:`view_config_chapter` for more +information about views. Example 2 ~~~~~~~~~ diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst new file mode 100644 index 000000000..39c0a0d1f --- /dev/null +++ b/docs/narr/viewconfig.rst @@ -0,0 +1,965 @@ +.. _view_config_chapter: + +.. _view_configuration: + +View Configuration +================== + +.. index:: + single: view lookup + +.. _view_lookup: + +View Lookup and Invocation +-------------------------- + +:term:`View lookup` is the :app:`Pyramid` subsystem responsible for finding +an invoking a :term:`view callable`. The view lookup subsystem is passed a +:term:`context` and a :term:`request` object. + +:term:`View configuration` information stored within in the +:term:`application registry` is compared against the context and request by +the view lookup subsystem in order to find the "best" view callable for the +set of circumstances implied by the context and request. + +:term:`View predicate` attributes are an important part of view +configuration that enables the :term:`View lookup` subsystem to find and +invoke the appropriate view. Predicate attributes can be thought of +like "narrowers". In general, the greater number of predicate +attributes possessed by a view's configuration, the more specific the +circumstances need to be before the registered view callable will be +invoked. + +Mapping a Resource or URL Pattern to a View Callable +---------------------------------------------------- + +A developer makes a :term:`view callable` available for use within a +:app:`Pyramid` application via :term:`view configuration`. A view +configuration associates a view callable with a set of statements that +determine the set of circumstances which must be true for the view callable +to be invoked. + +A view configuration statement is made about information present in the +:term:`context` resource and the :term:`request`. + +View configuration is performed in one of these ways: + +- by running a :term:`scan` against application source code which has a + :class:`pyramid.view.view_config` decorator attached to a Python object as + per :class:`pyramid.view.view_config` and + :ref:`mapping_views_using_a_decorator_section`. + +- by using the :meth:`pyramid.config.Configurator.add_view` method as per + :meth:`pyramid.config.Configurator.add_view` and + :ref:`mapping_views_using_imperative_config_section`. + +- By specifying a view within a :term:`route configuration`. View + configuration via a route configuration is performed by using the + :meth:`pyramid.config.Configurator.add_route` method, passing a ``view`` + argument specifying a view callable. + +- by using the :meth:`pyramid.config.Configurator.add_handler` against a + :term:`view handler` class (useful only for :term:`URL dispatch` + applications). + +.. note:: You can also add view configuration by adding a ````, + ```` or ```` declaration to :term:`ZCML` used by your + application as per :ref:`mapping_views_using_zcml_section`, + :ref:`view_directive`, :ref:`route_directive` or :ref:`handler_directive`. + +.. _view_configuration_parameters: + +View Configuration Parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +All forms of view configuration accept the same general types of arguments. + +Many arguments supplied during view configuration are :term:`view predicate` +arguments. View predicate arguments used during view configuration are used +to narrow the set of circumstances in which :mod:`view lookup` will find a +particular view callable. + +In general, the fewer number of predicates which are supplied to a +particular view configuration, the more likely it is that the associated +view callable will be invoked. The greater the number supplied, the +less likely. A view with five predicates will always be found and +evaluated before a view with two, for example. All predicates must +match for the associated view to be called. + +This does not mean however, that :app:`Pyramid` "stops looking" when it +finds a view registration with predicates that don't match. If one set +of view predicates does not match, the "next most specific" view (if +any) is consulted for predicates, and so on, until a view is found, or +no view can be matched up with the request. The first view with a set +of predicates all of which match the request environment will be +invoked. + +If no view can be found with predicates which allow it to be matched up with +the request, :app:`Pyramid` will return an error to the user's browser, +representing a "not found" (404) page. See :ref:`changing_the_notfound_view` +for more information about changing the default notfound view. + +Some view configuration arguments are non-predicate arguments. These tend to +modify the response of the view callable or prevent the view callable from +being invoked due to an authorization policy. The presence of non-predicate +arguments in a view configuration does not narrow the circumstances in which +the view callable will be invoked. + +Non-Predicate Arguments ++++++++++++++++++++++++ + +``permission`` + The name of a :term:`permission` that the user must possess in order to + invoke the :term:`view callable`. See :ref:`view_security_section` for + more information about view security and permissions. + + If ``permission`` is not supplied, no permission is registered for this + view (it's accessible by any caller). + +``attr`` + The view machinery defaults to using the ``__call__`` method of the + :term:`view callable` (or the function itself, if the view callable is a + function) to obtain a response. The ``attr`` value allows you to vary the + method attribute used to obtain the response. For example, if your view + was a class, and the class has a method named ``index`` and you wanted to + use this method instead of the class' ``__call__`` method to return the + response, you'd say ``attr="index"`` in the view configuration for the + view. This is most useful when the view definition is a class. + + If ``attr`` is not supplied, ``None`` is used (implying the function itself + if the view is a function, or the ``__call__`` callable attribute if the + view is a class). + +``renderer`` + Denotes the :term:`renderer` implementation which will be used to construct + a :term:`response` from the associated view callable's return value. (see + also :ref:`renderers_chapter`). + + This is either a single string term (e.g. ``json``) or a string implying a + path or :term:`asset specification` (e.g. ``templates/views.pt``) naming a + :term:`renderer` implementation. If the ``renderer`` value does not + contain a dot (``.``), the specified string will be used to look up a + renderer implementation, and that renderer implementation will be used to + construct a response from the view return value. If the ``renderer`` value + contains a dot (``.``), the specified term will be treated as a path, and + the filename extension of the last element in the path will be used to look + up the renderer implementation, which will be passed the full path. + + When the renderer is a path, although a path is usually just a simple + relative pathname (e.g. ``templates/foo.pt``, implying that a template + named "foo.pt" is in the "templates" directory relative to the directory of + the current :term:`package`), a path can be absolute, starting with a slash + on UNIX or a drive letter prefix on Windows. The path can alternately be a + :term:`asset specification` in the form + ``some.dotted.package_name:relative/path``, making it possible to address + template assets which live in a separate package. + + The ``renderer`` attribute is optional. If it is not defined, the "null" + renderer is assumed (no rendering is performed and the value is passed back + to the upstream :app:`Pyramid` machinery unmolested). Note that if the + view callable itself returns a :term:`response` (see :ref:`the_response`), + the specified renderer implementation is never called. + +``wrapper`` + The :term:`view name` of a different :term:`view configuration` which will + receive the response body of this view as the ``request.wrapped_body`` + attribute of its own :term:`request`, and the :term:`response` returned by + this view as the ``request.wrapped_response`` attribute of its own request. + Using a wrapper makes it possible to "chain" views together to form a + composite response. The response of the outermost wrapper view will be + returned to the user. The wrapper view will be found as any view is found: + see :ref:`view_lookup`. The "best" wrapper view will be found based on the + lookup ordering: "under the hood" this wrapper view is looked up via + ``pyramid.view.render_view_to_response(context, request, + 'wrapper_viewname')``. The context and request of a wrapper view is the + same context and request of the inner view. + + If ``wrapper`` is not supplied, no wrapper view is used. + +Predicate Arguments ++++++++++++++++++++ + +These arguments modify view lookup behavior. In general, the more predicate +arguments that are supplied, the more specific, and narrower the usage of the +configured view. + +``name`` + The :term:`view name` required to match this view callable. Read + :ref:`traversal_chapter` to understand the concept of a view name. + + If ``name`` is not supplied, the empty string is used (implying the default + view). + +``context`` + An object representing a Python class that the :term:`context` resource + must be an instance of *or* the :term:`interface` that the :term:`context` + resource must provide in order for this view to be found and called. This + predicate is true when the :term:`context` resource is an instance of the + represented class or if the :term:`context` resource provides the + represented interface; it is otherwise false. + + If ``context`` is not supplied, the value ``None``, which matches any + resource, is used. + +``route_name`` + If ``route_name`` is supplied, the view callable will be invoked only when + the named route has matched. + + This value must match the ``name`` of a :term:`route configuration` + declaration (see :ref:`urldispatch_chapter`) that must match before this + view will be called. Note that the ``route`` configuration referred to by + ``route_name`` will usually have a ``*traverse`` token in the value of its + ``pattern``, representing a part of the path that will be used by + :term:`traversal` against the result of the route's :term:`root factory`. + + If ``route_name`` is not supplied, the view callable will be have a chance + of being invoked if no other route was matched. This is when the + request/context pair found via :term:`resource location` does not indicate + it matched any configured route. + +``request_type`` + This value should be an :term:`interface` that the :term:`request` must + provide in order for this view to be found and called. + + If ``request_type`` is not supplied, the value ``None`` is used, implying + any request type. + + *This is an advanced feature, not often used by "civilians"*. + +``request_method`` + This value can either be one of the strings ``GET``, ``POST``, ``PUT``, + ``DELETE``, or ``HEAD`` representing an HTTP ``REQUEST_METHOD``. A view + declaration with this argument ensures that the view will only be called + when the request's ``method`` attribute (aka the ``REQUEST_METHOD`` of the + WSGI environment) string matches the supplied value. + + If ``request_method`` is not supplied, the view will be invoked regardless + of the ``REQUEST_METHOD`` of the :term:`WSGI` environment. + +``request_param`` + This value can be any string. A view declaration with this argument + ensures that the view will only be called when the :term:`request` has a + key in the ``request.params`` dictionary (an HTTP ``GET`` or ``POST`` + variable) that has a name which matches the supplied value. + + If the value supplied has a ``=`` sign in it, + e.g. ``request_params="foo=123"``, then the key (``foo``) must both exist + in the ``request.params`` dictionary, *and* the value must match the right + hand side of the expression (``123``) for the view to "match" the current + request. + + If ``request_param`` is not supplied, the view will be invoked without + consideration of keys and values in the ``request.params`` dictionary. + +``containment`` + This value should be a reference to a Python class or :term:`interface` + that a parent object in the context resource's :term:`lineage` must provide + in order for this view to be found and called. The resources in your + resource tree must be "location-aware" to use this feature. + + If ``containment`` is not supplied, the interfaces and classes in the + lineage are not considered when deciding whether or not to invoke the view + callable. + + See :ref:`location_aware` for more information about location-awareness. + +``xhr`` + This value should be either ``True`` or ``False``. If this value is + specified and is ``True``, the :term:`WSGI` environment must possess an + ``HTTP_X_REQUESTED_WITH`` (aka ``X-Requested-With``) header that has the + value ``XMLHttpRequest`` for the associated view callable to be found and + called. This is useful for detecting AJAX requests issued from jQuery, + Prototype and other Javascript libraries. + + If ``xhr`` is not specified, the ``HTTP_X_REQUESTED_WITH`` HTTP header is + not taken into consideration when deciding whether or not to invoke the + associated view callable. + +``accept`` + The value of this argument represents a match query for one or more + mimetypes in the ``Accept`` HTTP request header. If this value is + specified, it must be in one of the following forms: a mimetype match token + in the form ``text/plain``, a wildcard mimetype match token in the form + ``text/*`` or a match-all wildcard mimetype match token in the form + ``*/*``. If any of the forms matches the ``Accept`` header of the request, + this predicate will be true. + + If ``accept`` is not specified, the ``HTTP_ACCEPT`` HTTP header is not + taken into consideration when deciding whether or not to invoke the + associated view callable. + +``header`` + This value represents an HTTP header name or a header name/value pair. + + If ``header`` is specified, it must be a header name or a + ``headername:headervalue`` pair. + + If ``header`` is specified without a value (a bare header name only, + e.g. ``If-Modified-Since``), the view will only be invoked if the HTTP + header exists with any value in the request. + + If ``header`` is specified, and possesses a name/value pair + (e.g. ``User-Agent:Mozilla/.*``), the view will only be invoked if the HTTP + header exists *and* the HTTP header matches the value requested. When the + ``headervalue`` contains a ``:`` (colon), it will be considered a + name/value pair (e.g. ``User-Agent:Mozilla/.*`` or ``Host:localhost``). + The value portion should be a regular expression. + + Whether or not the value represents a header name or a header name/value + pair, the case of the header name is not significant. + + If ``header`` is not specified, the composition, presence or absence of + HTTP headers is not taken into consideration when deciding whether or not + to invoke the associated view callable. + +``path_info`` + This value represents a regular expression pattern that will be tested + against the ``PATH_INFO`` WSGI environment variable to decide whether or + not to call the associated view callable. If the regex matches, this + predicate will be ``True``. + + If ``path_info`` is not specified, the WSGI ``PATH_INFO`` is not taken into + consideration when deciding whether or not to invoke the associated view + callable. + +``custom_predicates`` + If ``custom_predicates`` is specified, it must be a sequence of references + to custom predicate callables. Use custom predicates when no set of + predefined predicates do what you need. Custom predicates can be combined + with predefined predicates as necessary. Each custom predicate callable + should accept two arguments: ``context`` and ``request`` and should return + either ``True`` or ``False`` after doing arbitrary evaluation of the + context resource and/or the request. If all callables return ``True``, the + associated view callable will be considered viable for a given request. + + If ``custom_predicates`` is not specified, no custom predicates are + used. + +.. index:: + single: view_config decorator + +.. _mapping_views_using_a_decorator_section: + +View Configuration Using the ``@view_config`` Decorator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For better locality of reference, you may use the +:class:`pyramid.view.view_config` decorator to associate your view functions +with URLs instead of using :term:`ZCML` or imperative configuration for the +same purpose. + +.. warning:: + + Using this feature tends to slows down application startup slightly, as + more work is performed at application startup to scan for view + declarations. + +Usage of the ``view_config`` decorator is a form of :term:`declarative +configuration`, like ZCML, but in decorator form. +:class:`pyramid.view.view_config` can be used to associate :term:`view +configuration` information -- as done via the equivalent imperative code or +ZCML -- with a function that acts as a :app:`Pyramid` view callable. All +arguments to the :meth:`pyramid.config.Configurator.add_view` method (save +for the ``view`` argument) are available in decorator form and mean precisely +the same thing. + +An example of the :class:`pyramid.view.view_config` decorator might reside in +a :app:`Pyramid` application module ``views.py``: + +.. ignore-next-block +.. code-block:: python + :linenos: + + from resources import MyResource + from pyramid.view import view_config + from pyramid.response import Response + + @view_config(name='my_view', request_method='POST', context=MyResource, + permission='read') + def my_view(request): + return Response('OK') + +Using this decorator as above replaces the need to add this imperative +configuration stanza: + +.. ignore-next-block +.. code-block:: python + :linenos: + + config.add_view('.views.my_view', name='my_view', request_method='POST', + context=MyResource, permission='read') + +All arguments to ``view_config`` may be omitted. For example: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + from pyramid.view import view_config + + @view_config() + def my_view(request): + """ My view """ + return Response() + +Such a registration as the one directly above implies that the view name will +be ``my_view``, registered with a ``context`` argument that matches any +resource type, using no permission, registered against requests with any +request method, request type, request param, route name, or containment. + +The mere existence of a ``@view_config`` decorator doesn't suffice to perform +view configuration. All that the decorator does is "annotate" the function +with your configuration declarations, it doesn't process them. To make +:app:`Pyramid` process your :class:`pyramid.view.view_config` declarations, +you *must* do use the ``scan`` method of a +:class:`pyramid.config.Configurator`: + +.. code-block:: python + :linenos: + + # config is assumed to be an instance of the + # pyramid.config.Configurator class + config.scan() + +.. note:: See :ref:`zcml_scanning` for information about how to invoke a scan + via ZCML (if you're not using imperative configuration). + +Please see :ref:`decorations_and_code_scanning` for detailed information +about what happens when code is scanned for configuration declarations +resulting from use of decorators like :class:`pyramid.view.view_config`. + +See :ref:`configuration_module` for additional API arguments to the +:meth:`pyramid.config.Configurator.scan` method. For example, the method +allows you to supply a ``package`` argument to better control exactly *which* +code will be scanned. + +``@view_config`` Placement +++++++++++++++++++++++++++ + +A :class:`pyramid.view.view_config` decorator can be placed in various points +in your application. + +If your view callable is a function, it may be used as a function decorator: + +.. code-block:: python + :linenos: + + from pyramid.view import view_config + from pyramid.response import Response + + @view_config(name='edit') + def edit(request): + return Response('edited!') + +If your view callable is a class, the decorator can also be used as a class +decorator in Python 2.6 and better (Python 2.5 and below do not support class +decorators). All the arguments to the decorator are the same when applied +against a class as when they are applied against a function. For example: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + from pyramid.view import view_config + + @view_config() + class MyView(object): + def __init__(self, request): + self.request = request + + def __call__(self): + return Response('hello') + +You can use the :class:`pyramid.view.view_config` decorator as a simple +callable to manually decorate classes in Python 2.5 and below without the +decorator syntactic sugar, if you wish: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + from pyramid.view import view_config + + class MyView(object): + def __init__(self, request): + self.request = request + + def __call__(self): + return Response('hello') + + my_view = view_config()(MyView) + +More than one :class:`pyramid.view.view_config` decorator can be stacked on +top of any number of others. Each decorator creates a separate view +registration. For example: + +.. code-block:: python + :linenos: + + from pyramid.view import view_config + from pyramid.response import Response + + @view_config(name='edit') + @view_config(name='change') + def edit(request): + return Response('edited!') + +This registers the same view under two different names. + +The decorator can also be used against class methods: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + from pyramid.view import view_config + + class MyView(object): + def __init__(self, request): + self.request = request + + @view_config(name='hello') + def amethod(self): + return Response('hello') + +When the decorator is used against a class method, a view is registered for +the *class*, so the class constructor must accept an argument list in one of +two forms: either it must accept a single argument ``request`` or it must +accept two arguments, ``context, request``. + +The method which is decorated must return a :term:`response`. + +Using the decorator against a particular method of a class is equivalent to +using the ``attr`` parameter in a decorator attached to the class itself. +For example, the above registration implied by the decorator being used +against the ``amethod`` method could be spelled equivalently as the below: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + from pyramid.view import view_config + + @view_config(attr='amethod', name='hello') + class MyView(object): + def __init__(self, request): + self.request = request + + def amethod(self): + return Response('hello') + +.. index:: + single: add_view + +.. _mapping_views_using_imperative_config_section: + +View Registration Using :meth:`~pyramid.config.Configurator.add_view` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :meth:`pyramid.config.Configurator.add_view` method within +:ref:`configuration_module` is used to configure a view imperatively. The +arguments to this method are very similar to the arguments that you provide +to the ``@view_config`` decorator. For example: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + + def hello_world(request): + return Response('hello!') + + # config is assumed to be an instance of the + # pyramid.config.Configurator class + config.add_view(hello_world, name='hello.html') + +The first argument, ``view``, is required. It must either be a Python object +which is the view itself or a :term:`dotted Python name` to such an object. +All other arguments are optional. See +:meth:`pyramid.config.Configurator.add_view` for more information. + +.. _using_add_handler: + +Handler Registration Using :meth:`~pyramid.config.Configurator.add_handler` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:app:`Pyramid` provides the special concept of a :term:`view handler`. View +handlers are view classes that implement a number of methods, each of which +is a :term:`view callable` as a convenience for :term:`URL dispatch` users. + +.. note:: + + View handlers are *not* useful when using :term:`traversal`, only when using + :term:`url dispatch`. + +Using a view handler instead of a plain function or class :term:`view +callable` makes it unnecessary to call +:meth:`pyramid.config.Configurator.add_route` (and/or +:meth:`pyramid.config.Configurator.add_view`) "by hand" multiple times, +making it more pleasant to register a collection of views as a single class +when using :term:`url dispatch`. The view handler machinery also introduces +the concept of an ``action``, which is used as a :term:`view predicate` to +control which method of the handler is called. The method name is the +default *action name* of a handler view callable. + +The concept of a view handler is analogous to a "controller" in Pylons 1.0. + +The view handler class is initialized by :app:`Pyramid` in the same manner as +a "plain" view class. Its ``__init__`` is called with a request object (see +:ref:`class_as_view`). It implements methods, each of which is a :term:`view +callable`. When a request enters the system which corresponds with an +*action* related to one of its view callable methods, this method is called, +and it is expected to return a response. + +Here's an example view handler class: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + + from pyramid.view import action + + class Hello(object): + def __init__(self, request): + self.request = request + + def index(self): + return Response('Hello world!') + + @action(renderer="mytemplate.mak") + def bye(self): + return {} + +The :class:`pyramid.view.action` decorator is used to fine-tune the view +parameters for each potential view callable which is a method of the handler. + +Handlers are added to application configuration via the +:meth:`pyramid.config.Configurator.add_handler` API. The +:meth:`~pyramid.config.Configurator.add_handler` method will scan a +:term:`view handler` class and automatically set up view configurations for +its methods that represent "auto-exposed" view callable, or those that were +decorated explicitly with the :class:`~pyramid.view.action` decorator. This +decorator is used to setup additional view configuration information for +individual methods of the class, and can be used repeatedly for a single view +method to register multiple view configurations for it. + +.. code-block:: python + :linenos: + + from myapp.handlers import Hello + config.add_handler('hello', '/hello/{action}', handler=Hello) + +This example will result in a route being added for the pattern +``/hello/{action}``, and each method of the ``Hello`` class will then be +examined to see if it should be registered as a potential view callable when +the ``/hello/{action}`` pattern matches. The value of ``{action}`` in the +route pattern will be used to determine which view should be called, and each +view in the class will be setup with a view predicate that requires a +specific ``action`` name. By default, the action name for a method of a +handler is the method name. + +If the URL was ``/hello/index``, the above example pattern would match, and, +by default, the ``index`` method of the ``Hello`` class would be called. + +Alternatively, the action can be declared specifically for a URL to be +registered for a *specific* ``action`` name: + +.. code-block:: python + :linenos: + + from myapp.handlers import Hello + config.add_handler('hello_index', '/hello/index', + handler=Hello, action='index') + +This will result one of the methods that are configured for the ``action`` of +'index' in the ``Hello`` handler class to be called. In this case the name of +the method is the same as the action name: ``index``. However, this need not +be the case, as we will see below. + +When calling :meth:`~pyramid.config.Configurator.add_handler`, an ``action`` +is required in either the route pattern or as a keyword argument, but +**cannot appear in both places**. A ``handler`` argument must also be +supplied, which can be either a :term:`asset specification` or a Python +reference to the handler class. Additional keyword arguments are passed +directly through to :meth:`pyramid.config.Configurator.add_route`. + +For example: + +.. code-block:: python + :linenos: + + config.add_handler('hello', '/hello/{action}', + handler='mypackage.handlers.MyHandler') + +Multiple :meth:`~pyramid.config.Configurator.add_handler` calls can specify +the same handler, to register specific route names for different +handler/action combinations. For example: + +.. code-block:: python + :linenos: + + config.add_handler('hello_index', '/hello/index', + handler=Hello, action='index') + config.add_handler('bye_index', '/hello/bye', + handler=Hello, action='bye') + +.. note:: + + Handler configuration may also be added to the system via :term:`ZCML` (see + :ref:`zcml_handler_configuration`). + +View Setup in the Handler Class ++++++++++++++++++++++++++++++++ + +A handler class can have a single class level attribute called +``__autoexpose__`` which should be a regular expression or the value +``None``. It's used to determine which method names will result in additional +view configurations being registered. + +When :meth:`~pyramid.config.Configurator.add_handler` runs, every method in +the handler class will be searched and a view registered if the method name +matches the ``__autoexpose__`` regular expression, or if the method was +decorated with :class:`~pyramid.view.action`. + +Every method in the handler class that has a name meeting the +``__autoexpose__`` regular expression will have a view registered for an +``action`` name corresponding to the method name. This functionality can be +disabled by setting the ``__autoexpose__`` attribute to ``None``: + +.. code-block:: python + :linenos: + + from pyramid.view import action + + class Hello(object): + __autoexpose__ = None + + def __init__(self, request): + self.request = request + + @action() + def index(self): + return Response('Hello world!') + + @action(renderer="mytemplate.mak") + def bye(self): + return {} + +With auto-expose effectively disabled, no views will be registered for a +method unless it is specifically decorated with +:class:`~pyramid.view.action`. + +Action Decorators in a Handler +++++++++++++++++++++++++++++++ + +The :class:`~pyramid.view.action` decorator registers view configuration +information on the handler method, which is used by +:meth:`~pyramid.config.Configurator.add_handler` to setup the view +configuration. + +All keyword arguments are recorded, and passed to +:meth:`~pyramid.config.Configurator.add_view`. Any valid keyword arguments +for :meth:`~pyramid.config.Configurator.add_view` can thus be used with the +:class:`~pyramid.view.action` decorator to further restrict when the view +will be called. + +One important difference is that a handler method can respond to an +``action`` name that is different from the method name by passing in a +``name`` argument. + +Example: + +.. code-block:: python + :linenos: + + from pyramid.view import action + + class Hello(object): + def __init__(self, request): + self.request = request + + @action(name='index', renderer='created.mak', request_method='POST') + def create(self): + return {} + + @action(renderer="view_all.mak", request_method='GET') + def index(self): + return {} + +This will register two views that require the ``action`` to be ``index``, +with the additional view predicate requiring a specific request method. + +It can be useful to decorate a single method multiple times with +:class:`~pyramid.view.action`. Each action decorator will register a new view +for the method. By specifying different names and renderers for each action, +the same view logic can be exposed and rendered differently on multiple URLs. + +Example: + +.. code-block:: python + :linenos: + + from pyramid.view import action + + class Hello(object): + def __init__(self, request): + self.request = request + + @action(name='home', renderer='home.mak') + @action(name='about', renderer='about.mak') + def show_template(self): + # prep some template vars + return {} + + # in the config + config.add_handler('hello', '/hello/{action}', handler=Hello) + +With this configuration, the url ``/hello/home`` will find a view +configuration that results in calling the ``show_template`` method, then +rendering the template with ``home.mak``, and the url ``/hello/about`` will +call the same method and render the ``about.mak`` template. + +.. index:: + single: resource interfaces + +.. _using_resource_interfaces: + +Using Resource Interfaces In View Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Instead of registering your views with a ``context`` that names a Python +resource *class*, you can optionally register a view callable with a +``context`` which is an :term:`interface`. An interface can be attached +arbitrarily to any resource object. View lookup treats context interfaces +specially, and therefore the identity of a resource can be divorced from that +of the class which implements it. As a result, associating a view with an +interface can provide more flexibility for sharing a single view between two +or more different implementations of a resource type. For example, if two +resource objects of different Python class types share the same interface, +you can use the same view configuration to specify both of them as a +``context``. + +In order to make use of interfaces in your application during view dispatch, +you must create an interface and mark up your resource classes or instances +with interface declarations that refer to this interface. + +To attach an interface to a resource *class*, you define the interface and +use the :func:`zope.interface.implements` function to associate the interface +with the class. + +.. code-block:: python + :linenos: + + from zope.interface import Interface + from zope.interface import implements + + class IHello(Interface): + """ A marker interface """ + + class Hello(object): + implements(IHello) + +To attach an interface to a resource *instance*, you define the interface and +use the :func:`zope.interface.alsoProvides` function to associate the +interface with the instance. This function mutates the instance in such a +way that the interface is attached to it. + +.. code-block:: python + :linenos: + + from zope.interface import Interface + from zope.interface import alsoProvides + + class IHello(Interface): + """ A marker interface """ + + class Hello(object): + pass + + def make_hello(): + hello = Hello() + alsoProvides(hello, IHello) + return hello + +Regardless of how you associate an interface, with a resource instance, or a +resource class, the resulting code to associate that interface with a view +callable is the same. Assuming the above code that defines an ``IHello`` +interface lives in the root of your application, and its module is named +"resources.py", the interface declaration below will associate the +``mypackage.views.hello_world`` view with resources that implement, or +provide, this interface. + +.. code-block:: python + :linenos: + + # config is an instance of pyramid.config.Configurator + + config.add_view('mypackage.views.hello_world', name='hello.html', + context='mypackage.resources.IHello') + +Any time a resource that is determined to be the :term:`context` provides +this interface, and a view named ``hello.html`` is looked up against it as +per the URL, the ``mypackage.views.hello_world`` view callable will be +invoked. + +Note, in cases where a view is registered against a resource class, and a +view is also registered against an interface that the resource class +implements, an ambiguity arises. Views registered for the resource class take +precedence over any views registered for any interface the resource class +implements. Thus, if one view configuration names a ``context`` of both the +class type of a resource, and another view configuration names a ``context`` +of interface implemented by the resource's class, and both view +configurations are otherwise identical, the view registered for the context's +class will "win". + +For more information about defining resources with interfaces for use within +view configuration, see :ref:`resources_which_implement_interfaces`. + +.. index:: + single: view security + pair: security; view + +.. _view_security_section: + +Configuring View Security +~~~~~~~~~~~~~~~~~~~~~~~~~ + +If an :term:`authorization policy` is active, any :term:`permission` attached +to a :term:`view configuration` found during view lookup will be verified. +This will ensure that the currently authenticated user possesses that +permission against the :term:`context` resource before the view function is +actually called. Here's an example of specifying a permission in a view +configuration using :meth:`pyramid.config.Configurator.add_view`: + +.. code-block:: python + :linenos: + + # config is an instance of pyramid.config.Configurator + + config.add_view('myproject.views.add_entry', name='add.html', + context='myproject.resources.IBlog', permission='add') + +When an :term:`authorization policy` is enabled, this view will be protected +with the ``add`` permission. The view will *not be called* if the user does +not possess the ``add`` permission relative to the current :term:`context`. +Instead the :term:`forbidden view` result will be returned to the client as +per :ref:`protecting_views`. + +.. index:: + single: debugging not found errors + single: not found error (debugging) + +.. _debug_notfound_section: + +:exc:`NotFound` Errors +~~~~~~~~~~~~~~~~~~~~~~ + +It's useful to be able to debug :exc:`NotFound` error responses when they +occur unexpectedly due to an application registry misconfiguration. To debug +these errors, use the ``PYRAMID_DEBUG_NOTFOUND`` environment variable or the +``debug_notfound`` configuration file setting. Details of why a view was not +found will be printed to ``stderr``, and the browser representation of the +error will include the same information. See :ref:`environment_chapter` for +more information about how, and where to set these values. + diff --git a/docs/narr/views.rst b/docs/narr/views.rst index 08ae5d5d8..48bb7f829 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -31,9 +31,9 @@ in the :term:`request` against :term:`view configuration` statements made by the developer to choose the most appropriate view callable for a specific set of circumstances. -This chapter provides documentation detailing the process of creating -view callables, documentation about performing view configuration, and -a detailed explanation of view lookup. +This chapter describes how view callables work. In the +:ref:`view_config_chapter` chapter, there are details about performing +view configuration, and a detailed explanation of view lookup. View Callables -------------- @@ -51,13 +51,14 @@ the request object contains everything your application needs to know about the specific HTTP request being made. A view callable's ultimate responsibility is to create a :mod:`Pyramid` -:term:`Response` object. This can be done by creating the response in -the view callable code and returning it directly. However, if a view -callable does not return a response itself, it can be configured to use -a :term:`renderer` that converts its return value into a -:term:`Response` object. Using renderers is the common way that -templates are used with view callables to generate markup. See the -:ref:`renderers_chapter` chapter for details. +:term:`Response` object. This can be done by creating the response +object in the view callable code and returning it directly, as we will +be doing in this chapter. However, if a view callable does not return a +response itself, it can be configured to use a :term:`renderer` that +converts its return value into a :term:`Response` object. Using +renderers is the common way that templates are used with view callables +to generate markup. See the :ref:`renderers_chapter` chapter for +details. .. index:: single: view calling convention @@ -66,7 +67,7 @@ templates are used with view callables to generate markup. See the .. _function_as_view: Defining a View Callable as a Function -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +-------------------------------------- One of the easiest way to define a view callable is to create a function that accepts a single argument named ``request``, and which returns a @@ -88,7 +89,7 @@ implemented as a function: .. _class_as_view: Defining a View Callable as a Class -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +----------------------------------- A view callable may also be represented by a Python class instead of a function. When a view callable is a class, the calling semantics are @@ -138,7 +139,7 @@ method expected to return a response, you can either: .. _request_and_context_view_definitions: Alternate View Callable Argument/Calling Conventions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +---------------------------------------------------- Usually, view callables are defined to accept only a single argument: ``request``. However, view callables may alternately be defined as classes, @@ -213,7 +214,7 @@ access to the context via ``request.context``. .. _the_response: View Callable Responses -~~~~~~~~~~~~~~~~~~~~~~~ +----------------------- A view callable may always return an object that implements the :app:`Pyramid` :term:`Response` interface. The easiest way to return something that @@ -259,7 +260,7 @@ These attributes form the notional "Pyramid Response interface". .. _http_redirect: Using a View Callable to Do an HTTP Redirect -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +-------------------------------------------- You can issue an HTTP redirect from within a view by returning a particular kind of response. @@ -298,7 +299,7 @@ Unauthorized``. .. _special_exceptions_in_callables: Using Special Exceptions In View Callables -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------------------ Usually when a Python exception is raised within a view callable, :app:`Pyramid` allows the exception to propagate all the way out to the @@ -328,7 +329,7 @@ available to the view which :app:`Pyramid` invokes as .. _exception_views: Exception Views -~~~~~~~~~~~~~~~~ +--------------- The machinery which allows the special :exc:`pyramid.exceptions.NotFound` and :exc:`pyramid.exceptions.Forbidden` exceptions to be caught by specialized @@ -412,1084 +413,3 @@ Exception views can be configured with any view registration mechanism: single: forms, views, and unicode single: views, forms, and unicode -Handling Form Submissions in View Callables (Unicode and Character Set Issues) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Most web applications need to accept form submissions from web browsers and -various other clients. In :app:`Pyramid`, form submission handling logic is -always part of a :term:`view`. For a general overview of how to handle form -submission data using the :term:`WebOb` API, see :ref:`webob_chapter` and -`"Query and POST variables" within the WebOb documentation -`_. -:app:`Pyramid` defers to WebOb for its request and response implementations, -and handling form submission data is a property of the request -implementation. Understanding WebOb's request API is the key to -understanding how to process form submission data. - -There are some defaults that you need to be aware of when trying to handle -form submission data in a :app:`Pyramid` view. Having high-order (i.e., -non-ASCII) characters in data contained within form submissions is -exceedingly common, and the UTF-8 encoding is the most common encoding used -on the web for character data. Since Unicode values are much saner than -working with and storing bytestrings, :app:`Pyramid` configures the -:term:`WebOb` request machinery to attempt to decode form submission values -into Unicode from UTF-8 implicitly. This implicit decoding happens when view -code obtains form field values via the ``request.params``, ``request.GET``, -or ``request.POST`` APIs (see :ref:`request_module` for details about these -APIs). - -.. note:: - - Many people find the difference between Unicode and UTF-8 confusing. - Unicode is a standard for representing text that supports most of the - world's writing systems. However, there are many ways that Unicode data - can be encoded into bytes for transit and storage. UTF-8 is a specific - encoding for Unicode, that is backwards-compatible with ASCII. This makes - UTF-8 very convenient for encoding data where a large subset of that data - is ASCII characters, which is largely true on the web. UTF-8 is also the - standard character encoding for URLs. - -As an example, let's assume that the following form page is served up to a -browser client, and its ``action`` points at some :app:`Pyramid` view code: - -.. code-block:: xml - :linenos: - - - - - -
-
- -
-
- -
- -
- - -The ``myview`` view code in the :app:`Pyramid` application *must* expect that -the values returned by ``request.params`` will be of type ``unicode``, as -opposed to type ``str``. The following will work to accept a form post from -the above form: - -.. code-block:: python - :linenos: - - def myview(request): - firstname = request.params['firstname'] - lastname = request.params['lastname'] - -But the following ``myview`` view code *may not* work, as it tries to decode -already-decoded (``unicode``) values obtained from ``request.params``: - -.. code-block:: python - :linenos: - - def myview(request): - # the .decode('utf-8') will break below if there are any high-order - # characters in the firstname or lastname - firstname = request.params['firstname'].decode('utf-8') - lastname = request.params['lastname'].decode('utf-8') - -For implicit decoding to work reliably, you should ensure that every form you -render that posts to a :app:`Pyramid` view explicitly defines a charset -encoding of UTF-8. This can be done via a response that has a -``;charset=UTF-8`` in its ``Content-Type`` header; or, as in the form above, -with a ``meta http-equiv`` tag that implies that the charset is UTF-8 within -the HTML ``head`` of the page containing the form. This must be done -explicitly because all known browser clients assume that they should encode -form data in the same character set implied by ``Content-Type`` value of the -response containing the form when subsequently submitting that form. There is -no other generally accepted way to tell browser clients which charset to use -to encode form data. If you do not specify an encoding explicitly, the -browser client will choose to encode form data in its default character set -before submitting it, which may not be UTF-8 as the server expects. If a -request containing form data encoded in a non-UTF8 charset is handled by your -view code, eventually the request code accessed within your view will throw -an error when it can't decode some high-order character encoded in another -character set within form data, e.g., when ``request.params['somename']`` is -accessed. - -If you are using the :class:`pyramid.response.Response` class to generate a -response, or if you use the ``render_template_*`` templating APIs, the UTF-8 -charset is set automatically as the default via the ``Content-Type`` header. -If you return a ``Content-Type`` header without an explicit charset, a -request will add a ``;charset=utf-8`` trailer to the ``Content-Type`` header -value for you, for response content types that are textual -(e.g. ``text/html``, ``application/xml``, etc) as it is rendered. If you are -using your own response object, you will need to ensure you do this yourself. - -.. note:: Only the *values* of request params obtained via - ``request.params``, ``request.GET`` or ``request.POST`` are decoded - to Unicode objects implicitly in the :app:`Pyramid` default - configuration. The keys are still (byte) strings. - -.. index:: - single: view configuration - -.. _view_configuration: - -View Configuration: Mapping a Resource or URL Pattern to a View Callable ------------------------------------------------------------------------- - -A developer makes a :term:`view callable` available for use within a -:app:`Pyramid` application via :term:`view configuration`. A view -configuration associates a view callable with a set of statements that -determine the set of circumstances which must be true for the view callable -to be invoked. - -A view configuration statement is made about information present in the -:term:`context` resource and the :term:`request`. - -View configuration is performed in one of these ways: - -- by running a :term:`scan` against application source code which has a - :class:`pyramid.view.view_config` decorator attached to a Python object as - per :class:`pyramid.view.view_config` and - :ref:`mapping_views_using_a_decorator_section`. - -- by using the :meth:`pyramid.config.Configurator.add_view` method as per - :meth:`pyramid.config.Configurator.add_view` and - :ref:`mapping_views_using_imperative_config_section`. - -- By specifying a view within a :term:`route configuration`. View - configuration via a route configuration is performed by using the - :meth:`pyramid.config.Configurator.add_route` method, passing a ``view`` - argument specifying a view callable. - -- by using the :meth:`pyramid.config.Configurator.add_handler` against a - :term:`view handler` class (useful only for :term:`URL dispatch` - applications). - -.. note:: You can also add view configuration by adding a ````, - ```` or ```` declaration to :term:`ZCML` used by your - application as per :ref:`mapping_views_using_zcml_section`, - :ref:`view_directive`, :ref:`route_directive` or :ref:`handler_directive`. - -.. _view_configuration_parameters: - -View Configuration Parameters -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -All forms of view configuration accept the same general types of arguments. - -Many arguments supplied during view configuration are :term:`view predicate` -arguments. View predicate arguments used during view configuration are used -to narrow the set of circumstances in which :mod:`view lookup` will find a -particular view callable. In general, the fewer number of predicates which -are supplied to a particular view configuration, the more likely it is that -the associated view callable will be invoked. The greater the number -supplied, the less likely. - -Some view configuration arguments are non-predicate arguments. These tend to -modify the response of the view callable or prevent the view callable from -being invoked due to an authorization policy. The presence of non-predicate -arguments in a view configuration does not narrow the circumstances in which -the view callable will be invoked. - -Non-Predicate Arguments -+++++++++++++++++++++++ - -``permission`` - The name of a :term:`permission` that the user must possess in order to - invoke the :term:`view callable`. See :ref:`view_security_section` for - more information about view security and permissions. - - If ``permission`` is not supplied, no permission is registered for this - view (it's accessible by any caller). - -``attr`` - The view machinery defaults to using the ``__call__`` method of the - :term:`view callable` (or the function itself, if the view callable is a - function) to obtain a response. The ``attr`` value allows you to vary the - method attribute used to obtain the response. For example, if your view - was a class, and the class has a method named ``index`` and you wanted to - use this method instead of the class' ``__call__`` method to return the - response, you'd say ``attr="index"`` in the view configuration for the - view. This is most useful when the view definition is a class. - - If ``attr`` is not supplied, ``None`` is used (implying the function itself - if the view is a function, or the ``__call__`` callable attribute if the - view is a class). - -``renderer`` - Denotes the :term:`renderer` implementation which will be used to construct - a :term:`response` from the associated view callable's return value. (see - also :ref:`renderers_chapter`). - - This is either a single string term (e.g. ``json``) or a string implying a - path or :term:`asset specification` (e.g. ``templates/views.pt``) naming a - :term:`renderer` implementation. If the ``renderer`` value does not - contain a dot (``.``), the specified string will be used to look up a - renderer implementation, and that renderer implementation will be used to - construct a response from the view return value. If the ``renderer`` value - contains a dot (``.``), the specified term will be treated as a path, and - the filename extension of the last element in the path will be used to look - up the renderer implementation, which will be passed the full path. - - When the renderer is a path, although a path is usually just a simple - relative pathname (e.g. ``templates/foo.pt``, implying that a template - named "foo.pt" is in the "templates" directory relative to the directory of - the current :term:`package`), a path can be absolute, starting with a slash - on UNIX or a drive letter prefix on Windows. The path can alternately be a - :term:`asset specification` in the form - ``some.dotted.package_name:relative/path``, making it possible to address - template assets which live in a separate package. - - The ``renderer`` attribute is optional. If it is not defined, the "null" - renderer is assumed (no rendering is performed and the value is passed back - to the upstream :app:`Pyramid` machinery unmolested). Note that if the - view callable itself returns a :term:`response` (see :ref:`the_response`), - the specified renderer implementation is never called. - -``wrapper`` - The :term:`view name` of a different :term:`view configuration` which will - receive the response body of this view as the ``request.wrapped_body`` - attribute of its own :term:`request`, and the :term:`response` returned by - this view as the ``request.wrapped_response`` attribute of its own request. - Using a wrapper makes it possible to "chain" views together to form a - composite response. The response of the outermost wrapper view will be - returned to the user. The wrapper view will be found as any view is found: - see :ref:`view_lookup`. The "best" wrapper view will be found based on the - lookup ordering: "under the hood" this wrapper view is looked up via - ``pyramid.view.render_view_to_response(context, request, - 'wrapper_viewname')``. The context and request of a wrapper view is the - same context and request of the inner view. - - If ``wrapper`` is not supplied, no wrapper view is used. - -Predicate Arguments -+++++++++++++++++++ - -These arguments modify view lookup behavior. In general, the more predicate -arguments that are supplied, the more specific, and narrower the usage of the -configured view. - -``name`` - The :term:`view name` required to match this view callable. Read - :ref:`traversal_chapter` to understand the concept of a view name. - - If ``name`` is not supplied, the empty string is used (implying the default - view). - -``context`` - An object representing a Python class that the :term:`context` resource - must be an instance of *or* the :term:`interface` that the :term:`context` - resource must provide in order for this view to be found and called. This - predicate is true when the :term:`context` resource is an instance of the - represented class or if the :term:`context` resource provides the - represented interface; it is otherwise false. - - If ``context`` is not supplied, the value ``None``, which matches any - resource, is used. - -``route_name`` - If ``route_name`` is supplied, the view callable will be invoked only when - the named route has matched. - - This value must match the ``name`` of a :term:`route configuration` - declaration (see :ref:`urldispatch_chapter`) that must match before this - view will be called. Note that the ``route`` configuration referred to by - ``route_name`` will usually have a ``*traverse`` token in the value of its - ``pattern``, representing a part of the path that will be used by - :term:`traversal` against the result of the route's :term:`root factory`. - - If ``route_name`` is not supplied, the view callable will be have a chance - of being invoked if no other route was matched. This is when the - request/context pair found via :term:`resource location` does not indicate - it matched any configured route. - -``request_type`` - This value should be an :term:`interface` that the :term:`request` must - provide in order for this view to be found and called. - - If ``request_type`` is not supplied, the value ``None`` is used, implying - any request type. - - *This is an advanced feature, not often used by "civilians"*. - -``request_method`` - This value can either be one of the strings ``GET``, ``POST``, ``PUT``, - ``DELETE``, or ``HEAD`` representing an HTTP ``REQUEST_METHOD``. A view - declaration with this argument ensures that the view will only be called - when the request's ``method`` attribute (aka the ``REQUEST_METHOD`` of the - WSGI environment) string matches the supplied value. - - If ``request_method`` is not supplied, the view will be invoked regardless - of the ``REQUEST_METHOD`` of the :term:`WSGI` environment. - -``request_param`` - This value can be any string. A view declaration with this argument - ensures that the view will only be called when the :term:`request` has a - key in the ``request.params`` dictionary (an HTTP ``GET`` or ``POST`` - variable) that has a name which matches the supplied value. - - If the value supplied has a ``=`` sign in it, - e.g. ``request_params="foo=123"``, then the key (``foo``) must both exist - in the ``request.params`` dictionary, *and* the value must match the right - hand side of the expression (``123``) for the view to "match" the current - request. - - If ``request_param`` is not supplied, the view will be invoked without - consideration of keys and values in the ``request.params`` dictionary. - -``containment`` - This value should be a reference to a Python class or :term:`interface` - that a parent object in the context resource's :term:`lineage` must provide - in order for this view to be found and called. The resources in your - resource tree must be "location-aware" to use this feature. - - If ``containment`` is not supplied, the interfaces and classes in the - lineage are not considered when deciding whether or not to invoke the view - callable. - - See :ref:`location_aware` for more information about location-awareness. - -``xhr`` - This value should be either ``True`` or ``False``. If this value is - specified and is ``True``, the :term:`WSGI` environment must possess an - ``HTTP_X_REQUESTED_WITH`` (aka ``X-Requested-With``) header that has the - value ``XMLHttpRequest`` for the associated view callable to be found and - called. This is useful for detecting AJAX requests issued from jQuery, - Prototype and other Javascript libraries. - - If ``xhr`` is not specified, the ``HTTP_X_REQUESTED_WITH`` HTTP header is - not taken into consideration when deciding whether or not to invoke the - associated view callable. - -``accept`` - The value of this argument represents a match query for one or more - mimetypes in the ``Accept`` HTTP request header. If this value is - specified, it must be in one of the following forms: a mimetype match token - in the form ``text/plain``, a wildcard mimetype match token in the form - ``text/*`` or a match-all wildcard mimetype match token in the form - ``*/*``. If any of the forms matches the ``Accept`` header of the request, - this predicate will be true. - - If ``accept`` is not specified, the ``HTTP_ACCEPT`` HTTP header is not - taken into consideration when deciding whether or not to invoke the - associated view callable. - -``header`` - This value represents an HTTP header name or a header name/value pair. - - If ``header`` is specified, it must be a header name or a - ``headername:headervalue`` pair. - - If ``header`` is specified without a value (a bare header name only, - e.g. ``If-Modified-Since``), the view will only be invoked if the HTTP - header exists with any value in the request. - - If ``header`` is specified, and possesses a name/value pair - (e.g. ``User-Agent:Mozilla/.*``), the view will only be invoked if the HTTP - header exists *and* the HTTP header matches the value requested. When the - ``headervalue`` contains a ``:`` (colon), it will be considered a - name/value pair (e.g. ``User-Agent:Mozilla/.*`` or ``Host:localhost``). - The value portion should be a regular expression. - - Whether or not the value represents a header name or a header name/value - pair, the case of the header name is not significant. - - If ``header`` is not specified, the composition, presence or absence of - HTTP headers is not taken into consideration when deciding whether or not - to invoke the associated view callable. - -``path_info`` - This value represents a regular expression pattern that will be tested - against the ``PATH_INFO`` WSGI environment variable to decide whether or - not to call the associated view callable. If the regex matches, this - predicate will be ``True``. - - If ``path_info`` is not specified, the WSGI ``PATH_INFO`` is not taken into - consideration when deciding whether or not to invoke the associated view - callable. - -``custom_predicates`` - If ``custom_predicates`` is specified, it must be a sequence of references - to custom predicate callables. Use custom predicates when no set of - predefined predicates do what you need. Custom predicates can be combined - with predefined predicates as necessary. Each custom predicate callable - should accept two arguments: ``context`` and ``request`` and should return - either ``True`` or ``False`` after doing arbitrary evaluation of the - context resource and/or the request. If all callables return ``True``, the - associated view callable will be considered viable for a given request. - - If ``custom_predicates`` is not specified, no custom predicates are - used. - -.. index:: - single: view_config decorator - -.. _mapping_views_using_a_decorator_section: - -View Configuration Using the ``@view_config`` Decorator -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -For better locality of reference, you may use the -:class:`pyramid.view.view_config` decorator to associate your view functions -with URLs instead of using :term:`ZCML` or imperative configuration for the -same purpose. - -.. warning:: - - Using this feature tends to slows down application startup slightly, as - more work is performed at application startup to scan for view - declarations. - -Usage of the ``view_config`` decorator is a form of :term:`declarative -configuration`, like ZCML, but in decorator form. -:class:`pyramid.view.view_config` can be used to associate :term:`view -configuration` information -- as done via the equivalent imperative code or -ZCML -- with a function that acts as a :app:`Pyramid` view callable. All -arguments to the :meth:`pyramid.config.Configurator.add_view` method (save -for the ``view`` argument) are available in decorator form and mean precisely -the same thing. - -An example of the :class:`pyramid.view.view_config` decorator might reside in -a :app:`Pyramid` application module ``views.py``: - -.. ignore-next-block -.. code-block:: python - :linenos: - - from resources import MyResource - from pyramid.view import view_config - from pyramid.response import Response - - @view_config(name='my_view', request_method='POST', context=MyResource, - permission='read') - def my_view(request): - return Response('OK') - -Using this decorator as above replaces the need to add this imperative -configuration stanza: - -.. ignore-next-block -.. code-block:: python - :linenos: - - config.add_view('.views.my_view', name='my_view', request_method='POST', - context=MyResource, permission='read') - -All arguments to ``view_config`` may be omitted. For example: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - from pyramid.view import view_config - - @view_config() - def my_view(request): - """ My view """ - return Response() - -Such a registration as the one directly above implies that the view name will -be ``my_view``, registered with a ``context`` argument that matches any -resource type, using no permission, registered against requests with any -request method, request type, request param, route name, or containment. - -The mere existence of a ``@view_config`` decorator doesn't suffice to perform -view configuration. All that the decorator does is "annotate" the function -with your configuration declarations, it doesn't process them. To make -:app:`Pyramid` process your :class:`pyramid.view.view_config` declarations, -you *must* do use the ``scan`` method of a -:class:`pyramid.config.Configurator`: - -.. code-block:: python - :linenos: - - # config is assumed to be an instance of the - # pyramid.config.Configurator class - config.scan() - -.. note:: See :ref:`zcml_scanning` for information about how to invoke a scan - via ZCML (if you're not using imperative configuration). - -Please see :ref:`decorations_and_code_scanning` for detailed information -about what happens when code is scanned for configuration declarations -resulting from use of decorators like :class:`pyramid.view.view_config`. - -See :ref:`configuration_module` for additional API arguments to the -:meth:`pyramid.config.Configurator.scan` method. For example, the method -allows you to supply a ``package`` argument to better control exactly *which* -code will be scanned. - -``@view_config`` Placement -++++++++++++++++++++++++++ - -A :class:`pyramid.view.view_config` decorator can be placed in various points -in your application. - -If your view callable is a function, it may be used as a function decorator: - -.. code-block:: python - :linenos: - - from pyramid.view import view_config - from pyramid.response import Response - - @view_config(name='edit') - def edit(request): - return Response('edited!') - -If your view callable is a class, the decorator can also be used as a class -decorator in Python 2.6 and better (Python 2.5 and below do not support class -decorators). All the arguments to the decorator are the same when applied -against a class as when they are applied against a function. For example: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - from pyramid.view import view_config - - @view_config() - class MyView(object): - def __init__(self, request): - self.request = request - - def __call__(self): - return Response('hello') - -You can use the :class:`pyramid.view.view_config` decorator as a simple -callable to manually decorate classes in Python 2.5 and below without the -decorator syntactic sugar, if you wish: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - from pyramid.view import view_config - - class MyView(object): - def __init__(self, request): - self.request = request - - def __call__(self): - return Response('hello') - - my_view = view_config()(MyView) - -More than one :class:`pyramid.view.view_config` decorator can be stacked on -top of any number of others. Each decorator creates a separate view -registration. For example: - -.. code-block:: python - :linenos: - - from pyramid.view import view_config - from pyramid.response import Response - - @view_config(name='edit') - @view_config(name='change') - def edit(request): - return Response('edited!') - -This registers the same view under two different names. - -The decorator can also be used against class methods: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - from pyramid.view import view_config - - class MyView(object): - def __init__(self, request): - self.request = request - - @view_config(name='hello') - def amethod(self): - return Response('hello') - -When the decorator is used against a class method, a view is registered for -the *class*, so the class constructor must accept an argument list in one of -two forms: either it must accept a single argument ``request`` or it must -accept two arguments, ``context, request``. - -The method which is decorated must return a :term:`response`. - -Using the decorator against a particular method of a class is equivalent to -using the ``attr`` parameter in a decorator attached to the class itself. -For example, the above registration implied by the decorator being used -against the ``amethod`` method could be spelled equivalently as the below: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - from pyramid.view import view_config - - @view_config(attr='amethod', name='hello') - class MyView(object): - def __init__(self, request): - self.request = request - - def amethod(self): - return Response('hello') - -.. index:: - single: add_view - -.. _mapping_views_using_imperative_config_section: - -View Registration Using :meth:`~pyramid.config.Configurator.add_view` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The :meth:`pyramid.config.Configurator.add_view` method within -:ref:`configuration_module` is used to configure a view imperatively. The -arguments to this method are very similar to the arguments that you provide -to the ``@view_config`` decorator. For example: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - - def hello_world(request): - return Response('hello!') - - # config is assumed to be an instance of the - # pyramid.config.Configurator class - config.add_view(hello_world, name='hello.html') - -The first argument, ``view``, is required. It must either be a Python object -which is the view itself or a :term:`dotted Python name` to such an object. -All other arguments are optional. See -:meth:`pyramid.config.Configurator.add_view` for more information. - -.. _using_add_handler: - -Handler Registration Using :meth:`~pyramid.config.Configurator.add_handler` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:app:`Pyramid` provides the special concept of a :term:`view handler`. View -handlers are view classes that implement a number of methods, each of which -is a :term:`view callable` as a convenience for :term:`URL dispatch` users. - -.. note:: - - View handlers are *not* useful when using :term:`traversal`, only when using - :term:`url dispatch`. - -Using a view handler instead of a plain function or class :term:`view -callable` makes it unnecessary to call -:meth:`pyramid.config.Configurator.add_route` (and/or -:meth:`pyramid.config.Configurator.add_view`) "by hand" multiple times, -making it more pleasant to register a collection of views as a single class -when using :term:`url dispatch`. The view handler machinery also introduces -the concept of an ``action``, which is used as a :term:`view predicate` to -control which method of the handler is called. The method name is the -default *action name* of a handler view callable. - -The concept of a view handler is analogous to a "controller" in Pylons 1.0. - -The view handler class is initialized by :app:`Pyramid` in the same manner as -a "plain" view class. Its ``__init__`` is called with a request object (see -:ref:`class_as_view`). It implements methods, each of which is a :term:`view -callable`. When a request enters the system which corresponds with an -*action* related to one of its view callable methods, this method is called, -and it is expected to return a response. - -Here's an example view handler class: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - - from pyramid.view import action - - class Hello(object): - def __init__(self, request): - self.request = request - - def index(self): - return Response('Hello world!') - - @action(renderer="mytemplate.mak") - def bye(self): - return {} - -The :class:`pyramid.view.action` decorator is used to fine-tune the view -parameters for each potential view callable which is a method of the handler. - -Handlers are added to application configuration via the -:meth:`pyramid.config.Configurator.add_handler` API. The -:meth:`~pyramid.config.Configurator.add_handler` method will scan a -:term:`view handler` class and automatically set up view configurations for -its methods that represent "auto-exposed" view callable, or those that were -decorated explicitly with the :class:`~pyramid.view.action` decorator. This -decorator is used to setup additional view configuration information for -individual methods of the class, and can be used repeatedly for a single view -method to register multiple view configurations for it. - -.. code-block:: python - :linenos: - - from myapp.handlers import Hello - config.add_handler('hello', '/hello/{action}', handler=Hello) - -This example will result in a route being added for the pattern -``/hello/{action}``, and each method of the ``Hello`` class will then be -examined to see if it should be registered as a potential view callable when -the ``/hello/{action}`` pattern matches. The value of ``{action}`` in the -route pattern will be used to determine which view should be called, and each -view in the class will be setup with a view predicate that requires a -specific ``action`` name. By default, the action name for a method of a -handler is the method name. - -If the URL was ``/hello/index``, the above example pattern would match, and, -by default, the ``index`` method of the ``Hello`` class would be called. - -Alternatively, the action can be declared specifically for a URL to be -registered for a *specific* ``action`` name: - -.. code-block:: python - :linenos: - - from myapp.handlers import Hello - config.add_handler('hello_index', '/hello/index', - handler=Hello, action='index') - -This will result one of the methods that are configured for the ``action`` of -'index' in the ``Hello`` handler class to be called. In this case the name of -the method is the same as the action name: ``index``. However, this need not -be the case, as we will see below. - -When calling :meth:`~pyramid.config.Configurator.add_handler`, an ``action`` -is required in either the route pattern or as a keyword argument, but -**cannot appear in both places**. A ``handler`` argument must also be -supplied, which can be either a :term:`asset specification` or a Python -reference to the handler class. Additional keyword arguments are passed -directly through to :meth:`pyramid.config.Configurator.add_route`. - -For example: - -.. code-block:: python - :linenos: - - config.add_handler('hello', '/hello/{action}', - handler='mypackage.handlers.MyHandler') - -Multiple :meth:`~pyramid.config.Configurator.add_handler` calls can specify -the same handler, to register specific route names for different -handler/action combinations. For example: - -.. code-block:: python - :linenos: - - config.add_handler('hello_index', '/hello/index', - handler=Hello, action='index') - config.add_handler('bye_index', '/hello/bye', - handler=Hello, action='bye') - -.. note:: - - Handler configuration may also be added to the system via :term:`ZCML` (see - :ref:`zcml_handler_configuration`). - -View Setup in the Handler Class -+++++++++++++++++++++++++++++++ - -A handler class can have a single class level attribute called -``__autoexpose__`` which should be a regular expression or the value -``None``. It's used to determine which method names will result in additional -view configurations being registered. - -When :meth:`~pyramid.config.Configurator.add_handler` runs, every method in -the handler class will be searched and a view registered if the method name -matches the ``__autoexpose__`` regular expression, or if the method was -decorated with :class:`~pyramid.view.action`. - -Every method in the handler class that has a name meeting the -``__autoexpose__`` regular expression will have a view registered for an -``action`` name corresponding to the method name. This functionality can be -disabled by setting the ``__autoexpose__`` attribute to ``None``: - -.. code-block:: python - :linenos: - - from pyramid.view import action - - class Hello(object): - __autoexpose__ = None - - def __init__(self, request): - self.request = request - - @action() - def index(self): - return Response('Hello world!') - - @action(renderer="mytemplate.mak") - def bye(self): - return {} - -With auto-expose effectively disabled, no views will be registered for a -method unless it is specifically decorated with -:class:`~pyramid.view.action`. - -Action Decorators in a Handler -++++++++++++++++++++++++++++++ - -The :class:`~pyramid.view.action` decorator registers view configuration -information on the handler method, which is used by -:meth:`~pyramid.config.Configurator.add_handler` to setup the view -configuration. - -All keyword arguments are recorded, and passed to -:meth:`~pyramid.config.Configurator.add_view`. Any valid keyword arguments -for :meth:`~pyramid.config.Configurator.add_view` can thus be used with the -:class:`~pyramid.view.action` decorator to further restrict when the view -will be called. - -One important difference is that a handler method can respond to an -``action`` name that is different from the method name by passing in a -``name`` argument. - -Example: - -.. code-block:: python - :linenos: - - from pyramid.view import action - - class Hello(object): - def __init__(self, request): - self.request = request - - @action(name='index', renderer='created.mak', request_method='POST') - def create(self): - return {} - - @action(renderer="view_all.mak", request_method='GET') - def index(self): - return {} - -This will register two views that require the ``action`` to be ``index``, -with the additional view predicate requiring a specific request method. - -It can be useful to decorate a single method multiple times with -:class:`~pyramid.view.action`. Each action decorator will register a new view -for the method. By specifying different names and renderers for each action, -the same view logic can be exposed and rendered differently on multiple URLs. - -Example: - -.. code-block:: python - :linenos: - - from pyramid.view import action - - class Hello(object): - def __init__(self, request): - self.request = request - - @action(name='home', renderer='home.mak') - @action(name='about', renderer='about.mak') - def show_template(self): - # prep some template vars - return {} - - # in the config - config.add_handler('hello', '/hello/{action}', handler=Hello) - -With this configuration, the url ``/hello/home`` will find a view -configuration that results in calling the ``show_template`` method, then -rendering the template with ``home.mak``, and the url ``/hello/about`` will -call the same method and render the ``about.mak`` template. - -.. index:: - single: resource interfaces - -.. _using_resource_interfaces: - -Using Resource Interfaces In View Configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Instead of registering your views with a ``context`` that names a Python -resource *class*, you can optionally register a view callable with a -``context`` which is an :term:`interface`. An interface can be attached -arbitrarily to any resource object. View lookup treats context interfaces -specially, and therefore the identity of a resource can be divorced from that -of the class which implements it. As a result, associating a view with an -interface can provide more flexibility for sharing a single view between two -or more different implementations of a resource type. For example, if two -resource objects of different Python class types share the same interface, -you can use the same view configuration to specify both of them as a -``context``. - -In order to make use of interfaces in your application during view dispatch, -you must create an interface and mark up your resource classes or instances -with interface declarations that refer to this interface. - -To attach an interface to a resource *class*, you define the interface and -use the :func:`zope.interface.implements` function to associate the interface -with the class. - -.. code-block:: python - :linenos: - - from zope.interface import Interface - from zope.interface import implements - - class IHello(Interface): - """ A marker interface """ - - class Hello(object): - implements(IHello) - -To attach an interface to a resource *instance*, you define the interface and -use the :func:`zope.interface.alsoProvides` function to associate the -interface with the instance. This function mutates the instance in such a -way that the interface is attached to it. - -.. code-block:: python - :linenos: - - from zope.interface import Interface - from zope.interface import alsoProvides - - class IHello(Interface): - """ A marker interface """ - - class Hello(object): - pass - - def make_hello(): - hello = Hello() - alsoProvides(hello, IHello) - return hello - -Regardless of how you associate an interface, with a resource instance, or a -resource class, the resulting code to associate that interface with a view -callable is the same. Assuming the above code that defines an ``IHello`` -interface lives in the root of your application, and its module is named -"resources.py", the interface declaration below will associate the -``mypackage.views.hello_world`` view with resources that implement, or -provide, this interface. - -.. code-block:: python - :linenos: - - # config is an instance of pyramid.config.Configurator - - config.add_view('mypackage.views.hello_world', name='hello.html', - context='mypackage.resources.IHello') - -Any time a resource that is determined to be the :term:`context` provides -this interface, and a view named ``hello.html`` is looked up against it as -per the URL, the ``mypackage.views.hello_world`` view callable will be -invoked. - -Note, in cases where a view is registered against a resource class, and a -view is also registered against an interface that the resource class -implements, an ambiguity arises. Views registered for the resource class take -precedence over any views registered for any interface the resource class -implements. Thus, if one view configuration names a ``context`` of both the -class type of a resource, and another view configuration names a ``context`` -of interface implemented by the resource's class, and both view -configurations are otherwise identical, the view registered for the context's -class will "win". - -For more information about defining resources with interfaces for use within -view configuration, see :ref:`resources_which_implement_interfaces`. - -.. index:: - single: view security - pair: security; view - -.. _view_security_section: - -Configuring View Security -~~~~~~~~~~~~~~~~~~~~~~~~~ - -If an :term:`authorization policy` is active, any :term:`permission` attached -to a :term:`view configuration` found during view lookup will be verified. -This will ensure that the currently authenticated user possesses that -permission against the :term:`context` resource before the view function is -actually called. Here's an example of specifying a permission in a view -configuration using :meth:`pyramid.config.Configurator.add_view`: - -.. code-block:: python - :linenos: - - # config is an instance of pyramid.config.Configurator - - config.add_view('myproject.views.add_entry', name='add.html', - context='myproject.resources.IBlog', permission='add') - -When an :term:`authorization policy` is enabled, this view will be protected -with the ``add`` permission. The view will *not be called* if the user does -not possess the ``add`` permission relative to the current :term:`context`. -Instead the :term:`forbidden view` result will be returned to the client as -per :ref:`protecting_views`. - -.. index:: - single: view lookup - -.. _view_lookup: - -View Lookup and Invocation --------------------------- - -:term:`View lookup` is the :app:`Pyramid` subsystem responsible for finding -an invoking a :term:`view callable`. The view lookup subsystem is passed a -:term:`context` and a :term:`request` object. - -:term:`View configuration` information stored within in the -:term:`application registry` is compared against the context and request by -the view lookup subsystem in order to find the "best" view callable for the -set of circumstances implied by the context and request. - -Predicate attributes of view configuration can be thought of like -"narrowers". In general, the greater number of predicate attributes -possessed by a view's configuration, the more specific the circumstances need -to be before the registered view callable will be invoked. - -For any given request, a view with five predicates will always be found and -evaluated before a view with two, for example. All predicates must match for -the associated view to be called. - -This does not mean however, that :app:`Pyramid` "stops looking" when it finds -a view registration with predicates that don't match. If one set of view -predicates does not match, the "next most specific" view (if any) view is -consulted for predicates, and so on, until a view is found, or no view can be -matched up with the request. The first view with a set of predicates all of -which match the request environment will be invoked. - -If no view can be found with predicates which allow it to be matched up with -the request, :app:`Pyramid` will return an error to the user's browser, -representing a "not found" (404) page. See :ref:`changing_the_notfound_view` -for more information about changing the default notfound view. - -.. index:: - single: debugging not found errors - single: not found error (debugging) - -.. _debug_notfound_section: - -:exc:`NotFound` Errors -~~~~~~~~~~~~~~~~~~~~~~ - -It's useful to be able to debug :exc:`NotFound` error responses when they -occur unexpectedly due to an application registry misconfiguration. To debug -these errors, use the ``PYRAMID_DEBUG_NOTFOUND`` environment variable or the -``debug_notfound`` configuration file setting. Details of why a view was not -found will be printed to ``stderr``, and the browser representation of the -error will include the same information. See :ref:`environment_chapter` for -more information about how, and where to set these values. - -Further Information -------------------- - -The chapter entitled :ref:`renderers_chapter` explains how to create -functions (or instances/classes) which do not return a :term:`Response` -object, yet which still can be used as view callables. - -- cgit v1.2.3 From c7af1563eaf89694233ddd1ebf2c48b163146d01 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Sun, 2 Jan 2011 23:50:39 -0700 Subject: remove unneeded liars remorse paragraph atop renderers --- docs/narr/renderers.rst | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index 76e9562fa..d888e3376 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -3,18 +3,10 @@ Renderers ========= -In the :ref:`views_chapter` chapter, we said that a view callable must -return a :term:`Response` object. We lied. A :term:`renderer` is a service -that attempts to convert a non-Response return value of a function, class, or -instance that acts as a :term:`view callable` to a :term:`Response` object. - -Overview --------- - -A view needn't *always* return a Response object. If a view happens to -return something which does not implement the Pyramid Response interface, -:app:`Pyramid` will attempt to use a :term:`renderer` to construct a -response. For example: +A view needn't *always* return a :term:`Response` object. If a view +happens to return something which does not implement the Pyramid +Response interface, :app:`Pyramid` will attempt to use a +:term:`renderer` to construct a response. For example: .. code-block:: python :linenos: -- cgit v1.2.3 From db14e3eea2a56f67a2db34369283e7a15813d971 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Sun, 2 Jan 2011 23:52:55 -0700 Subject: :term: not :mod: --- docs/narr/viewconfig.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index 39c0a0d1f..299342b74 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -76,7 +76,7 @@ All forms of view configuration accept the same general types of arguments. Many arguments supplied during view configuration are :term:`view predicate` arguments. View predicate arguments used during view configuration are used -to narrow the set of circumstances in which :mod:`view lookup` will find a +to narrow the set of circumstances in which :term:`view lookup` will find a particular view callable. In general, the fewer number of predicates which are supplied to a -- cgit v1.2.3 From 245316b0159f75269a1a7a4c5327c712e1475cc2 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Sun, 2 Jan 2011 23:57:43 -0700 Subject: add intro paragraph to view config chapt --- docs/narr/viewconfig.rst | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'docs/narr') diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index 299342b74..372e520b4 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -8,6 +8,11 @@ View Configuration .. index:: single: view lookup +:term:`View configuration` controls how :term:`view lookup` operates in +your application. In earlier chapters, you have been exposed to a few +simple view configuration declarations without much explanation. In this +chapter we will explore the subject in detail. + .. _view_lookup: View Lookup and Invocation -- cgit v1.2.3 From 81bd32edadc54928fb84e1a35d7ccd4b34c4dc22 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Mon, 3 Jan 2011 00:10:04 -0700 Subject: add placeholder form handling chapt, may move to cookbook --- docs/narr/forms.rst | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 docs/narr/forms.rst (limited to 'docs/narr') diff --git a/docs/narr/forms.rst b/docs/narr/forms.rst new file mode 100644 index 000000000..9ba862022 --- /dev/null +++ b/docs/narr/forms.rst @@ -0,0 +1,121 @@ +.. _forms_chapter: + +Form Handling +============= + +Handling Form Submissions in View Callables (Unicode and Character Set Issues) +------------------------------------------------------------------------------ + +Most web applications need to accept form submissions from web browsers and +various other clients. In :app:`Pyramid`, form submission handling logic is +always part of a :term:`view`. For a general overview of how to handle form +submission data using the :term:`WebOb` API, see :ref:`webob_chapter` and +`"Query and POST variables" within the WebOb documentation +`_. +:app:`Pyramid` defers to WebOb for its request and response implementations, +and handling form submission data is a property of the request +implementation. Understanding WebOb's request API is the key to +understanding how to process form submission data. + +There are some defaults that you need to be aware of when trying to handle +form submission data in a :app:`Pyramid` view. Having high-order (i.e., +non-ASCII) characters in data contained within form submissions is +exceedingly common, and the UTF-8 encoding is the most common encoding used +on the web for character data. Since Unicode values are much saner than +working with and storing bytestrings, :app:`Pyramid` configures the +:term:`WebOb` request machinery to attempt to decode form submission values +into Unicode from UTF-8 implicitly. This implicit decoding happens when view +code obtains form field values via the ``request.params``, ``request.GET``, +or ``request.POST`` APIs (see :ref:`request_module` for details about these +APIs). + +.. note:: + + Many people find the difference between Unicode and UTF-8 confusing. + Unicode is a standard for representing text that supports most of the + world's writing systems. However, there are many ways that Unicode data + can be encoded into bytes for transit and storage. UTF-8 is a specific + encoding for Unicode, that is backwards-compatible with ASCII. This makes + UTF-8 very convenient for encoding data where a large subset of that data + is ASCII characters, which is largely true on the web. UTF-8 is also the + standard character encoding for URLs. + +As an example, let's assume that the following form page is served up to a +browser client, and its ``action`` points at some :app:`Pyramid` view code: + +.. code-block:: xml + :linenos: + + + + + +
+
+ +
+
+ +
+ +
+ + +The ``myview`` view code in the :app:`Pyramid` application *must* expect that +the values returned by ``request.params`` will be of type ``unicode``, as +opposed to type ``str``. The following will work to accept a form post from +the above form: + +.. code-block:: python + :linenos: + + def myview(request): + firstname = request.params['firstname'] + lastname = request.params['lastname'] + +But the following ``myview`` view code *may not* work, as it tries to decode +already-decoded (``unicode``) values obtained from ``request.params``: + +.. code-block:: python + :linenos: + + def myview(request): + # the .decode('utf-8') will break below if there are any high-order + # characters in the firstname or lastname + firstname = request.params['firstname'].decode('utf-8') + lastname = request.params['lastname'].decode('utf-8') + +For implicit decoding to work reliably, you should ensure that every form you +render that posts to a :app:`Pyramid` view explicitly defines a charset +encoding of UTF-8. This can be done via a response that has a +``;charset=UTF-8`` in its ``Content-Type`` header; or, as in the form above, +with a ``meta http-equiv`` tag that implies that the charset is UTF-8 within +the HTML ``head`` of the page containing the form. This must be done +explicitly because all known browser clients assume that they should encode +form data in the same character set implied by ``Content-Type`` value of the +response containing the form when subsequently submitting that form. There is +no other generally accepted way to tell browser clients which charset to use +to encode form data. If you do not specify an encoding explicitly, the +browser client will choose to encode form data in its default character set +before submitting it, which may not be UTF-8 as the server expects. If a +request containing form data encoded in a non-UTF8 charset is handled by your +view code, eventually the request code accessed within your view will throw +an error when it can't decode some high-order character encoded in another +character set within form data, e.g., when ``request.params['somename']`` is +accessed. + +If you are using the :class:`pyramid.response.Response` class to generate a +response, or if you use the ``render_template_*`` templating APIs, the UTF-8 +charset is set automatically as the default via the ``Content-Type`` header. +If you return a ``Content-Type`` header without an explicit charset, a +request will add a ``;charset=utf-8`` trailer to the ``Content-Type`` header +value for you, for response content types that are textual +(e.g. ``text/html``, ``application/xml``, etc) as it is rendered. If you are +using your own response object, you will need to ensure you do this yourself. + +.. note:: Only the *values* of request params obtained via + ``request.params``, ``request.GET`` or ``request.POST`` are decoded + to Unicode objects implicitly in the :app:`Pyramid` default + configuration. The keys are still (byte) strings. + + -- cgit v1.2.3 From f7c36724a22cb951f2c6cc7cdd49602be6506873 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 3 Jan 2011 02:23:42 -0500 Subject: edit much ado chapter --- docs/narr/muchadoabouttraversal.rst | 477 +++++++++++++++++++----------------- docs/narr/resources.rst | 2 + 2 files changed, 249 insertions(+), 230 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/muchadoabouttraversal.rst b/docs/narr/muchadoabouttraversal.rst index cb1c6f73a..bc1b48462 100644 --- a/docs/narr/muchadoabouttraversal.rst +++ b/docs/narr/muchadoabouttraversal.rst @@ -9,285 +9,302 @@ Much Ado About Traversal `http://blog.nonsequitarian.org/2010/much-ado-about-traversal/ `_. -A lot of folks who have been using Pylons (and, therefore, Routes-based -URL matching) are being exposed for the first time, via :app:`Pyramid`, -to new ideas such as ":term:`traversal`" and ":term:`view lookup`" as a -way to route incoming HTTP requests to callable code. This has caused a -bit of consternation in some circles. Many think that traversal is hard -to understand. Others question its usefulness; URL matching has worked -for them so far, why should they even consider dealing with another -approach, one which doesn't fit their brain and which doesn't provide -any immediately obvious value? - -This chapter is an attempt to counter these opinions. Traversal and -view lookup *are* useful. There are some straightforward, real-world -use cases that are much more easily served by a traversal-based approach -than by a pattern-matching mechanism. Even if you haven't yet hit one -of these use cases yourself, understanding these new ideas is worth the -effort for any web developer so you know when you might want to use -them. Especially because (WARNING: Bold Assertion Ahead) these ideas -are *not* particularly hard to understand. In fact, :term:`traversal` -is a straightforward metaphor easily comprehended by anyone who's ever -used a run-of-the-mill file system with folders and files. +Traversal is an alternative to :term:`URL dispatch` which allows +:app:`Pyramid` applications to map URLs to code. .. note:: - Those of you who are already familiar with traversal and view lookup - conceptually, may want to skip directly to the - :ref:`traversal_chapter` chapter, which discusses the technical - details. + Ex-Zope users whom are already familiar with traversal and view lookup + conceptually may want to skip directly to the :ref:`traversal_chapter` + chapter, which discusses technical details. This chapter is mostly aimed + at people who have previous :term:`Pylons` experience or experience in + another framework which does not provide traversal, and need an + introduction to the "why" of traversal. + +Some folks who have been using Pylons and its Routes-based URL matching for a +long time are being exposed for the first time, via :app:`Pyramid`, to new +ideas such as ":term:`traversal`" and ":term:`view lookup`" as a way to route +incoming HTTP requests to callable code. Some of the same folks believe that +traversal is hard to understand. Others question its usefulness; URL +matching has worked for them so far, why should they even consider dealing +with another approach, one which doesn't fit their brain and which doesn't +provide any immediately obvious value? + +You can be assured that if you don't want to understand traversal, you don't +have to. You can happily build :app:`Pyramid` applications with only +:term:`URL dispatch`. However, there are some straightforward, real-world +use cases that are much more easily served by a traversal-based approach than +by a pattern-matching mechanism. Even if you haven't yet hit one of these +use cases yourself, understanding these new ideas is worth the effort for any +web developer so you know when you might want to use them. :term:`Traversal` +is actually a straightforward metaphor easily comprehended by anyone who's +ever used a run-of-the-mill file system with folders and files. URL Dispatch ------------ -Let's take a step back. The problem we're trying to solve is -simple. We have an HTTP request for a particular path that -has been routed to our web application. The requested path will -possibly invoke a specific callable function defined somewhere in our -app, or it may point to nothing in which case a 404 response should be -generated. What we're trying to do is figure out is which callable -function, if any, should be invoked for a given requested path. - -URL matching (or :term:`URL dispatch` in :app:`Pyramid` parlance) -approaches this problem by parsing the URL path and comparing the -results to a set of registered "patterns", defined by a set of regular -expressions, or some other URL path templating syntax. Each pattern is -mapped to a callable function somewhere; if the request path matches a -specific pattern, the associated function is called. If the request -path matches more than one pattern, some conflict resolution scheme is -used, usually a simple order precedence so that the first match will -take priority over any subsequent matches. If a request path doesn't -match any of the defined patterns, we've got a 404. - -Just in case it's not crystal clear, we'll give an example. Using -:app:`Pyramid`'s syntax, we might have a match pattern such as -``/{userid}/photos/{photoid}``, mapped to a ``photo_view()`` function -defined somewhere in our code. Then a request for a path such as +Let's step back and consider the problem we're trying to solve, which is +simple. An HTTP request for a particular path has been routed to our web +application. The requested path will possibly invoke a specific :term:`view +callable` function defined somewhere in our app. We're trying to determine +*which* callable function, if any, should be invoked for a given requested +URL. + +Many systems, including Pyramid, offer a simple solution. They offer the +concept of "URL matching". URL matching approaches this problem by parsing +the URL path and comparing the results to a set of registered "patterns", +defined by a set of regular expressions, or some other URL path templating +syntax. Each pattern is mapped to a callable function somewhere; if the +request path matches a specific pattern, the associated function is called. +If the request path matches more than one pattern, some conflict resolution +scheme is used, usually a simple order precedence so that the first match +will take priority over any subsequent matches. If a request path doesn't +match any of the defined patterns, :app:`Pyramid` a "404 Not Found" response +is returned. + +In Pyramid, we offer an implementation of URL mapping which we call +:term:`URL dispatch`. Using :app:`Pyramid` syntax, we might have a match +pattern such as ``/{userid}/photos/{photoid}``, mapped to a ``photo_view()`` +function defined somewhere in our code. Then a request for a path such as ``/joeschmoe/photos/photo1`` would be a match, and the ``photo_view()`` function would be invoked to handle the request. Similarly, ``/{userid}/blog/{year}/{month}/{postid}`` might map to a -``blog_post_view()`` function, so -``/joeschmoe/blog/2010/12/urlmatching`` would trigger the function, -which presumably would know how to find and render the ``urlmatching`` -blog post. +``blog_post_view()`` function, so ``/joeschmoe/blog/2010/12/urlmatching`` +would trigger the function, which presumably would know how to find and +render the ``urlmatching`` blog post. Historical Refresher -------------------- -Okay, we've got :term:`URL dispatch` out of the way, soon we'll dig in -to the supposedly "harder to understand" idea of traversal. Before we -do, though, let's take a trip down memory lane. If you've been doing -web work for a while, you may remember a time when we didn't have these -fancy web frameworks. Instead, we had general purpose HTTP servers that -primarily served files off of a file system. The "root" of a given site -mapped to a particular folder somewhere on the file system. Each -segment of the request path represented a subdirectory. The final path -segment would be either a directory or a file, and once the server found -the right file it would package it up in an HTTP response and send it -back to the client. So serving up a request for -``/joeschmoe/photos/photo1`` literally meant that there was a -``joeschmoe`` folder somewhere, which contained a ``photos`` folder, -which in turn contained a ``photo1`` file. If at any point along the -way we find that there is not a folder or file matching the requested -path, we return a 404 response. - -As the web grew more dynamic, however, a little bit of extra -complexity was added. Technologies such as CGI and HTTP server -modules were developed. Files were still looked up on the file -system, but if the file ended with (for example) ``.cgi`` or ``.php``, -or if it lived in a special folder, instead of simply sending the file -to the client the server would read the file, execute it using an -interpreter of some sort, and then send the output from this process -to the client as the final result. The server configuration specified -which files would trigger some dynamic code, with the default case -being to just serve the static file. +Now that we've refreshed our understanding of :term:`URL dispatch`, we'll dig +in to the idea of traversal. Before we do, though, let's take a trip down +memory lane. If you've been doing web work for a while, you may remember a +time when we didn't have fancy web frameworks like :term:`Pylons` and +:app:`Pyramid`. Instead, we had general purpose HTTP servers that primarily +served files off of a file system. The "root" of a given site mapped to a +particular folder somewhere on the file system. Each segment of the request +URL path represented a subdirectory. The final path segment would be either +a directory or a file, and once the server found the right file it would +package it up in an HTTP response and send it back to the client. So serving +up a request for ``/joeschmoe/photos/photo1`` literally meant that there was +a ``joeschmoe`` folder somewhere, which contained a ``photos`` folder, which +in turn contained a ``photo1`` file. If at any point along the way we find +that there is not a folder or file matching the requested path, we return a +404 response. + +As the web grew more dynamic, however, a little bit of extra complexity was +added. Technologies such as CGI and HTTP server modules were developed. +Files were still looked up on the file system, but if the file ended with +(for example) ``.cgi`` or ``.php``, or if it lived in a special folder, +instead of simply sending the file to the client the server would read the +file, execute it using an interpreter of some sort, and then send the output +from this process to the client as the final result. The server +configuration specified which files would trigger some dynamic code, with the +default case being to just serve the static file. Traversal (aka Resource Location) --------------------------------- -You with me so far? Good. Because if you understand how serving -files from a file system works, then you pretty much understand -traversal. And if you understand that a server might do something -different based on what type of file a given request specifies, then -you pretty much understand view lookup. - -Wait... what!?! - .. index:: single: traversal overview -The only difference between file system lookup and traversal is that a -file system lookup is stepping through nested directories and files in -a file system tree, while traversal is stepping through nested -dictionary-type objects in an object tree. Let's take a detailed look -at one of our example paths, so we can see what I mean: - -With ``/joeschmoe/photos/photo1``, we've got 4 segments: ``/``, -``joeschmoe/``, ``photos/`` and ``photo1``. With file system -lookup we have a root folder (``/``) containing a nested folder -(``joeschmoe``), which contains ANOTHER nested folder (``photos``), -which finally contains a JPG file ("photo1"). With traversal, we -have a dictionary-like root object. Asking for the ``joeschmoe`` key -gives us another dictionary-like object. Asking this in turn for the -``photos`` key gives us yet another mapping object, which finally -(hopefully) contains the resource that we're looking for within its -values, referenced by the ``photo1`` key. +Believe it or not, if you understand how serving files from a file system +works,you understand traversal. And if you understand that a server might do +something different based on what type of file a given request specifies, +then you understand view lookup. + +The major difference between file system lookup and traversal is that a file +system lookup steps through nested directories and files in a file system +tree, while traversal steps through nested dictionary-type objects in an +:term:`resource tree`. Let's take a detailed look at one of our example +paths, so we can see what I mean: + +The path ``/joeschmoe/photos/photo1``, has four segments: ``/``, +``joeschmoe``, ``photos`` and ``photo1``. With file system lookup we might +have a root folder (``/``) containing a nested folder (``joeschmoe``), which +contains another nested folder (``photos``), which finally contains a JPG +file ("photo1"). With traversal, we instead have a dictionary-like root +object. Asking for the ``joeschmoe`` key gives us another dictionary-like +object. Asking this in turn for the ``photos`` key gives us yet another +mapping object, which finally (hopefully) contains the resource that we're +looking for within its values, referenced by the ``photo1`` key. In pure Python terms, then, the traversal or "resource location" portion of satisfying the ``/joeschmoe/photos/photo1`` request -will look like this:: +will look something like this pseudocode:: get_root()['joeschmoe']['photos']['photo1'] -Where ``get_root()`` is some function that returns our root traversal -resource. If all of the specified keys exist, then the returned object -will be the resource that is being requested, analogous to the JPG file -that was retrieved in the file system example. If a :exc:`KeyError` is -generated anywhere along the way, we get a 404. (Well, this isn't -precisely true, as you'll see when we learn about view lookup below, but -the basic idea holds.) +``get_root()`` is some function that returns a root traversal +:term:`resource`. If all of the specified keys exist, then the returned +object will be the resource that is being requested, analogous to the JPG +file that was retrieved in the file system example. If a :exc:`KeyError` is +generated anywhere along the way, :app:`Pyramid` will return 404. (This +isn't precisely true, as you'll see when we learn about view lookup below, +but the basic idea holds.) -What is a "resource"? +What Is a "Resource"? --------------------- -Okay, okay... files on a file system I understand, you might say. But -what are these nested dictionary things? Where do these objects, these -"resources", live? What *are* they? - -Well, since :app:`Pyramid` is not a highly opinionated framework, there -is no restriction on how a resource is implemented; the developer can do -whatever they want. One common pattern is to persist all of the -resources, including the root, in a database. The root object stores -the ids of all of its subresources, and provides a ``__getitem__`` -implementation that fetches them. So ``get_root()`` fetches the unique -root object, while ``get_root()['joeschmoe']`` returns a different -object, also stored in the database, which in turn has its own -subresources and ``__getitem__`` implementation, etc. These resources -could be persisted in a relational database, one of the many "NoSQL" -solutions that are becoming popular these days, or anywhere else, it -doesn't matter. As long as the returned objects provide the -dictionary-like API (i.e. as long as they have an appropriately +"Files on a file system I understand", you might say. "But what are these +nested dictionary things? Where do these objects, these 'resources', live? +What *are* they?" + +Since :app:`Pyramid` is not a highly opinionated framework, it makes no +restriction on how a :term:`resource` is implemented; a developer can +implement them as he wishes. One common pattern used is to persist all of +the resources, including the root, in a database as a graph. The root object +is a dictionarylike object. Dictionarylike objects in Python supply a +``__getitem__`` method which is called when key lookup is done. Under the +hood, when ``adict`` is a dictionarylike object, Python translates +``adict['a']`` to ``adict.__getitem__('a')``. Try doing this in a Python +interpreter prompt if you don't believe us: + +.. code-block:: text + :linenos: + + Python 2.4.6 (#2, Apr 29 2010, 00:31:48) + [GCC 4.4.3] on linux2 + Type "help", "copyright", "credits" or "license" for more information. + >>> adict = {} + >>> adict['a'] = 1 + >>> adict['a'] + 1 + >>> adict.__getitem__('a') + 1 + + +The dictionarylike root object stores the ids of all of its subresources as +keys, and provides a ``__getitem__`` implementation that fetches them. So +``get_root()`` fetches the unique root object, while +``get_root()['joeschmoe']`` returns a different object, also stored in the +database, which in turn has its own subresources and ``__getitem__`` +implementation, etc. These resources might be persisted in a relational +database, one of the many "NoSQL" solutions that are becoming popular these +days, or anywhere else, it doesn't matter. As long as the returned objects +provide the dictionary-like API (i.e. as long as they have an appropriately implemented ``__getitem__`` method) then traversal will work. -In fact, you don't need a "database" at all. You could trivially -implement a set of objects with ``__getitem__`` methods that search -for files in specific directories, and thus precisely recreate the -older mechanism of having the URL path mapped directly to a folder -structure on the file system. Traversal is in fact a superset of file +In fact, you don't need a "database" at all. You could trivially implement a +set of objects with ``__getitem__`` methods that search for files in specific +directories, and thus precisely recreate the older mechanism of having the +URL path mapped directly to a folder structure on the file system. Or you +could use plain dictionaries too. Traversal is in fact a superset of file system lookup. +.. note:: See the chapter entitled :ref:`resources_chapter` for a more + technical overview of resources. + View Lookup ----------- -At this point we're nearly there. We've covered traversal, which is -the process by which a specific resource is retrieved according to a -specific URL path. But what is this "view lookup" business? - -View lookup comes from a simple realization, namely, that there is more -than one possible action that you might want to take for a single -resource. With our photo example, for instance, you might want to view -the photo in a page, but you might also want to provide a way for the -user to edit the photo and any associated metadata. We'll call the -former the ``view`` view, and the latter will be the ``edit`` view -(Original, I know.) :app:`Pyramid` has a centralized view registry -where named views can be associated with specific resource types. So in -our example, we'll assume that we've registered ``view`` and ``edit`` -views for photo objects, and that we've specified the ``view`` view as -the default, so that ``/joeschmoe/photos/photo1/view`` and -``/joeschmoe/photos/photo1`` are equivalent. The edit view would -sensibly be provided by a request for ``/joeschmoe/photos/photo1/edit``. - -Hopefully it's clear that the first portion of the edit view's URL path -is going to resolve to the same resource as the non-edit version, -specifically the resource returned by -``get_root()['joeschmoe']['photos']['photo1']``. But traveral ends -there; the ``photo1`` resource doesn't have an ``edit`` key. In fact, -it might not even be a dictionary-like object, in which case -``photo1['edit']`` would be meaningless. When :app:`Pyramid`'s resource -location has resolved to a *leaf* resource but the entire request path -has not yet been expended, the next path segment is treated as a view -name. The registry is then checked to see if a view of the given name -has been specified for a resource of the given type. If so, the view -callable is invoked, with the resource passed in as the ``context`` -object; if not, we 404. - -This is a slight simplification, but to summarize you can think of a -request for ``/joeschmoe/photos/photo1/edit`` as ultimately converted -into the following piece of Python:: +At this point we're nearly there. We've covered traversal, which is the +process by which a specific resource is retrieved according to a specific URL +path. But what is "view lookup"? + +The need for view lookup is simple: there is more than one possible action +that you might want to take after finding a :term:`resource`. With our photo +example, for instance, you might want to view the photo in a page, but you +might also want to provide a way for the user to edit the photo and any +associated metadata. We'll call the former the ``view`` view, and the latter +will be the ``edit`` view (Original, I know.) :app:`Pyramid` has a +centralized view registry where named views can be associated with specific +resource types. So in our example, we'll assume that we've registered +``view`` and ``edit`` views for photo objects, and that we've specified the +``view`` view as the default, so that ``/joeschmoe/photos/photo1/view`` and +``/joeschmoe/photos/photo1`` are equivalent. The edit view would sensibly be +provided by a request for ``/joeschmoe/photos/photo1/edit``. + +Hopefully it's clear that the first portion of the edit view's URL path is +going to resolve to the same resource as the non-edit version, specifically +the resource returned by ``get_root()['joeschmoe']['photos']['photo1']``. +But traveral ends there; the ``photo1`` resource doesn't have an ``edit`` +key. In fact, it might not even be a dictionary-like object, in which case +``photo1['edit']`` would be meaningless. When the :app:`Pyramid` resource +location has been resolved to a *leaf* resource, but the entire request path +has not yet been expended, the *very next* path segment is treated as a +:term:`view name`. The registry is then checked to see if a view of the +given name has been specified for a resource of the given type. If so, the +view callable is invoked, with the resource passed in as the related +``context`` object (also available as ``request.context``). If a view +callable could not be found, :app:`Pyramid` will return a "404 Not Found" +response. + +You might conceptualize a request for ``/joeschmoe/photos/photo1/edit`` as +ultimately converted into the following piece of Pythonic pseudocode:: context = get_root()['joeschmoe']['photos']['photo1'] - view_callable = registry.get_view(context, 'edit') - view_callable(context, request) + view_callable = get_view(context, 'edit') + request.context = context + view_callable(request) -That's not too hard to conceptualize, is it? +The ``get_root`` and ``get_view`` functions don't really exist. Internally, +:app:`Pyramid` does something more complicated. But the example above is a +reasonable approximation of the view lookup algorithm in pseudocode. Use Cases --------- -Let's come back around to look at why we even care. Yes, maybe -traversal and view lookup isn't mind-bending rocket science. But URL -matching is easier to explain, and it's good enough, right? +Why should we care about traversal? URL matching is easier to explain, and +it's good enough, right? -In some cases, yes, but certainly not in all cases. So far we've had -very structured URLs, where our paths have had a specific, small -number of pieces, like this:: +In some cases, yes, but certainly not in all cases. So far we've had very +structured URLs, where our paths have had a specific, small number of pieces, +like this:: /{userid}/{typename}/{objectid}[/{view_name}] In all of the examples thus far, we've hard coded the typename value, -assuming that we'd know at development time what names were going to -be used ("photos", "blog", etc.). But what if we don't know what -these names will be? Or, worse yet, what if we don't know *anything* -about the structure of the URLs inside a user's folder? We could be -writing a CMS where we want the end user to be able to arbitrarily add -content and other folders inside his folder. He might decide to nest -folders dozens of layers deep. How would you construct matching -patterns that could account for every possible combination of paths -that might develop? - -It may be possible, but it's tricky at best. And your matching -patterns are going to become quite complex very quickly as you try -to handle all of the edge cases. - -With traversal, however, it's straightforward. You want 20 layers of -nesting? No problem, :app:`Pyramid` will happily call ``__getitem__`` -as long as it needs to, until it runs out of path segments or until it -gets a :exc:`KeyError`. Each resource only needs to know how to fetch -its immediate children, the traversal algorithm takes care of the rest. - -The key advantage of traversal here is that the structure of the -resource tree can live in the database, and not in the code. It's -simple to let users modify the tree at runtime to set up their own -personalized directory structures. - -Another use case in which traversal shines is when there is a need to -support a context-dependent security policy. One example might be a -document management infrastructure for a large corporation, where -members of different departments have varying access levels to the -various other departments' files. Reasonably, even specific files -might need to be made available to specific individuals. Traversal -does well here because the idea of a resource context is baked right -into the code resolution and calling process. Resource objects can -store ACLs, which can be inherited and/or overridden by the -subresources. - -If each resource can thus generate a context-based ACL, then whenever -view code is attempting to perform a sensitive action, it can check -against that ACL to see whether the current user should be allowed to -perform the action. In this way you achieve so called "instance based" -or "row level" security which is considerably harder to model using a -traditional tabular approach. :app:`Pyramid` actively supports such a -scheme, and in fact if you register your views with guard permissions -and use an authorization policy, :app:`Pyramid` can check against a -resource's ACL when deciding whether or not the view itself is available -to the current user. - -In summary, there are entire classes of problems that are more easily -served by traversal and view lookup than by :term:`URL dispatch`. If -your problems aren't of this nature, great, stick with :term:`URL -dispatch`. But if you're using :app:`Pyramid` and you ever find that -you *do* need to support one of these use cases, you'll be glad you have -traversal in your toolkit. +assuming that we'd know at development time what names were going to be used +("photos", "blog", etc.). But what if we don't know what these names will +be? Or, worse yet, what if we don't know *anything* about the structure of +the URLs inside a user's folder? We could be writing a CMS where we want the +end user to be able to arbitrarily add content and other folders inside his +folder. He might decide to nest folders dozens of layers deep. How will you +construct matching patterns that could account for every possible combination +of paths that might develop? + +It's possible, but it will make for some somewhat ugly URLs. And the +matching patterns are going to become complex quickly as you try to handle +all of the edge cases. + +With traversal, however, it's straightforward. If you want 20 layers of +nesting, it's no problem. :app:`Pyramid` will happily call ``__getitem__`` +as many times as it needs to, until it runs out of path segments or until a +resource raises a :exc:`KeyError`. Each resource only needs to know how to +fetch its immediate children, the traversal algorithm takes care of the rest. + +One of the key advantages of traversal is that the structure of the resource +tree can live in the database, and not in the code. It's simple to let users +modify the tree at runtime to set up their own personalized directory +structures. + +Another use case in which traversal shines is when there is a need to support +a context-dependent security policy. One example might be a document +management infrastructure for a large corporation, where members of different +departments have varying access levels to the various other departments' +files. Reasonably, even specific files might need to be made available to +specific individuals. Traversal does well here if your resources actually +represent the data objects related to your documents, because the idea of a +resource authorization is baked right into the code resolution and calling +process. Resource objects can store ACLs, which can be inherited and/or +overridden by the subresources. + +If each resource can thus generate a context-based ACL, then whenever view +code is attempting to perform a sensitive action, it can check against that +ACL to see whether the current user should be allowed to perform the action. +In this way you achieve so called "instance based" or "row level" security +which is considerably harder to model using a traditional tabular approach. +:app:`Pyramid` actively supports such a scheme, and in fact if you register +your views with guard permissions and use an authorization policy, +:app:`Pyramid` can check against a resource's ACL when deciding whether or +not the view itself is available to the current user. + +In summary, there are entire classes of problems that are more easily served +by traversal and view lookup than by :term:`URL dispatch`. If your problems +don't require it, great: stick with :term:`URL dispatch`. But if you're +using :app:`Pyramid` and you ever find that you *do* need to support one of +these use cases, you'll be glad you have traversal in your toolkit. .. note:: It is even possible to mix and match :term:`traversal` with diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst index b892cf3cd..cf13f8c8d 100644 --- a/docs/narr/resources.rst +++ b/docs/narr/resources.rst @@ -1,3 +1,5 @@ +.. _resources_chapter: + Resources ========= -- cgit v1.2.3 From 15c45d58e1a60bd3809e70c47e0ccd0a69d5fa2b Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Wed, 5 Jan 2011 22:21:56 -0700 Subject: slight reword for better flow --- docs/narr/sessions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index de9add3b7..3e3ad9288 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -32,7 +32,7 @@ limitation: representation of the session is fewer than 4000. This is suitable only for very small data sets. -It is, however, digitally signed, and thus its data cannot easily be +It is digitally signed, however, and thus its data cannot easily be tampered with. You can configure this session factory in your :app:`Pyramid` -- cgit v1.2.3 From 4614826b25f692ff431a110d371242a470ef0681 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Wed, 5 Jan 2011 22:35:50 -0700 Subject: reduce strength of assertion --- docs/narr/sessions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index 3e3ad9288..cb3545a06 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -113,7 +113,7 @@ documentation. Some gotchas: - Keys and values of session data must be *pickleable*. This means, - typically, that they must be instances of basic types of objects, + typically, that they are instances of basic types of objects, such as strings, lists, dictionaries, tuples, integers, etc. If you place an object in a session data key or value that is not pickleable, an error will be raised when the session is serialized. -- cgit v1.2.3 From b0b9f70b16aeb36b8588efde13b7b2475a46278b Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Wed, 5 Jan 2011 23:21:12 -0700 Subject: combine flash and csrf into sessions chapt --- docs/narr/csrf.rst | 63 ----------------- docs/narr/flash.rst | 111 ------------------------------ docs/narr/sessions.rst | 183 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 181 insertions(+), 176 deletions(-) delete mode 100644 docs/narr/csrf.rst delete mode 100644 docs/narr/flash.rst (limited to 'docs/narr') diff --git a/docs/narr/csrf.rst b/docs/narr/csrf.rst deleted file mode 100644 index 2f545fb4f..000000000 --- a/docs/narr/csrf.rst +++ /dev/null @@ -1,63 +0,0 @@ -.. _csrf_chapter: - -Preventing Cross-Site Request Forgery Attacks -============================================= - -`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 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/flash.rst b/docs/narr/flash.rst deleted file mode 100644 index 037bfc416..000000000 --- a/docs/narr/flash.rst +++ /dev/null @@ -1,111 +0,0 @@ -.. _flash_chapter: - -Flash Messages -============== - -"Flash messages" are simply a queue of message strings stored in the -:term:`session`. To use flash messaging, you must enable a :term:`session -factory` as described in :ref:`using_the_default_session_factory` or -:ref:`using_alternate_session_factories`. - -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 a number of methods of the -:term:`session` object. - -Using the ``session.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 a flash queue, creating the queue -if necessary. - -``.flash`` accepts three arguments: - -.. method:: flash(message, queue='', allow_duplicate=True) - -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. - -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. - -.. code-block:: python - - request.session.flash(msg, 'myappsqueue') - -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. - -Using the ``session.pop_flash`` Method --------------------------------------- - -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 ``pop_flash`` method. - -.. method:: pop_flash(queue='') - -.. code-block:: python - :linenos: - - >>> request.session.flash('info message') - >>> request.session.pop_flash() - ['info message'] - -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('info message') - >>> request.session.pop_flash() - ['info message'] - >>> request.session.pop_flash() - [] - -The object returned from ``pop_flash`` is a list. - -Using the ``session.peek_flash`` Method ---------------------------------------- - -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. - -.. method:: peek_flash(queue='') - -.. code-block:: python - :linenos: - - >>> request.session.flash('info message') - >>> request.session.peek_flash() - ['info message'] - >>> request.session.peek_flash() - ['info message'] - >>> request.session.pop_flash() - ['info message'] - >>> request.session.peek_flash() - [] diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index cb3545a06..a4e30520f 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -3,8 +3,8 @@ .. _sessions_chapter: -Session Objects -=============== +Sessions +======== A :term:`session` is a namespace which is valid for some period of continual activity that can be used to represent a user's interaction @@ -162,3 +162,182 @@ both types are available in :class:`pyramid.interfaces.ISession`. You might use the cookie implementation in the :mod:`pyramid.session` module as inspiration. +.. index:: + single: flash messages + +Flash Messages +-------------- + +"Flash messages" are simply a queue of message strings stored in the +:term:`session`. To use flash messaging, you must enable a :term:`session +factory` as described in :ref:`using_the_default_session_factory` or +:ref:`using_alternate_session_factories`. + +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 a number of methods of the +:term:`session` object. + +Using the ``session.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 a flash queue, creating the queue +if necessary. + +``.flash`` accepts three arguments: + +.. method:: flash(message, queue='', allow_duplicate=True) + +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. + +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. + +.. code-block:: python + + request.session.flash(msg, 'myappsqueue') + +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. + +Using the ``session.pop_flash`` Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +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 ``pop_flash`` method. + +.. method:: pop_flash(queue='') + +.. code-block:: python + :linenos: + + >>> request.session.flash('info message') + >>> request.session.pop_flash() + ['info message'] + +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('info message') + >>> request.session.pop_flash() + ['info message'] + >>> request.session.pop_flash() + [] + +The object returned from ``pop_flash`` is a list. + +Using the ``session.peek_flash`` Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +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. + +.. method:: peek_flash(queue='') + +.. code-block:: python + :linenos: + + >>> request.session.flash('info message') + >>> request.session.peek_flash() + ['info message'] + >>> request.session.peek_flash() + ['info message'] + >>> request.session.pop_flash() + ['info message'] + >>> request.session.peek_flash() + [] + +.. index:: + single: preventing cross-site request forgery attacks + single: cross-site request forgery attacks, prevention + +Preventing Cross-Site Request Forgery Attacks +--------------------------------------------- + +`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 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') + + + -- cgit v1.2.3 From 7aaeb5b93f894c57efb99985180fb893b0634a55 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Wed, 5 Jan 2011 23:21:38 -0700 Subject: don't need linenos for one line --- docs/narr/sessions.rst | 1 - 1 file changed, 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index a4e30520f..def70aa6f 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -186,7 +186,6 @@ To add a message to a flash message queue, use a session object's ``flash`` method: .. code-block:: python - :linenos: request.session.flash('mymessage') -- cgit v1.2.3 From fd3988fa3f97e217ac69abec2117bb72c2f5f262 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Wed, 5 Jan 2011 23:26:07 -0700 Subject: add parens to method references --- docs/narr/sessions.rst | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index def70aa6f..0c50a996a 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -182,17 +182,17 @@ template. The user interface consists of a number of methods of the Using the ``session.flash`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To add a message to a flash message queue, use a session object's ``flash`` +To add a message to a flash message queue, use a session object's ``flash()`` method: .. code-block:: python request.session.flash('mymessage') -The ``.flash`` method appends a message to a flash queue, creating the queue +The ``flash()`` method appends a message to a flash queue, creating the queue if necessary. -``.flash`` accepts three arguments: +``flash()`` accepts three arguments: .. method:: flash(message, queue='', allow_duplicate=True) @@ -200,14 +200,15 @@ 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. -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. +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. .. code-block:: python @@ -221,11 +222,11 @@ Using the ``session.pop_flash`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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. +``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 ``pop_flash`` method. +object's ``pop_flash()`` method. .. method:: pop_flash(queue='') @@ -237,7 +238,7 @@ object's ``pop_flash`` method. ['info message'] Calling ``session.pop_flash()`` again like above without a corresponding call -to ``session.flash`` will return an empty list, because the queue has already +to ``session.flash()`` will return an empty list, because the queue has already been popped. .. code-block:: python @@ -249,15 +250,15 @@ been popped. >>> request.session.pop_flash() [] -The object returned from ``pop_flash`` is a list. +The object returned from ``pop_flash()`` is a list. Using the ``session.peek_flash`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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. +``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. .. method:: peek_flash(queue='') -- cgit v1.2.3 From 3a6919fe80d0ba6501b52b013800832549d09d63 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Wed, 5 Jan 2011 23:31:58 -0700 Subject: remove redundant sentence --- docs/narr/sessions.rst | 1 - 1 file changed, 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index 0c50a996a..acc7bdd80 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -204,7 +204,6 @@ 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 -- cgit v1.2.3 From 2eefe0dd57f578cc11edbf8c98bf58ab86d6025c Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Wed, 5 Jan 2011 23:36:18 -0700 Subject: clarify behavior of allow_duplicate --- docs/narr/sessions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index acc7bdd80..6a538766c 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -214,7 +214,7 @@ queue. request.session.flash(msg, 'myappsqueue') The ``allow_duplicate`` argument defaults to ``True``. If this is -``False``, if you attempt to add a message to a queue which is already +``False``, and you attempt to add a message value which is already present in the queue, it will not be added. Using the ``session.pop_flash`` Method -- cgit v1.2.3 From abedea975768affa778e433ebb75697998075cc9 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Wed, 5 Jan 2011 23:44:54 -0700 Subject: incorporate return type and queue semantics into same paragraph --- docs/narr/sessions.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index 6a538766c..958ede7d6 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -222,10 +222,11 @@ Using the ``session.pop_flash`` Method 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. +pop an entire queue and return it for use. To pop a particular queue of messages from the flash object, use the session -object's ``pop_flash()`` method. +object's ``pop_flash()`` method. This returns a list of the messages +that were added to the flash queue, and empties the queue. .. method:: pop_flash(queue='') @@ -249,8 +250,6 @@ been popped. >>> request.session.pop_flash() [] -The object returned from ``pop_flash()`` is a list. - Using the ``session.peek_flash`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From 089c63c9bd0a2bf666cbfa17868f67802fe1d502 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Wed, 5 Jan 2011 23:53:25 -0700 Subject: add summary paragraph to tie things together better --- docs/narr/sessions.rst | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'docs/narr') diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index 958ede7d6..d46685c08 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -10,6 +10,11 @@ A :term:`session` is a namespace which is valid for some period of continual activity that can be used to represent a user's interaction with a web application. +This chapter describes how to configure sessions, what session +implementations :app:`Pyramid` provides out of the box, and two +session-specific features: flash messages, and cross-site request +forgery attack prevention. + .. _using_the_default_session_factory: Using The Default Session Factory -- cgit v1.2.3 From 826fd7b11d190dafe9571e10eb7c2cf96ed97732 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Wed, 5 Jan 2011 23:54:50 -0700 Subject: forgot an important session feature in summary --- docs/narr/sessions.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index d46685c08..6a6de2639 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -11,9 +11,9 @@ continual activity that can be used to represent a user's interaction with a web application. This chapter describes how to configure sessions, what session -implementations :app:`Pyramid` provides out of the box, and two -session-specific features: flash messages, and cross-site request -forgery attack prevention. +implementations :app:`Pyramid` provides out of the box, how to store and +retrieve data from sessions, and two session-specific features: flash +messages, and cross-site request forgery attack prevention. .. _using_the_default_session_factory: -- cgit v1.2.3 From e5f66f8e839ee5d7eeaebb118c9d03f11578dd14 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Wed, 5 Jan 2011 23:58:24 -0700 Subject: add parens to method references --- docs/narr/sessions.rst | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index 6a6de2639..edd24d839 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -301,19 +301,18 @@ as described in :ref:`using_the_default_session_factory` or Using the ``session.new_csrf_token`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To add a CSRF token to the session, use 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* +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. +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 +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. @@ -321,19 +320,18 @@ Using the ``session.get_csrf_token`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To get the current CSRF token from the session, use the -``session.get_csrf_token`` method. +``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 +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``: +obtained via ``session.new_csrf_token()`` as a hidden input field named +``csrf_token()``: .. code-block:: python :linenos: @@ -342,5 +340,3 @@ obtained via ``session.new_csrf_token`` as a hidden input field named if token != request.POST['csrf_token']: raise ValueError('CSRF token did not match') - - -- cgit v1.2.3 From f8f2fa32bcbec2334e02b9f16ee72d40e2fa857b Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Thu, 6 Jan 2011 00:00:34 -0700 Subject: clarify --- docs/narr/sessions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index edd24d839..cce77ca5b 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -293,7 +293,7 @@ application to perform some command that requires elevated privileges. You can avoid most of these attacks by making sure that 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 +actions in code which requires elevated privileges that 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`. -- cgit v1.2.3 From edd530c7bf07ff902585b57a136c0ab8fafc9254 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Thu, 6 Jan 2011 00:06:31 -0700 Subject: clarify by promoting parenthetical, add comment requesting some advice --- docs/narr/sessions.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index cce77ca5b..bd0fe69bf 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -327,7 +327,7 @@ To get the current CSRF token from the session, use the 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 +*token* string generated by 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 @@ -340,3 +340,6 @@ obtained via ``session.new_csrf_token()`` as a hidden input field named if token != request.POST['csrf_token']: raise ValueError('CSRF token did not match') +.. comment:: + XXX Some advice on when a new csrf token should be generated would be + useful. At login time? When the form is generated? -- cgit v1.2.3 From 3fe316cb75b4311fbc4c7610f140e423b30d9ff6 Mon Sep 17 00:00:00 2001 From: Casey Duncan Date: Thu, 6 Jan 2011 00:11:22 -0700 Subject: remove comment, it's more or less answered --- docs/narr/sessions.rst | 3 --- 1 file changed, 3 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index bd0fe69bf..842b838cd 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -340,6 +340,3 @@ obtained via ``session.new_csrf_token()`` as a hidden input field named if token != request.POST['csrf_token']: raise ValueError('CSRF token did not match') -.. comment:: - XXX Some advice on when a new csrf token should be generated would be - useful. At login time? When the form is generated? -- cgit v1.2.3 From 8e1f34063e06a680b795e9621d714fb908b3bbb1 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Thu, 6 Jan 2011 12:15:05 -0800 Subject: Typo: python setup.py develop.py shouldn't have that .py at the end. --- docs/narr/project.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 5e84a4fa7..c1017b5c1 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -180,7 +180,7 @@ Installing your Newly Created Project for Development To install a newly created project for development, you should ``cd`` to the newly created project directory and use the Python interpreter from the :term:`virtualenv` you created during :ref:`installing_chapter` to invoke the -command ``python setup.py develop.py`` +command ``python setup.py develop`` The file named ``setup.py`` will be in the root of the paster-generated project directory. The ``python`` you're invoking should be the one that -- cgit v1.2.3 From 780b6f1937b12ebaf99147ed996f224dc169fc9f Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 6 Jan 2011 20:47:49 -0500 Subject: get rid of incorrect usage of threadlocals during configuration in venusian example --- docs/narr/hooks.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 2917b5254..e30313d5c 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -581,7 +581,7 @@ follows: self.path = path def register(self, scanner, name, wrapped): - registry = get_current_registry() + registry = scanner.config.registry registry.getUtility(IMyUtility).register( self.path, wrapped ) -- cgit v1.2.3 From 0b0e74764fa0f2a3d6daa8f8ce70112419ea6981 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 7 Jan 2011 22:53:05 -0500 Subject: - Allow ``decorator`` and ``mapper`` arguments to view ZCML directive. - Document ``decorator`` and ``mapper`` arguments in various places. --- docs/narr/views.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'docs/narr') diff --git a/docs/narr/views.rst b/docs/narr/views.rst index 81f3e644f..813c54bee 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -658,6 +658,22 @@ Non-Predicate Arguments If ``wrapper`` is not supplied, no wrapper view is used. +``decorator`` + A :term:`dotted Python name` to function (or the function itself) which + will be used to decorate the registered :term:`view callable`. The + decorator function will be called with the view callable as a single + argument. The view callable it is passed will accept ``(context, + request)``. The decorator must return a replacement view callable which + also accepts ``(context, request)``. + +``mapper`` + A Python object or :term:`dotted Python name` which refers to a :term:`view + mapper`, or ``None``. By default it is ``None``, which indicates that the + view should use the default view mapper. This plug-point is useful for + Pyramid extension developers, but it's not very useful for 'civilians' who + are just developing stock Pyramid applications. Pay no attention to the man + behind the curtain. + Predicate Arguments +++++++++++++++++++ -- cgit v1.2.3 From c5e83c3285b12a4f08547fa5cdafcb9fbeb044e9 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 7 Jan 2011 23:47:46 -0500 Subject: - Document ``Configurator.set_view_mapper``. - Document ``__view_mapper__`` attribute and ``mapper`` argument to view configuration for view callable view mapper preference. --- docs/narr/hooks.rst | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) (limited to 'docs/narr') diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index e30313d5c..cf3f56e87 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -540,6 +540,94 @@ The default context URL generator is available for perusal as the class `_ of the :term:`Pylons` GitHub Pyramid repository. +.. index:: + single: view mapper + +Using a View Mapper +------------------- + +The default calling conventions for view callables are documented in the +:ref:`views_chapter`. You can change the way users define view callbles by +employing a :term:`view mapper`. + +A view mapper is an object that accepts a set of keyword arguments and which +returns a callable. The returned callable is called with the :term:`view +callable` object. The returned callable should itself return another +callable which can be called with the "internal calling protocol" ``(context, +request)``. + +You can use a view mapper in a number of ways: + +- by setting a ``__view_mapper__`` attribute (which is the view mapper + object) on the view callable itself + +- by passing the mapper object to + :meth:`pyramid.config.Configurator.add_view` (or its declarative/decorator + equivalents) as the ``mapper`` argument. + +- by registering a *default* view mapper. + +Here's an example of a view mapper that emulates (somewhat) a Pylons +"controller". The mapper is initialized with some keyword arguments. Its +``__call__`` method accepts the view object (which will be a class). It uses +the ``attr`` keyword argument it is passed to determine which attribute +should be used as an action method. The wrapper method it returns accepts +``(context, request)`` and returns the result of calling the action method +with keyword arguments implied by the :term:`matchdict` after popping the +``action`` out of it. This somewhat emulates the Pylons style of calling +action methods with routing parameters pulled out of the route matching dict +as keyword arguments. + +.. code-block:: python + :linenos: + + # framework + + class PylonsControllerViewMapper(object): + def __init__(self, **kw): + self.kw = kw + + def __call__(self, view): + attr = self.kw['attr'] + def wrapper(context, request): + matchdict = request.matchdict.copy() + matchdict.pop('action', None) + inst = view() + meth = getattr(inst, attr) + return meth(**matchdict) + return wrapper + + class BaseController(object): + __view_mapper__ = PylonsControllerViewMapper + +A user might make use of these framework components like so: + +.. code-block:: python + :linenos: + + # user application + + from webob import Response + from pyramid.config import Configurator + from paste.httpserver import serve + + class MyController(BaseController): + def index(self, id): + return Response(id) + + if __name__ == '__main__': + config = Configurator() + config.add_handler('one', '/{id}', MyController, action='index') + config.add_handler('two', '/{action}/{id}', MyController) + serve(config.make_wsgi_app()) + +The :meth:`pyramid.config.Configurator.set_default_mapper` method can be used +to set a *default* view mapper (overriding the superdefault view mapper used +by Pyramid itself). + +A *single* view registration can use a view mapper by passing the mapper as +the ``mapper`` argument to :meth:`pyramid.config.Configuration.add_view`. + .. index:: single: configuration decorator -- cgit v1.2.3 From 46d30f532edc7017b3dcc5233ef050aca5d7d586 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 8 Jan 2011 00:28:45 -0500 Subject: redocument relationship between get_csrf_token and new_csrf_token --- docs/narr/sessions.rst | 52 +++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 24 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index 842b838cd..0ed52b563 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -298,24 +298,6 @@ 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 - - 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -326,12 +308,20 @@ To get the current CSRF token from the session, use the token = request.session.get_csrf_token() -The ``get_csrf_token()`` method accepts no arguments. It returns the "current" -*token* string generated by 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()``: +The ``session.get_csrf_token()`` method accepts no arguments. It returns a +CSRF *token* string. If ``session.get_csrf_token()`` or +``session.new_csrf_token()`` was invoked previously for this session, the +existing token will be returned. If no CSRF token previously existed for +this session, a new token will be will be set into the session and returned. +The newly created token will be opaque and randomized. + +You can 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()`` *again* to obtain the +current CSRF token related to the user from the session, and compare it to +the value of the hidden form field. For example, if your form rendering +included the CSRF token obtained via ``session.get_csrf_token()`` as a hidden +input field named ``csrf_token``: .. code-block:: python :linenos: @@ -340,3 +330,17 @@ obtained via ``session.new_csrf_token()`` as a hidden input field named if token != request.POST['csrf_token']: raise ValueError('CSRF token did not match') +Using the ``session.new_csrf_token`` Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To explicitly add a new CSRF token to the session, use the +``session.new_csrf_token()`` method. This differs only from +``session.get_csrf_token()`` inasmuch as it clears any existing CSRF token, +creates a new CSRF token, sets the token into the session, and returns the +token. + +.. code-block:: python + + token = request.session.new_csrf_token() + + -- cgit v1.2.3 From 5d23bd01db7ba99975a2f6c7205c2f1cd838ee52 Mon Sep 17 00:00:00 2001 From: tomlikestorock Date: Sat, 8 Jan 2011 11:44:46 -0800 Subject: fixing typo --- docs/narr/resources.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst index ac88afdfd..da1933190 100644 --- a/docs/narr/resources.rst +++ b/docs/narr/resources.rst @@ -184,7 +184,7 @@ you will reach the filesystem root directory. objects "by hand". Instead, as necessary, during traversal :app:`Pyramid` will wrap each resource (even the root resource) in a ``LocationProxy`` which will dynamically assign a ``__name__`` and a ``__parent__`` to the - traversed resrouce (based on the last traversed resource and the name + traversed resource (based on the last traversed resource and the name supplied to ``__getitem__``). The root resource will have a ``__name__`` attribute of ``None`` and a ``__parent__`` attribute of ``None``. -- cgit v1.2.3 From 522d7d704b4d4413f0222dcc404e7d7b07b73b68 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 9 Jan 2011 12:44:54 -0500 Subject: add reference target --- docs/narr/hooks.rst | 2 ++ 1 file changed, 2 insertions(+) (limited to 'docs/narr') diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index cf3f56e87..c12a5abb4 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -543,6 +543,8 @@ The default context URL generator is available for perusal as the class .. index:: single: view mapper +.. _using_a_view_mapper: + Using a View Mapper ------------------- -- cgit v1.2.3 From f52d595bd1cef5cb97d440c8ba1b1a9850ec8f4b Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 12 Jan 2011 02:36:10 -0500 Subject: Features -------- - ``pyramid.testing.setUp`` and ``pyramid.testing.tearDown`` have been undeprecated. They are now the canonical setup and teardown APIs for test configuration, replacing "direct" creation of a Configurator. This is a change designed to provide a facade that will protect against any future Configurator deprecations. Paster Templates ---------------- - All paster templates now use ``pyramid.testing.setUp`` and ``pyramid.testing.tearDown`` rather than creating a Configurator "by hand" within their ``tests.py`` module, as per decision in features above. Documentation ------------- - The wiki and wiki2 tutorials now use ``pyramid.testing.setUp`` and ``pyramid.testing.tearDown`` rather than creating a Configurator "by hand", as per decision in features above. - The "Testing" narrative chapter now explains ``pyramid.testing.setUp`` and ``pyramid.testing.tearDown`` instead of Configurator creation and ``Configurator.begin()`` and ``Configurator.end()``. --- docs/narr/MyProject/myproject/tests.py | 6 +- docs/narr/project.rst | 6 +- docs/narr/testing.rst | 101 ++++++++++++++++----------------- 3 files changed, 55 insertions(+), 58 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/MyProject/myproject/tests.py b/docs/narr/MyProject/myproject/tests.py index b14fb37af..5fa710278 100644 --- a/docs/narr/MyProject/myproject/tests.py +++ b/docs/narr/MyProject/myproject/tests.py @@ -1,15 +1,13 @@ import unittest -from pyramid.config import Configurator from pyramid import testing class ViewTests(unittest.TestCase): def setUp(self): - self.config = Configurator(autocommit=True) - self.config.begin() + self.config = testing.setUp() def tearDown(self): - self.config.end() + testing.tearDown() def test_my_view(self): from myproject.views import my_view diff --git a/docs/narr/project.rst b/docs/narr/project.rst index c1017b5c1..5af8c3231 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -876,9 +876,6 @@ represent the root. This directory contains static assets which support the ``mytemplate.pt`` template. It includes CSS and images. -.. index:: - single: tests.py - ``templates/mytemplate.pt`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -892,6 +889,9 @@ Templates are accessed and used by view configurations and sometimes by view functions themselves. See :ref:`templates_used_directly` and :ref:`templates_used_as_renderers`. +.. index:: + single: tests.py + ``tests.py`` ~~~~~~~~~~~~ diff --git a/docs/narr/testing.rst b/docs/narr/testing.rst index 007b96c2a..08c6e355b 100644 --- a/docs/narr/testing.rst +++ b/docs/narr/testing.rst @@ -78,38 +78,43 @@ See :ref:`threadlocals_chapter` for information about these functions and the data structures they return. If your code uses these ``get_current_*`` functions or calls :app:`Pyramid` -code which uses ``get_current_*`` functions, you will need to construct a -:term:`Configurator` and call its ``begin`` method within the ``setUp`` -method of your unit test and call the same Configurator's ``end`` method -within the ``tearDown`` method of your unit test. - -We'll also instruct the Configurator we use during testing to *autocommit*. -Normally when a Configurator is used by an application, it defers performing -any "real work" until its ``.commit`` method is called (often implicitly by -the :meth:`pyramid.config.Configurator.make_wsgi_app` method). Passing -``autocommit=True`` to the Configurator constructor causes the Configurator -to perform all actions implied by methods called on it immediately, which is -more convenient for unit-testing purposes than needing to call -:meth:`pyramid.config.Configurator.commit` in each test. - -The use of a Configurator and its ``begin`` and ``end`` methods allows you to -supply each unit test method in a test case with an environment that has an -isolated registry and an isolated request for the duration of a single test. -Here's an example of using this feature: +code which uses ``get_current_*`` functions, you will need to call +:func:`pyramid.testing.setUp` in your test setup and you will need to call +:func:`pyramid.testing.tearDown` in your test teardown. +:func:`~pyramid.testing.setUp` pushes a registry onto the :term:`thread +local` stack, which makes the ``get_current_*`` functions work. It returns a +:term:`Configurator` object which can be used to perform extra configuration +required by the code under test. :func:`~pyramid.testing.tearDown` pops the +thread local stack. + +Normally when a Configurator is used directly with the ``main`` block of a +Pyramid application, it defers performing any "real work" until its +``.commit`` method is called (often implicitly by the +:meth:`pyramid.config.Configurator.make_wsgi_app` method). The Configurator +returned by :func:`~pyramid.testing.setUp` is however an *autocommitting* +Configurator which performs all actions implied by methods called on it +immediately. This is more convenient for unit-testing purposes than needing +to call :meth:`pyramid.config.Configurator.commit` in each test after adding +extra configuration statements. + +The use of the :func:`~pyramid.testing.setUp` and +:func:`~pyramid.testing.tearDown` functions allows you to supply each unit +test method in a test case with an environment that has an isolated registry +and an isolated request for the duration of a single test. Here's an example +of using this feature: .. code-block:: python :linenos: import unittest - from pyramid.config import Configurator + from pyramid import testing class MyTest(unittest.TestCase): def setUp(self): - self.config = Configurator(autocommit=True) - self.config.begin() + self.config = testing.setUp() def tearDown(self): - self.config.end() + testing.tearDown() The above will make sure that :func:`pyramid.threadlocal.get_current_registry` will return the @@ -118,35 +123,33 @@ instance when :func:`pyramid.threadlocal.get_current_registry` is called in a test case method attached to ``MyTest``. Each test case method attached to ``MyTest`` will use an isolated registry. -The :meth:`pyramid.config.Configurator.begin` method accepts various -arguments that influence the code run during the test. See the -:ref:`configuration_module` chapter for information about the API of a -:term:`Configurator`, including its ``begin`` and ``end`` methods. +The :func:`~pyramid.testing.setUp` and :func:`~pyramid.testing.tearDown` +functions accepts various arguments that influence the environment of the +test. See the :ref:`testing_module` chapter for information about the extra +arguments supported by these functions. If you also want to make :func:`pyramid.get_current_request` return something other than ``None`` during the course of a single test, you can pass a -:term:`request` object into the :meth:`pyramid.config.Configurator.begin` -method of the Configurator within the ``setUp`` method of your test: +:term:`request` object into the :func:`pyramid.testing.setUp` within the +``setUp`` method of your test: .. code-block:: python :linenos: import unittest - from pyramid.config import Configurator from pyramid import testing class MyTest(unittest.TestCase): def setUp(self): - self.config = Configurator(autocommit=True) request = testing.DummyRequest() - self.config.begin(request=request) + self.config = testing.setUp(request=request) def tearDown(self): - self.config.end() + testing.tearDown() -If you pass a :term:`request` object into the ``begin`` method of the -configurator within your test case's ``setUp``, any test method attached to -the ``MyTest`` test case that directly or indirectly calls +If you pass a :term:`request` object into :func:`pyramid.testing.setUp` +within your test case's ``setUp``, any test method attached to the ``MyTest`` +test case that directly or indirectly calls :func:`pyramid.threadlocal.get_current_request` will receive the request you passed into the ``begin`` method. Otherwise, during testing, :func:`pyramid.threadlocal.get_current_request` will return ``None``. We use @@ -162,18 +165,18 @@ they're used by frameworks. Sorry. So here's a rule of thumb: if you don't *know* whether you're calling code that uses the :func:`pyramid.threadlocal.get_current_registry` or :func:`pyramid.threadlocal.get_current_request` functions, or you don't care -about any of this, but you still want to write test code, just always create -an autocommitting Configurator instance and call its ``begin`` method within -the ``setUp`` of a unit test, then subsequently call its ``end`` method in -the test's ``tearDown``. This won't really hurt anything if the application -you're testing does not call any ``get_current*`` function. +about any of this, but you still want to write test code, just always call +:func:`pyramid.testing.setUp` in your test's ``setUp`` method and +:func:`pyramid.testing.tearDown` in your tests' ``tearDown`` method. This +won't really hurt anything if the application you're testing does not call +any ``get_current*`` function. .. index:: single: pyramid.testing single: Configurator testing API Using the ``Configurator`` and ``pyramid.testing`` APIs in Unit Tests ------------------------------------------------------------------------- +--------------------------------------------------------------------- The ``Configurator`` API and the ``pyramid.testing`` module provide a number of functions which can be used during unit testing. These functions make @@ -217,16 +220,14 @@ without needing to invoke the actual application configuration implied by its :linenos: import unittest - from pyramid.config import Configurator from pyramid import testing class MyTest(unittest.TestCase): def setUp(self): - self.config = Configurator(autocommit=True) - self.config.begin() + self.config = testing.setUp() def tearDown(self): - self.config.end() + testing.tearDown() def test_view_fn_not_submitted(self): from my.package import view_fn @@ -277,8 +278,8 @@ performs a similar template registration and assertion. We assert at the end of this that the renderer's ``say`` attribute is ``Yo``, as this is what is expected of the view function in the branch it's testing. -Note that the test calls the :meth:`pyramid.config.Configurator.begin` method -in its ``setUp`` method and the ``end`` method of the same in its +Note that the test calls the :func:`pyramid.testing.setUp` function in its +``setUp`` method and the :func:`pyramid.testing.tearDown` function in its ``tearDown`` method. If you use any of the :class:`pyramid.config.Configurator` APIs during testing, be sure to use this pattern in your test case's ``setUp`` and ``tearDown``; these methods make @@ -327,7 +328,6 @@ after accessing some values that require a fully set up environment. import unittest - from pyramid.config import Configurator from pyramid import testing class ViewIntegrationTests(unittest.TestCase): @@ -337,13 +337,12 @@ after accessing some values that require a fully set up environment. (including dependent registrations for pyramid itself). """ import myapp - self.config = Configurator(package=myapp, autocommit=True) - self.config.begin() + self.config = testing.setUp() self.config.load_zcml('myapp:configure.zcml') def tearDown(self): """ Clear out the application registry """ - self.config.end() + testing.tearDown() def test_my_view(self): from myapp.views import my_view -- cgit v1.2.3 From 6a790b32db3ff52e568222fd2cbf1437dcfa09b6 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 12 Jan 2011 02:55:31 -0500 Subject: - Document the ``request.override_renderer`` attribute within the narrative "Renderers" chapter in a section named "Overriding A Renderer at Runtime". --- docs/narr/declarative.rst | 14 ++++++------- docs/narr/renderers.rst | 51 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 12 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/declarative.rst b/docs/narr/declarative.rst index f36e55b29..5c731ab06 100644 --- a/docs/narr/declarative.rst +++ b/docs/narr/declarative.rst @@ -1068,15 +1068,15 @@ See :ref:`aclauthorizationpolicy_directive` for detailed information. .. _zcml_adding_and_overriding_renderers: -Adding and Overriding Renderers via ZCML ----------------------------------------- +Adding and Changing Renderers via ZCML +-------------------------------------- New templating systems and serializers can be associated with :app:`Pyramid` renderer names. To this end, configuration declarations can be made which -override an existing :term:`renderer factory` and which add a new renderer +change an existing :term:`renderer factory` and which add a new renderer factory. -Adding or overriding a renderer via ZCML is accomplished via the +Adding or changing an existing renderer via ZCML is accomplished via the :ref:`renderer_directive` ZCML directive. For example, to add a renderer which renders views which have a @@ -1163,7 +1163,7 @@ with ``.jinja2`` as its ``renderer`` value. The ``name`` passed to the See also :ref:`renderer_directive` and :meth:`pyramid.config.Configurator.add_renderer`. -Overriding an Existing Renderer +Changing an Existing Renderer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can associate more than one filename extension with the same @@ -1184,7 +1184,7 @@ After you do this, :app:`Pyramid` will treat templates ending in both the ``.pt`` and ``.zpt`` filename extensions as Chameleon ZPT templates. -To override the default mapping in which files with a ``.pt`` +To change the default mapping in which files with a ``.pt`` extension are rendered via a Chameleon ZPT page template renderer, use a variation on the following in your application's ZCML: @@ -1200,7 +1200,7 @@ After you do this, the :term:`renderer factory` in ``my.package.pt_renderer`` will be used to render templates which end in ``.pt``, replacing the default Chameleon ZPT renderer. -To override the default mapping in which files with a ``.txt`` +To ochange the default mapping in which files with a ``.txt`` extension are rendered via a Chameleon text template renderer, use a variation on the following in your application's ZCML: diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index d888e3376..56d0a5199 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -389,12 +389,12 @@ documentation in :ref:`request_module`. .. _adding_and_overriding_renderers: -Adding and Overriding Renderers -------------------------------- +Adding and Changing Renderers +----------------------------- New templating systems and serializers can be associated with :app:`Pyramid` renderer names. To this end, configuration declarations can be made which -override an existing :term:`renderer factory`, and which add a new renderer +change an existing :term:`renderer factory`, and which add a new renderer factory. Renderers can be registered imperatively using the @@ -546,7 +546,7 @@ set as ``renderer=`` in the view configuration. See also :ref:`renderer_directive` and :meth:`pyramid.config.Configurator.add_renderer`. -Overriding an Existing Renderer +Changing an Existing Renderer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can associate more than one filename extension with the same existing @@ -563,7 +563,7 @@ extension for the same kinds of templates. For example, to associate the After you do this, :app:`Pyramid` will treat templates ending in both the ``.pt`` and ``.zpt`` filename extensions as Chameleon ZPT templates. -To override the default mapping in which files with a ``.pt`` extension are +To change the default mapping in which files with a ``.pt`` extension are rendered via a Chameleon ZPT page template renderer, use a variation on the following in your application's startup code: @@ -585,3 +585,44 @@ the ``name`` attribute to the renderer tag: config.add_renderer(None, 'mypackage.json_renderer_factory') +Overriding A Renderer At Runtime +-------------------------------- + +.. warning:: This is an advanced feature, not typically used by "civilians". + +In some circumstances, it is necessary to instruct the system to ignore the +static renderer declaration provided by the developer in view configuration, +replacing the renderer with another *after a request starts*. For example, +an "omnipresent" XML-RPC implementation that detects that the request is from +an XML-RPC client might override a view configuration statement made by the +user instructing the view to use a template renderer with one that uses an +XML-RPC renderer. This renderer would produce an XML-RPC representation of +the data returned by an arbitrary view callable. + +To use this feature, create a :class:`pyramid.events.NewRequest` +:term:`subscriber` which sniffs at the request data and which conditionally +sets an ``override_renderer`` attribute on the request itself, which is the +*name* of a registered renderer. For example: + +.. code-block:: python + :linenos: + + from pyramid.event import subscriber + from pyramid.event import NewRequest + + @subscriber(NewRequest) + def set_xmlrpc_params(event): + request = event.request + if (request.content_type == 'text/xml' + and request.method == 'POST' + and not 'soapaction' in request.headers + and not 'x-pyramid-avoid-xmlrpc' in request.headers): + params, method = parse_xmlrpc_request(request) + request.xmlrpc_params, request.xmlrpc_method = params, method + request.is_xmlrpc = True + request.override_renderer = 'xmlrpc' + return True + +The result of such a subscriber will be to replace any existing static +renderer configured by the developer with a (notional, nonexistent) XML-RPC +renderer if the request appears to come from an XML-RPC client. -- cgit v1.2.3 From 7c241987fa0f388c3c61a8b0a4a87ce9fafd8896 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 12 Jan 2011 13:34:17 -0500 Subject: fix path --- docs/narr/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/narr') diff --git a/docs/narr/install.rst b/docs/narr/install.rst index e32d0c6c3..c5ec14aa1 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -295,7 +295,7 @@ Installing :app:`Pyramid` on a Windows System c:\> cd env -#. (Optional) Consider using ``bin\activate.bat`` to make your shell +#. (Optional) Consider using ``Scripts\activate.bat`` to make your shell environment wired to use the virtualenv. #. Use ``easy_install`` pointed at the "current" index to get -- cgit v1.2.3 From e7b5fe05297d46cc169bd2f255529952569d899c Mon Sep 17 00:00:00 2001 From: Blaise Laflamme Date: Wed, 12 Jan 2011 23:07:54 -0500 Subject: Changed some references from pylonshq.com to pylonsproject.org --- docs/narr/MyProject/myproject/templates/mytemplate.pt | 16 ++++++++-------- docs/narr/introduction.rst | 2 +- docs/narr/templates.rst | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) (limited to 'docs/narr') diff --git a/docs/narr/MyProject/myproject/templates/mytemplate.pt b/docs/narr/MyProject/myproject/templates/mytemplate.pt index 02fc00eeb..ec22ef396 100644 --- a/docs/narr/MyProject/myproject/templates/mytemplate.pt +++ b/docs/narr/MyProject/myproject/templates/mytemplate.pt @@ -32,7 +32,7 @@

Search documentation

-
+
@@ -41,25 +41,25 @@

Pyramid links