Chris McDonough
2011-06-11 a4d5525cdbb6b7e614939b20a340b989258779ca
Merge branch 'master' of github.com:Pylons/pyramid
2 files added
34 files modified
682 ■■■■ changed files
CHANGES.txt 6 ●●●●● patch | view | raw | blame | history
CONTRIBUTORS.txt 2 ●●●●● patch | view | raw | blame | history
docs/narr/advconfig.rst 10 ●●●● patch | view | raw | blame | history
docs/narr/environment.rst 16 ●●●● patch | view | raw | blame | history
docs/narr/extending.rst 6 ●●●● patch | view | raw | blame | history
docs/narr/hooks.rst 35 ●●●● patch | view | raw | blame | history
docs/narr/i18n.rst 14 ●●●● patch | view | raw | blame | history
docs/narr/zca.rst 8 ●●●● patch | view | raw | blame | history
docs/tutorials/modwsgi/index.rst 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki/authorization.rst 6 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/definingviews.rst 6 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/index.rst 4 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/installation.rst 4 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/authorization/tutorial/__init__.py 4 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py 4 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/models/tutorial/__init__.py 4 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/tests/tutorial/tests.py 10 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/views/tutorial/__init__.py 4 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/tests.rst 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki2/authorization.rst 6 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/basiclayout.rst 6 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/definingmodels.rst 4 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/definingviews.rst 98 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/index.rst 5 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/installation.rst 4 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/authorization/tutorial/models.py 7 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/authorization/tutorial/views.py 8 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/models/tutorial/models.py 7 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/tests/tutorial/tests.py 266 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/views/tutorial/models.py 5 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/views/tutorial/views.py 13 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/tests.rst 74 ●●●●● patch | view | raw | blame | history
pyramid/i18n.py 7 ●●●● patch | view | raw | blame | history
pyramid/scaffolds/zodb/+package+/__init__.py_tmpl 4 ●●●● patch | view | raw | blame | history
pyramid/tests/test_i18n.py 13 ●●●●● patch | view | raw | blame | history
pyramid/url.py 8 ●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -42,6 +42,12 @@
Features
--------
- Add support for language fallbacks: when trying to translate for a
  specific territory (such as ``en_GB``) fall back to translations
  for the language (ie ``en``). This brings the translation behaviour in line
  with GNU gettext and fixes partially translated texts when using C
  extensions.
- New authentication policy:
  ``pyramid.authentication.SessionAuthenticationPolicy``, which uses a session
  to store credentials.
CONTRIBUTORS.txt
@@ -140,3 +140,5 @@
- Philip Jenvey, 2011/04/24
- Michael Merickel, 2011/5/25
- Christoph Zwerschke, 2011/06/07
docs/narr/advconfig.rst
@@ -86,9 +86,9 @@
       for action in resolveConflicts(self.actions):
     File "zope/configuration/config.py", line 1507, in resolveConflicts
       raise ConfigurationConflictError(conflicts)
   zope.configuration.config.ConfigurationConflictError:
   zope.configuration.config.ConfigurationConflictError:
           Conflicting configuration actions
     For: ('view', None, '', None, <InterfaceClass pyramid.interfaces.IView>,
     For: ('view', None, '', None, <InterfaceClass pyramid.interfaces.IView>,
           None, None, None, None, None, False, None, None, None)
    ('app.py', 14, '<module>', 'config.add_view(hello_world)')
    ('app.py', 17, '<module>', 'config.add_view(hello_world)')
@@ -291,7 +291,7 @@
: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_renderer_globals_factory`,
:meth:`~pyramid.config.Configurator.set_locale_negotiator` and
:meth:`~pyramid.config.Configurator.set_default_permission`.
@@ -425,7 +425,7 @@
   if __name__ == '__main__':
      config = Configurator()
      config.add_directive('add_newrequest_subscriber',
      config.add_directive('add_newrequest_subscriber',
                           add_newrequest_subscriber)
Once :meth:`~pyramid.config.Configurator.add_directive` is called, a user can
@@ -450,7 +450,7 @@
   :linenos:
   def includeme(config)
      config.add_directive('add_newrequest_subscriber',
      config.add_directive('add_newrequest_subscriber',
                           add_newrequest_subscriber)
The user of the add-on package ``pyramid_subscriberhelpers`` would then be
docs/narr/environment.rst
@@ -13,7 +13,7 @@
   single: environment variables
   single: ini file settings
   single: PasteDeploy settings
.. _environment_chapter:
Environment Variables and ``.ini`` File Settings
@@ -84,7 +84,7 @@
| ``PYRAMID_DEBUG_AUTHORIZATION`` |  ``debug_authorization``    |
|                                 |                             |
|                                 |                             |
|                                 |                             |
|                                 |                             |
+---------------------------------+-----------------------------+
Debugging Not Found Errors
@@ -259,7 +259,7 @@
Mako Import
+++++++++++
String list of Python statements, typically individual “import” lines, which
String list of Python statements, typically individual "import" lines, which
will be placed into the module level preamble of all generated Python modules.
@@ -330,7 +330,7 @@
``reload_templates``.
If you want to turn all ``reload`` settings (every setting that starts
with ``reload_``). on in one fell swoop, you can use
with ``reload_``) on in one fell swoop, you can use
``PYRAMID_RELOAD_ALL=1`` as an environment variable setting or you may use
``reload_all=true`` in the config file.  Note that this does not
affect settings that do not start with ``reload_*`` such as
@@ -341,10 +341,10 @@
   most useful during development, where you may wish to augment or
   override the more permanent settings in the configuration file.
   This is useful because many of the reload and debug settings may
   have performance or security (i.e., disclosure) implications
   have performance or security (i.e., disclosure) implications
   that make them undesirable in a production environment.
.. index::
.. index::
   single: reload_templates
   single: reload_assets
@@ -442,7 +442,7 @@
     registry = pyramid.threadlocal.get_current_registry()
     settings = registry.settings
     debug_frobnosticator = settings['debug_frobnosticator']
docs/narr/extending.rst
@@ -120,7 +120,7 @@
method.  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,
configurator methods also deal in routes, views, and assets.  For example, the
``add_handler`` directive of the ``pyramid_handlers`` package adds a single
route, and some number of views.
@@ -163,7 +163,7 @@
   if __name__ == '__main__':
       config.scan('someotherpackage')
       config.commit()
       config.add_view('mypackage.views.myview', name='myview'
       config.add_view('mypackage.views.myview', name='myview')
Once this is done, you should be able to extend or override the application
like any other (see :ref:`extending_the_application`).
@@ -201,7 +201,7 @@
  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
- 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`.
docs/narr/hooks.rst
@@ -84,7 +84,7 @@
the view which generates it can be overridden as necessary.
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
:term:`view configuration` which causes it to be a "forbidden" view consists
only of naming the :exc:`pyramid.exceptions.Forbidden` class as the
``context`` of the view configuration.
@@ -182,7 +182,7 @@
-----------------------
Whenever :app:`Pyramid` handles a request to perform a rendering (after a
view with a ``renderer=`` configuration attribute is invoked, or when the any
view with a ``renderer=`` configuration attribute is invoked, or when 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,
@@ -199,7 +199,7 @@
   :linenos:
   def renderer_globals_factory(system):
       return {'a':1}
       return {'a': 1}
   config = Configurator(
            renderer_globals_factory=renderer_globals_factory)
@@ -220,7 +220,7 @@
   from pyramid.config import Configurator
   def renderer_globals_factory(system):
       return {'a':1}
       return {'a': 1}
   config = Configurator()
   config.set_renderer_globals_factory(renderer_globals_factory)
@@ -237,8 +237,8 @@
-----------------------------
Subscribers to the :class:`pyramid.events.BeforeRender` event may introspect
the and modify the set of :term:`renderer globals` before they are passed to
a :term:`renderer`.  This event object iself has a dictionary-like interface
and modify the set of :term:`renderer globals` before they are passed to a
:term:`renderer`.  This event object iself has a dictionary-like interface
that can be used for this purpose.  For example:
.. code-block:: python
@@ -484,7 +484,7 @@
   from myapp.traversal import URLGenerator
   from myapp.resources import MyRoot
   config.registry.registerAdapter(URLGenerator, (MyRoot, Interface),
   config.registry.registerAdapter(URLGenerator, (MyRoot, Interface),
                                   IContextURL)
In the above example, the ``myapp.traversal.URLGenerator`` class will be used
@@ -531,7 +531,7 @@
The default calling conventions for view callables are documented in the
:ref:`views_chapter` chapter.  You can change the way users define view
callbles by employing a :term:`view mapper`.
callables 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
@@ -645,24 +645,22 @@
   :linenos:
   import venusian
   from pyramid.threadlocal import get_current_registry
   from mypackage.interfaces import IMyUtility
   class registerFunction(object):
       def __init__(self, path):
           self.path = path
       def register(self, scanner, name, wrapped):
           registry = scanner.config.registry
           registry.getUtility(IMyUtility).register(
               self.path, wrapped
               )
               self.path, wrapped)
       def __call__(self, wrapped):
           venusian.attach(wrapped, self.register)
           return wrapped
This decorator could then be used to register functions throughout
your code:
@@ -681,16 +679,17 @@
   from paste.httpserver import serve
   from pyramid.config import Configurator
   from mypackage.interfaces import IMyUtility
   class UtilityImplementation:
       implements(ISomething)
       implements(IMyUtility)
       def __init__(self):
          self.registrations = {}
       def register(self,path,callable_):
          self.registrations[path]=callable_
       def register(self, path, callable_):
          self.registrations[path] = callable_
   if __name__ == '__main__':
       config = Configurator()
docs/narr/i18n.rst
@@ -95,7 +95,7 @@
   :linenos:
   from pyramid.i18n import TranslationString
   ts = TranslationString('Add ${number}', mapping={'number':1},
   ts = TranslationString('Add ${number}', mapping={'number':1},
                          domain='form')
The above translation string named a domain of ``form``.  A
@@ -170,7 +170,7 @@
   :linenos:
   from pyramid.i18n import TranslationString as _
   ts = _('Add ${number}', msgid='add-number', mapping={'number':1},
   ts = _('Add ${number}', msgid='add-number', mapping={'number':1},
          domain='pyramid')
You can set up your own translation string factory much like the one
@@ -231,7 +231,7 @@
  A ``.pot`` file is created by a program which searches through your
  project's source code and which picks out every :term:`message
  identifier` passed to one of the '``_()`` functions
  identifier` passed to one of the ``_()`` functions
  (eg. :term:`translation string` constructions). The list of all
  message identifiers is placed into a ``.pot`` file, which serves as
  a template for creating ``.po`` files.
@@ -288,7 +288,7 @@
.. code-block:: text
   C> cd \my\virtualenv
   C> bin\easy_install Babel lingua
   C> Scripts\easy_install Babel lingua
.. index::
   single: Babel; message extractors
@@ -535,7 +535,7 @@
   from pyramid.i18n import get_localizer
   from pyramid.i18n import TranslationString
   ts = TranslationString('Add ${number}', mapping={'number':1},
   ts = TranslationString('Add ${number}', mapping={'number':1},
                          domain='pyramid')
   def aview(request):
@@ -844,7 +844,7 @@
.. code-block:: python
   :linenos:
   from pyramid.threadlocal import get_current_registry
   settings = get_current_registry().settings
   languages = settings['available_languages'].split()
@@ -897,7 +897,7 @@
   :linenos:
   from pyramid.config import Configurator
   config.add_translation_dirs('my.application:locale/',
   config.add_translation_dirs('my.application:locale/',
                               'another.application:locale/')
A message catalog in a translation directory added via
docs/narr/zca.rst
@@ -38,10 +38,10 @@
While the ZCA is an excellent tool with which to build a *framework*
such as :app:`Pyramid`, it is not always the best tool with which
to build an *application* due to the opacity of the ``zope.component``
APIs.  Accordingly, :app:`Pyramid` tends to hide the the presence
of the ZCA from application developers.  You needn't understand the
ZCA to create a :app:`Pyramid` application; its use is effectively
only a framework implementation detail.
APIs.  Accordingly, :app:`Pyramid` tends to hide the presence of the
ZCA from application developers.  You needn't understand the ZCA to
create a :app:`Pyramid` application; its use is effectively only a
framework implementation detail.
However, developers who are already used to writing :term:`Zope`
applications often still wish to use the ZCA while building a
docs/tutorials/modwsgi/index.rst
@@ -109,7 +109,7 @@
       <Directory /Users/chrism/modwsgi/env>
         WSGIProcessGroup pyramid
         Order allow, deny
         Order allow,deny
         Allow from all
       </Directory>
 
docs/tutorials/wiki/authorization.rst
@@ -64,7 +64,7 @@
~~~~~~~~~~~~~~~~~~~~~~
Add a ``security.py`` module within your package (in the same
directory as ``__init__.py``, ``views.py``, etc) with the following
directory as ``__init__.py``, ``views.py``, etc.) with the following
content:
.. literalinclude:: src/authorization/tutorial/security.py
@@ -172,7 +172,7 @@
   logged_in = authenticated_userid(request)
We'll then change the return value of each view that has an associated
``renderer`` to pass the `resulting `logged_in`` value to the
``renderer`` to pass the resulting ``logged_in`` value to the
template.  For example:
.. ignore-next-block
@@ -291,7 +291,7 @@
  credentials with the username ``editor``, password ``editor`` will
  show the edit page form being displayed.
- After logging in (as a result of hitting an edit or add page and
- After logging in (as a result of hitting an edit or add page and
  submitting the login form with the ``editor`` credentials), we'll see
  a Logout link in the upper right hand corner.  When we click it,
  we're logged out, and redirected back to the front page.
docs/tutorials/wiki/definingviews.rst
@@ -23,7 +23,7 @@
   the request as a single argument, you can obtain it via
   ``request.context``.
We're going to define several :term:`view callable` functions then wire them
We're going to define several :term:`view callable` functions, then wire them
into :app:`Pyramid` using some :term:`view configuration`.
The source code for this tutorial stage can be browsed via
@@ -202,8 +202,8 @@
If the view execution is *not* a result of a form submission (if the
expression ``'form.submitted' in request.params`` is ``False``), the view
simply renders the edit form, passing the request, the page resource, and a
save_url which will be used as the action of the generated form.
simply renders the edit form, passing the page resource, and a ``save_url``
which will be used as the action of the generated form.
If the view execution *is* a result of a form submission (if the expression
``'form.submitted' in request.params`` is ``True``), the view grabs the
docs/tutorials/wiki/index.rst
@@ -11,8 +11,8 @@
For cut and paste purposes, the source code for all stages of this
tutorial can be browsed at
`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki
<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki>`_.
`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/
<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/>`_.
.. toctree::
   :maxdepth: 2
docs/tutorials/wiki/installation.rst
@@ -122,7 +122,7 @@
   .. code-block:: text
     c:\pyramidtut> Scripts\easy_install docutils repoze.tm2 \
     c:\pyramidtut> Scripts\easy_install docutils repoze.tm2 ^
           repoze.zodbconn nose coverage
.. _making_a_project:
@@ -234,7 +234,7 @@
.. code-block:: text
  c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial \
  c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial ^
       --cover-erase --with-coverage
Looks like the code in the ``pyramid_zodb`` scaffold for ZODB projects is
docs/tutorials/wiki/src/authorization/tutorial/__init__.py
@@ -16,8 +16,8 @@
    authn_policy = AuthTktAuthenticationPolicy(secret='sosecret',
                                               callback=groupfinder)
    authz_policy = ACLAuthorizationPolicy()
    zodb_uri = settings.get('zodb_uri')
    if zodb_uri is None:
    zodb_uri = settings.get('zodb_uri', False)
    if zodb_uri is False:
        raise ValueError("No 'zodb_uri' in application configuration.")
    finder = PersistentApplicationFinder(zodb_uri, appmaker)
docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
@@ -5,8 +5,8 @@
def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    zodb_uri = settings.get('zodb_uri')
    if zodb_uri is None:
    zodb_uri = settings.get('zodb_uri', False)
    if zodb_uri is False:
        raise ValueError("No 'zodb_uri' in application configuration.")
    finder = PersistentApplicationFinder(zodb_uri, appmaker)
docs/tutorials/wiki/src/models/tutorial/__init__.py
@@ -5,8 +5,8 @@
def main(global_config, **settings):
    """ This function returns a WSGI application.
    """
    zodb_uri = settings.get('zodb_uri')
    if zodb_uri is None:
    zodb_uri = settings.get('zodb_uri', False)
    if zodb_uri is False:
        raise ValueError("No 'zodb_uri' in application configuration.")
    finder = PersistentApplicationFinder(zodb_uri, appmaker)
docs/tutorials/wiki/src/tests/tutorial/tests.py
@@ -139,16 +139,20 @@
        self.tmpdir = tempfile.mkdtemp()
        dbpath = os.path.join( self.tmpdir, 'test.db')
        settings = { 'zodb_uri' : 'file://' + dbpath }
        from repoze.zodbconn.uri import db_from_uri
        db = db_from_uri('file://' + dbpath)
        settings = { 'zodb_uri' : None }
        app = main({}, **settings)
        from repoze.zodbconn.middleware import EnvironmentDeleterMiddleware
        app = EnvironmentDeleterMiddleware(app)
        from repoze.zodbconn.connector import Connector
        app = Connector(app, db)
        self.db = db
        from webtest import TestApp
        self.testapp = TestApp(app)
    def tearDown(self):
        import shutil
        self.db.close()
        shutil.rmtree( self.tmpdir )
    def test_root(self):
docs/tutorials/wiki/src/views/tutorial/__init__.py
@@ -5,8 +5,8 @@
def main(global_config, **settings):
    """ This function returns a WSGI application.
    """
    zodb_uri = settings.get('zodb_uri')
    if zodb_uri is None:
    zodb_uri = settings.get('zodb_uri', False)
    if zodb_uri is False:
        raise ValueError("No 'zodb_uri' in application configuration.")
    finder = PersistentApplicationFinder(zodb_uri, appmaker)
docs/tutorials/wiki/tests.rst
@@ -73,6 +73,6 @@
   .........
   ----------------------------------------------------------------------
   Ran 9 tests in 0.203s
   Ran 23 tests in 1.653s
   
   OK
docs/tutorials/wiki2/authorization.rst
@@ -76,7 +76,7 @@
   :meth:`pyramid.config.Configurator.add_route` for more info.
We'll pass the ``RootFactory`` we created in the step above in as the
``root_factory`` argument to a :term:`Configurator`.
``root_factory`` argument to a :term:`Configurator`.
Configuring an Authorization Policy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -116,7 +116,7 @@
:term:`view callable`.  This is also known as a :term:`forbidden view`:
.. literalinclude:: src/authorization/tutorial/__init__.py
   :lines: 41-43
   :lines: 25,41-43
   :linenos:
   :language: python
@@ -163,7 +163,7 @@
----------------------
Add a ``security.py`` module within your package (in the same directory as
:file:`__init__.py`, :file:`views.py`, etc) with the following content:
:file:`__init__.py`, :file:`views.py`, etc.) with the following content:
.. literalinclude:: src/authorization/tutorial/security.py
   :linenos:
docs/tutorials/wiki2/basiclayout.rst
@@ -92,7 +92,7 @@
register views for the routes, mapping your patterns to code:
   .. literalinclude:: src/basiclayout/tutorial/__init__.py
      :lines: 14
      :lines: 14-15
      :language: py
The first positional ``add_view`` argument ``tutorial.views.my_view`` is the
@@ -102,7 +102,7 @@
``renderer``, which is a template which lives in the ``templates``
subdirectory of the package.  When the ``tutorial.views.my_view`` view
returns a dictionary, a :term:`renderer` will use this template to create a
response.  This
response.
Finally, we use the :meth:`pyramid.config.Configurator.make_wsgi_app`
method to return a :term:`WSGI` application:
@@ -133,7 +133,7 @@
      :linenos:
      :language: py
Next we set up a SQLAlchemy "DBSession" object:
Next we set up a SQLAlchemy "DBSession" object:
   .. literalinclude:: src/basiclayout/tutorial/models.py
      :lines: 15-16
docs/tutorials/wiki2/definingmodels.rst
@@ -45,7 +45,7 @@
   :language: python
As you can see, our ``Page`` class has a class level attribute
``__tablename__`` which equals the string ``pages``.  This means that
``__tablename__`` which equals the string ``'pages'``.  This means that
SQLAlchemy will store our wiki data in a SQL table named ``pages``.  Our Page
class will also have class-level attributes named ``id``, ``name`` and
``data`` (all instances of :class:`sqlalchemy.Column`).  These will map to
@@ -67,7 +67,7 @@
largely the same as the ``initialize_sql`` in the paster-generated
``models.py``.
Our DBSession assignment stays the same as the original generated
Our ``DBSession`` assignment stays the same as the original generated
``models.py``.
Looking at the Result of all Our Edits to ``models.py``
docs/tutorials/wiki2/definingviews.rst
@@ -25,9 +25,9 @@
into the URL by the ``pattern`` of a ``route`` statement.  For instance, if a
call to :meth:`pyramid.config.Configurator.add_route` in ``__init__.py`` had
the pattern ``{one}/{two}``, and the URL at ``http://example.com/foo/bar``
was invoked, matching this pattern, the matchdict dictionary attached to the
request passed to the view would have a ``one`` key with the value ``foo``
and a ``two`` key with the value ``bar``.
was invoked, matching this pattern, the ``matchdict`` dictionary attached to
the request passed to the view would have a ``'one'`` key with the value
``'foo'`` and a ``'two'`` key with the value ``'bar'``.
The source code for this tutorial stage can be browsed at
`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/views/
@@ -80,9 +80,9 @@
The ``view_wiki`` view function
-------------------------------
The ``view_wiki`` function will respond as the :term:`default view` of a
``Wiki`` model object.  It always redirects to a URL which represents the
path to our "FrontPage".
The ``view_wiki`` function is the :term:`default view` that will be called
when a request is made to the root URL of our wiki.  It always redirects to
a URL which represents the path to our "FrontPage".
.. literalinclude:: src/views/tutorial/views.py
   :pyobject: view_wiki
@@ -99,11 +99,10 @@
The ``view_page`` view function
-------------------------------
The ``view_page`` function will respond as the :term:`default view` of
a ``Page`` object.  The ``view_page`` function renders the
:term:`ReStructuredText` body of a page (stored as the ``data``
attribute of a Page object) as HTML.  Then it substitutes an HTML
anchor for each *WikiWord* reference in the rendered HTML using a
The ``view_page`` function will be used to show a single page of our
wiki.  It renders the :term:`ReStructuredText` body of a page (stored as
the ``data`` attribute of a Page object) as HTML.  Then it substitutes an
HTML anchor for each *WikiWord* reference in the rendered HTML using a
compiled regular expression.
.. literalinclude:: src/views/tutorial/views.py
@@ -146,15 +145,15 @@
   :linenos:
   :language: python
The matchdict will have a ``pagename`` key that matches the name of the page
we'd like to add.  If our add view is invoked via,
e.g. ``http://localhost:6543/add_page/SomeName``, the ``pagename`` value in
the matchdict will be ``SomeName``.
The ``matchdict`` will have a ``'pagename'`` key that matches the name of
the page we'd like to add.  If our add view is invoked via,
e.g. ``http://localhost:6543/add_page/SomeName``, the value for
``'pagename'`` in the ``matchdict`` will be ``'SomeName'``.
If the view execution is *not* a result of a form submission (if the
expression ``'form.submitted' in request.params`` is ``False``), the view
callable renders a template.  To do so, it generates a "save url" which the
template use as the form post URL during rendering.  We're lazy here, so
template uses as the form post URL during rendering.  We're lazy here, so
we're trying to use the same template (``templates/edit.pt``) for the add
view as well as the page edit view, so we create a dummy Page object in order
to satisfy the edit form's desire to have *some* page object exposed as
@@ -163,10 +162,10 @@
If the view execution *is* a result of a form submission (if the expression
``'form.submitted' in request.params`` is ``True``), we scrape the page body
from the form data, create a Page object using the name in the matchdict
``pagename``, and obtain the page body from the request, and save it into the
database using ``session.add``.  We then redirect back to the ``view_page``
view (the :term:`default view` for a Page) for the newly created page.
from the form data, create a Page object with this page body and the name
taken from ``matchdict['pagename']``, and save it into the database using
``session.add``.  We then redirect back to the ``view_page`` view for the
newly created page.
The ``edit_page`` view function
-------------------------------
@@ -174,7 +173,7 @@
The ``edit_page`` function will be invoked when a user clicks the "Edit this
Page" button on the view form.  It renders an edit form but it also acts as
the handler for the form it renders.  The ``matchdict`` attribute of the
request passed to the ``edit_page`` view will have a ``pagename`` key
request passed to the ``edit_page`` view will have a ``'pagename'`` key
matching the name of the page the user wants to edit.
.. literalinclude:: src/views/tutorial/views.py
@@ -184,14 +183,14 @@
If the view execution is *not* a result of a form submission (if the
expression ``'form.submitted' in request.params`` is ``False``), the view
simply renders the edit form, passing the request, the page object, and a
save_url which will be used as the action of the generated form.
simply renders the edit form, passing the page object and a ``save_url``
which will be used as the action of the generated form.
If the view execution *is* a result of a form submission (if the expression
``'form.submitted' in request.params`` is ``True``), the view grabs the
``body`` element of the request parameter and sets it as the ``data``
attribute of the page object.  It then redirects to the default view of the
wiki page, which will always be the ``view_page`` view.
``body`` element of the request parameters and sets it as the ``data``
attribute of the page object.  It then redirects to the ``view_page`` view
of the wiki page.
Viewing the Result of all Our Edits to ``views.py``
===================================================
@@ -274,7 +273,7 @@
The ``__init__.py`` file contains
:meth:`pyramid.config.Configurator.add_view` calls which serve to map
routes via :term:`url dispatch` to views.  First, we’ll get rid of the
existing route created by the template using the name ``home``. It’s only an
existing route created by the template using the name ``'home'``. It’s only an
example and isn’t relevant to our application.
We then need to add four calls to ``add_route``.  Note that the *ordering* of
@@ -282,7 +281,7 @@
the order they're found in the ``__init__.py`` file.
#. Add a declaration which maps the pattern ``/`` (signifying the root URL)
   to the route named ``view_wiki``.
   to the route named ``view_wiki``.
#. Add a declaration which maps the pattern ``/{pagename}`` to the route named
   ``view_page``.  This is the regular view for a page.
@@ -342,46 +341,3 @@
in a browser.  You should see an interactive exception handler in the
browser which allows you to examine values in a post-mortem mode.
Adding Tests
============
Since we've added a good bit of imperative code here, it's useful to
define tests for the views we've created.  We'll change our tests.py
module to look like this:
.. literalinclude:: src/views/tutorial/tests.py
   :linenos:
   :language: python
We can then run the tests using something like:
.. code-block:: text
   :linenos:
    $ python setup.py test -q
The expected output is something like:
.. code-block:: text
   :linenos:
   running test
   running egg_info
   writing requirements to tutorial.egg-info/requires.txt
   writing tutorial.egg-info/PKG-INFO
   writing top-level names to tutorial.egg-info/top_level.txt
   writing dependency_links to tutorial.egg-info/dependency_links.txt
   writing entry points to tutorial.egg-info/entry_points.txt
   unrecognized .svn/entries format in
   reading manifest file 'tutorial.egg-info/SOURCES.txt'
   writing manifest file 'tutorial.egg-info/SOURCES.txt'
   running build_ext
   ......
   ----------------------------------------------------------------------
   Ran 6 tests in 0.181s
   OK
docs/tutorials/wiki2/index.rst
@@ -11,8 +11,8 @@
For cut and paste purposes, the source code for all stages of this
tutorial can be browsed at
`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/
<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/>`_.
`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/
<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/>`_.
.. toctree::
   :maxdepth: 2
@@ -23,6 +23,7 @@
   definingmodels
   definingviews
   authorization
   tests
   distributing
docs/tutorials/wiki2/installation.rst
@@ -73,7 +73,7 @@
   .. code-block:: text
      c:\pyramidtut> Scripts\easy_install docutils \
      c:\pyramidtut> Scripts\easy_install docutils ^
               nose coverage zope.sqlalchemy SQLAlchemy repoze.tm2
@@ -205,7 +205,7 @@
.. code-block:: text
   c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial \
   c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial ^
         --cover-erase --with-coverage
Looks like our package's ``models`` module doesn't quite have 100%
docs/tutorials/wiki2/src/authorization/tutorial/models.py
@@ -26,16 +26,17 @@
    data = Column(Text)
    def __init__(self, name, data):
       self.name = name
       self.data = data
        self.name = name
        self.data = data
def initialize_sql(engine):
    DBSession.configure(bind=engine)
    Base.metadata.bind = engine
    Base.metadata.create_all(engine)
    try:
        transaction.begin()
        session = DBSession()
        page = Page('FrontPage', 'initial data')
        page = Page('FrontPage', 'This is the front page')
        session.add(page)
        transaction.commit()
    except IntegrityError:
docs/tutorials/wiki2/src/authorization/tutorial/views.py
@@ -2,7 +2,7 @@
from docutils.core import publish_parts
from pyramid.httpexceptions import HTTPFound
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from pyramid.security import authenticated_userid
from pyramid.url import route_url
@@ -19,7 +19,9 @@
def view_page(request):
    pagename = request.matchdict['pagename']
    session = DBSession()
    page = session.query(Page).filter_by(name=pagename).one()
    page = session.query(Page).filter_by(name=pagename).first()
    if page is None:
        return HTTPNotFound('No such page')
    def check(match):
        word = match.group(1)
@@ -51,7 +53,7 @@
    page = Page('', '')
    logged_in = authenticated_userid(request)
    return dict(page=page, save_url=save_url, logged_in=logged_in)
def edit_page(request):
    name = request.matchdict['pagename']
    session = DBSession()
docs/tutorials/wiki2/src/models/tutorial/models.py
@@ -24,16 +24,17 @@
    data = Column(Text)
    def __init__(self, name, data):
       self.name = name
       self.data = data
        self.name = name
        self.data = data
def initialize_sql(engine):
    DBSession.configure(bind=engine)
    Base.metadata.bind = engine
    Base.metadata.create_all(engine)
    try:
        transaction.begin()
        session = DBSession()
        page = Page('FrontPage', 'initial data')
        page = Page('FrontPage', 'This is the front page')
        session.add(page)
        transaction.commit()
    except IntegrityError:
docs/tutorials/wiki2/src/tests/tutorial/tests.py
New file
@@ -0,0 +1,266 @@
import unittest
from pyramid import testing
def _initTestingDB():
    from tutorial.models import DBSession
    from tutorial.models import Base
    from sqlalchemy import create_engine
    engine = create_engine('sqlite:///:memory:')
    DBSession.configure(bind=engine)
    Base.metadata.bind = engine
    Base.metadata.create_all(engine)
    return DBSession
def _registerRoutes(config):
    config.add_route('view_page', '{pagename}')
    config.add_route('edit_page', '{pagename}/edit_page')
    config.add_route('add_page', 'add_page/{pagename}')
class PageModelTests(unittest.TestCase):
    def setUp(self):
        self.session = _initTestingDB()
    def tearDown(self):
        self.session.remove()
    def _getTargetClass(self):
        from tutorial.models import Page
        return Page
    def _makeOne(self, name='SomeName', data='some data'):
        return self._getTargetClass()(name, data)
    def test_constructor(self):
        instance = self._makeOne()
        self.assertEqual(instance.name, 'SomeName')
        self.assertEqual(instance.data, 'some data')
class InitializeSqlTests(unittest.TestCase):
    def setUp(self):
        from tutorial.models import DBSession
        DBSession.remove()
    def tearDown(self):
        from tutorial.models import DBSession
        DBSession.remove()
    def _callFUT(self, engine):
        from tutorial.models import initialize_sql
        return initialize_sql(engine)
    def test_it(self):
        from sqlalchemy import create_engine
        engine = create_engine('sqlite:///:memory:')
        self._callFUT(engine)
        from tutorial.models import DBSession, Page
        self.assertEqual(DBSession.query(Page).one().data,
            'This is the front page')
class ViewWikiTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def _callFUT(self, request):
        from tutorial.views import view_wiki
        return view_wiki(request)
    def test_it(self):
        _registerRoutes(self.config)
        request = testing.DummyRequest()
        response = self._callFUT(request)
        self.assertEqual(response.location, 'http://example.com/FrontPage')
class ViewPageTests(unittest.TestCase):
    def setUp(self):
        self.session = _initTestingDB()
        self.config = testing.setUp()
    def tearDown(self):
        self.session.remove()
        testing.tearDown()
    def _callFUT(self, request):
        from tutorial.views import view_page
        return view_page(request)
    def test_it(self):
        from tutorial.models import Page
        request = testing.DummyRequest()
        request.matchdict['pagename'] = 'IDoExist'
        page = Page('IDoExist', 'Hello CruelWorld IDoExist')
        self.session.add(page)
        _registerRoutes(self.config)
        info = self._callFUT(request)
        self.assertEqual(info['page'], page)
        self.assertEqual(
            info['content'],
            '<div class="document">\n'
            '<p>Hello <a href="http://example.com/add_page/CruelWorld">'
            'CruelWorld</a> '
            '<a href="http://example.com/IDoExist">'
            'IDoExist</a>'
            '</p>\n</div>\n')
        self.assertEqual(info['edit_url'],
            'http://example.com/IDoExist/edit_page')
class AddPageTests(unittest.TestCase):
    def setUp(self):
        self.session = _initTestingDB()
        self.config = testing.setUp()
        self.config.begin()
    def tearDown(self):
        self.session.remove()
        testing.tearDown()
    def _callFUT(self, request):
        from tutorial.views import add_page
        return add_page(request)
    def test_it_notsubmitted(self):
        _registerRoutes(self.config)
        request = testing.DummyRequest()
        request.matchdict = {'pagename':'AnotherPage'}
        info = self._callFUT(request)
        self.assertEqual(info['page'].data,'')
        self.assertEqual(info['save_url'],
                         'http://example.com/add_page/AnotherPage')
    def test_it_submitted(self):
        from tutorial.models import Page
        _registerRoutes(self.config)
        request = testing.DummyRequest({'form.submitted':True,
                                        'body':'Hello yo!'})
        request.matchdict = {'pagename':'AnotherPage'}
        self._callFUT(request)
        page = self.session.query(Page).filter_by(name='AnotherPage').one()
        self.assertEqual(page.data, 'Hello yo!')
class EditPageTests(unittest.TestCase):
    def setUp(self):
        self.session = _initTestingDB()
        self.config = testing.setUp()
    def tearDown(self):
        self.session.remove()
        testing.tearDown()
    def _callFUT(self, request):
        from tutorial.views import edit_page
        return edit_page(request)
    def test_it_notsubmitted(self):
        from tutorial.models import Page
        _registerRoutes(self.config)
        request = testing.DummyRequest()
        request.matchdict = {'pagename':'abc'}
        page = Page('abc', 'hello')
        self.session.add(page)
        info = self._callFUT(request)
        self.assertEqual(info['page'], page)
        self.assertEqual(info['save_url'],
            'http://example.com/abc/edit_page')
    def test_it_submitted(self):
        from tutorial.models import Page
        _registerRoutes(self.config)
        request = testing.DummyRequest({'form.submitted':True,
            'body':'Hello yo!'})
        request.matchdict = {'pagename':'abc'}
        page = Page('abc', 'hello')
        self.session.add(page)
        response = self._callFUT(request)
        self.assertEqual(response.location, 'http://example.com/abc')
        self.assertEqual(page.data, 'Hello yo!')
class FunctionalTests(unittest.TestCase):
    viewer_login = '/login?login=viewer&password=viewer' \
                   '&came_from=FrontPage&form.submitted=Login'
    viewer_wrong_login = '/login?login=viewer&password=incorrect' \
                   '&came_from=FrontPage&form.submitted=Login'
    editor_login = '/login?login=editor&password=editor' \
                   '&came_from=FrontPage&form.submitted=Login'
    def setUp(self):
        from tutorial import main
        settings = { 'sqlalchemy.url': 'sqlite:///:memory:'}
        app = main({}, **settings)
        from webtest import TestApp
        self.testapp = TestApp(app)
    def tearDown(self):
        del self.testapp
        from tutorial.models import DBSession
        DBSession.remove()
    def test_root(self):
        res = self.testapp.get('/', status=302)
        self.assertTrue(not res.body)
    def test_FrontPage(self):
        res = self.testapp.get('/FrontPage', status=200)
        self.assertTrue('FrontPage' in res.body)
    def test_unexisting_page(self):
        res = self.testapp.get('/SomePage', status=404)
    def test_successful_log_in(self):
        res = self.testapp.get(self.viewer_login, status=302)
        self.assertTrue(res.location == 'FrontPage')
    def test_failed_log_in(self):
        res = self.testapp.get(self.viewer_wrong_login, status=200)
        self.assertTrue('login' in res.body)
    def test_logout_link_present_when_logged_in(self):
        self.testapp.get(self.viewer_login, status=302)
        res = self.testapp.get('/FrontPage', status=200)
        self.assertTrue('Logout' in res.body)
    def test_logout_link_not_present_after_logged_out(self):
        self.testapp.get(self.viewer_login, status=302)
        self.testapp.get('/FrontPage', status=200)
        res = self.testapp.get('/logout', status=302)
        self.assertTrue('Logout' not in res.body)
    def test_anonymous_user_cannot_edit(self):
        res = self.testapp.get('/FrontPage/edit_page', status=200)
        self.assertTrue('Login' in res.body)
    def test_anonymous_user_cannot_add(self):
        res = self.testapp.get('/add_page/NewPage', status=200)
        self.assertTrue('Login' in res.body)
    def test_viewer_user_cannot_edit(self):
        self.testapp.get(self.viewer_login, status=302)
        res = self.testapp.get('/FrontPage/edit_page', status=200)
        self.assertTrue('Login' in res.body)
    def test_viewer_user_cannot_add(self):
        self.testapp.get(self.viewer_login, status=302)
        res = self.testapp.get('/add_page/NewPage', status=200)
        self.assertTrue('Login' in res.body)
    def test_editors_member_user_can_edit(self):
        self.testapp.get(self.editor_login, status=302)
        res = self.testapp.get('/FrontPage/edit_page', status=200)
        self.assertTrue('Editing' in res.body)
    def test_editors_member_user_can_add(self):
        self.testapp.get(self.editor_login, status=302)
        res = self.testapp.get('/add_page/NewPage', status=200)
        self.assertTrue('Editing' in res.body)
    def test_editors_member_user_can_view(self):
        self.testapp.get(self.editor_login, status=302)
        res = self.testapp.get('/FrontPage', status=200)
        self.assertTrue('FrontPage' in res.body)
docs/tutorials/wiki2/src/views/tutorial/models.py
@@ -23,14 +23,15 @@
    data = Column(Text)
    def __init__(self, name, data):
       self.name = name
       self.data = data
        self.name = name
        self.data = data
def initialize_sql(engine):
    DBSession.configure(bind=engine)
    Base.metadata.bind = engine
    Base.metadata.create_all(engine)
    try:
        transaction.begin()
        session = DBSession()
        page = Page('FrontPage', 'initial data')
        session.add(page)
docs/tutorials/wiki2/src/views/tutorial/views.py
@@ -2,7 +2,7 @@
from docutils.core import publish_parts
from pyramid.httpexceptions import HTTPFound
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from pyramid.url import route_url
from tutorial.models import DBSession
@@ -16,9 +16,11 @@
                                          pagename='FrontPage'))
def view_page(request):
    matchdict = request.matchdict
    pagename = request.matchdict['pagename']
    session = DBSession()
    page = session.query(Page).filter_by(name=matchdict['pagename']).one()
    page = session.query(Page).filter_by(name=pagename).first()
    if page is None:
        return HTTPNotFound('No such page')
    def check(match):
        word = match.group(1)
@@ -32,8 +34,7 @@
    content = publish_parts(page.data, writer_name='html')['html_body']
    content = wikiwords.sub(check, content)
    edit_url = route_url('edit_page', request,
                         pagename=matchdict['pagename'])
    edit_url = route_url('edit_page', request, pagename=pagename)
    return dict(page=page, content=content, edit_url=edit_url)
def add_page(request):
@@ -48,7 +49,7 @@
    save_url = route_url('add_page', request, pagename=name)
    page = Page('', '')
    return dict(page=page, save_url=save_url)
def edit_page(request):
    name = request.matchdict['pagename']
    session = DBSession()
docs/tutorials/wiki2/tests.rst
New file
@@ -0,0 +1,74 @@
============
Adding Tests
============
We will now add tests for the models and the views and a few functional
tests in the ``tests.py``.  Tests ensure that an application works, and
that it continues to work after some changes are made in the future.
Testing the Models
==================
We write a test class for the model class ``Page`` and another test class
for the ``initialize_sql`` function.
To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided as a
result of the ``pyramid_routesalchemy`` project generator.  We'll add two
test classes: one for the ``Page`` model named ``PageModelTests``, one for the
``initialize_sql`` function named ``InitializeSqlTests``.
Testing the Views
=================
We'll modify our ``tests.py`` file, adding tests for each view function we
added above.  As a result, we'll *delete* the ``ViewTests`` test in the file,
and add four other test classes: ``ViewWikiTests``, ``ViewPageTests``,
``AddPageTests``, and ``EditPageTests``.  These test the ``view_wiki``,
``view_page``, ``add_page``, and ``edit_page`` views respectively.
Functional tests
================
We test the whole application, covering security aspects that are not
tested in the unit tests, like logging in, logging out, checking that
the ``viewer`` user cannot add or edit pages, but the ``editor`` user
can, and so on.
Viewing the results of all our edits to ``tests.py``
====================================================
Once we're done with the ``tests.py`` module, it will look a lot like the
below:
.. literalinclude:: src/tests/tutorial/tests.py
   :linenos:
   :language: python
Running the Tests
=================
We can run these tests by using ``setup.py test`` in the same way we did in
:ref:`running_tests`.  Assuming our shell's current working directory is the
"tutorial" distribution directory:
On UNIX:
.. code-block:: text
   $ ../bin/python setup.py test -q
On Windows:
.. code-block:: text
   c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q
The expected result looks something like:
.. code-block:: text
   ......................
   ----------------------------------------------------------------------
   Ran 22 tests in 2.700s
   OK
pyramid/i18n.py
@@ -151,11 +151,16 @@
    translations found in the list of translation directories."""
    translations = Translations()
    translations._catalog = {}
    locales_to_try = [current_locale_name]
    if '_' in current_locale_name:
        locales_to_try.append(current_locale_name.split('_')[0])
    for tdir in translation_directories:
        locale_dirs = [ (lname, os.path.join(tdir, lname)) for lname in
                        os.listdir(tdir) ]
        for locale_name, locale_dir in locale_dirs:
            if locale_name != current_locale_name:
            if locale_name not in locales_to_try:
                continue
            messages_dir = os.path.join(locale_dir, 'LC_MESSAGES')
            if not os.path.isdir(os.path.realpath(messages_dir)):
pyramid/scaffolds/zodb/+package+/__init__.py_tmpl
@@ -5,8 +5,8 @@
def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    zodb_uri = settings.get('zodb_uri')
    if zodb_uri is None:
    zodb_uri = settings.get('zodb_uri', False)
    if zodb_uri is False:
        raise ValueError("No 'zodb_uri' in application configuration.")
    finder = PersistentApplicationFinder(zodb_uri, appmaker)
pyramid/tests/test_i18n.py
@@ -200,6 +200,19 @@
        self.assertEqual(result.translate('Approve', 'deformsite'),
                         'Approve')
    def test_territory_fallback(self):
        import os
        from pyramid.i18n import Localizer
        here = os.path.dirname(__file__)
        localedir = os.path.join(here, 'localeapp', 'locale')
        localedirs = [localedir]
        locale_name = 'de_DE'
        result = self._callFUT(locale_name, localedirs)
        self.assertEqual(result.__class__, Localizer)
        self.assertEqual(result.translate('Approve', 'deformsite'),
                         'Genehmigen')
class Test_get_localizer(unittest.TestCase):
    def setUp(self):
        cleanUp()
pyramid/url.py
@@ -20,7 +20,7 @@
    """Generates a fully qualified URL for a named :app:`Pyramid`
    :term:`route configuration`.
    .. note:: Calling :meth:`pyramid.request.Request.route_url` can be used to
    .. note:: Calling :meth:`pyramid.Request.route_url` can be used to
              achieve the same result as :func:`pyramid.url.route_url`.
    Use the route's ``name`` as the first positional argument.  Use a
@@ -159,7 +159,7 @@
    """Generates a path (aka a 'relative URL', a URL minus the host, scheme,
    and port) for a named :app:`Pyramid` :term:`route configuration`.
    .. note:: Calling :meth:`pyramid.request.Request.route_path` can be used to
    .. note:: Calling :meth:`pyramid.Request.route_path` can be used to
              achieve the same result as :func:`pyramid.url.route_path`.
    This function accepts the same argument as :func:`pyramid.url.route_url`
@@ -191,7 +191,7 @@
    overall result of this function is always a UTF-8 encoded string
    (never Unicode).
    .. note:: Calling :meth:`pyramid.request.Request.resource_url` can be used to
    .. note:: Calling :meth:`pyramid.Request.resource_url` can be used to
              achieve the same result as :func:`pyramid.url.resource_url`.
    Examples::
@@ -325,7 +325,7 @@
    :meth:`pyramid.config.Configurator.add_static_view`
    :term:`configuration declaration` (see :ref:`static_assets_section`).
    .. note:: Calling :meth:`pyramid.request.Request.static_url` can be used to
    .. note:: Calling :meth:`pyramid.Request.static_url` can be used to
              achieve the same result as :func:`pyramid.url.static_url`.
    Example::