Chris McDonough
2012-01-10 26484029f9d4d591e9541547f6d5e381ce3a0be2
Merge branch '1.3-branch'
1 files deleted
82 files modified
1767 ■■■■■ changed files
CHANGES.txt 122 ●●●●● patch | view | raw | blame | history
TODO.txt 22 ●●●● patch | view | raw | blame | history
docs/api/renderers.rst 2 ●●● patch | view | raw | blame | history
docs/api/request.rst 55 ●●●●● patch | view | raw | blame | history
docs/conf.py 2 ●●● patch | view | raw | blame | history
docs/designdefense.rst 12 ●●●●● patch | view | raw | blame | history
docs/index.rst 1 ●●●● patch | view | raw | blame | history
docs/latexindex.rst 1 ●●●● patch | view | raw | blame | history
docs/narr/MyProject/development.ini 2 ●●● patch | view | raw | blame | history
docs/narr/MyProject/production.ini 2 ●●● patch | view | raw | blame | history
docs/narr/MyProject/setup.py 6 ●●●● patch | view | raw | blame | history
docs/narr/advconfig.rst 20 ●●●●● patch | view | raw | blame | history
docs/narr/configuration.rst 11 ●●●●● patch | view | raw | blame | history
docs/narr/firstapp.rst 24 ●●●● patch | view | raw | blame | history
docs/narr/hooks.rst 5 ●●●●● patch | view | raw | blame | history
docs/narr/install.rst 11 ●●●●● patch | view | raw | blame | history
docs/narr/paste.rst 2 ●●● patch | view | raw | blame | history
docs/narr/project.rst 27 ●●●● patch | view | raw | blame | history
docs/narr/startup.rst 4 ●●●● patch | view | raw | blame | history
docs/narr/urldispatch.rst 118 ●●●●● patch | view | raw | blame | history
docs/tutorials/gae/index.rst 231 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/authorization/development.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/authorization/production.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/authorization/setup.py 1 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/basiclayout/development.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/basiclayout/production.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/basiclayout/setup.py 1 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/models/development.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/models/production.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/models/setup.py 1 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/tests/development.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/tests/production.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/tests/setup.py 1 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/views/development.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/views/production.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/views/setup.py 1 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/authorization/development.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/authorization/production.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/authorization/setup.py 5 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/basiclayout/development.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/basiclayout/production.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/basiclayout/setup.py 5 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/models/development.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/models/production.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/models/setup.py 5 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/tests/development.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/tests/production.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/tests/setup.py 5 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/views/development.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/views/production.ini 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/views/setup.py 5 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/tests.rst 2 ●●● patch | view | raw | blame | history
docs/whatsnew-1.3.rst 81 ●●●●● patch | view | raw | blame | history
pyramid/compat.py 26 ●●●● patch | view | raw | blame | history
pyramid/config/testing.py 10 ●●●● patch | view | raw | blame | history
pyramid/config/util.py 8 ●●●● patch | view | raw | blame | history
pyramid/config/views.py 13 ●●●●● patch | view | raw | blame | history
pyramid/events.py 9 ●●●●● patch | view | raw | blame | history
pyramid/request.py 3 ●●●● patch | view | raw | blame | history
pyramid/scaffolds/alchemy/development.ini_tmpl 2 ●●● patch | view | raw | blame | history
pyramid/scaffolds/alchemy/production.ini_tmpl 2 ●●● patch | view | raw | blame | history
pyramid/scaffolds/alchemy/setup.py_tmpl 2 ●●● patch | view | raw | blame | history
pyramid/scaffolds/starter/development.ini_tmpl 2 ●●● patch | view | raw | blame | history
pyramid/scaffolds/starter/production.ini_tmpl 2 ●●● patch | view | raw | blame | history
pyramid/scaffolds/starter/setup.py_tmpl 6 ●●●● patch | view | raw | blame | history
pyramid/scaffolds/zodb/development.ini_tmpl 2 ●●● patch | view | raw | blame | history
pyramid/scaffolds/zodb/production.ini_tmpl 2 ●●● patch | view | raw | blame | history
pyramid/scaffolds/zodb/setup.py_tmpl 1 ●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_routes.py 31 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_util.py 16 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_views.py 102 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_events.py 10 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_request.py 18 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_traversal.py 60 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_urldispatch.py 99 ●●●● patch | view | raw | blame | history
pyramid/tests/test_util.py 108 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_view.py 40 ●●●● patch | view | raw | blame | history
pyramid/traversal.py 90 ●●●●● patch | view | raw | blame | history
pyramid/url.py 2 ●●● patch | view | raw | blame | history
pyramid/urldispatch.py 128 ●●●● patch | view | raw | blame | history
pyramid/util.py 83 ●●●●● patch | view | raw | blame | history
pyramid/view.py 55 ●●●● patch | view | raw | blame | history
setup.py 2 ●●● patch | view | raw | blame | history
CHANGES.txt
@@ -1,5 +1,112 @@
Next release
============
1.3a5 (2012-01-09)
==================
Bug Fixes
---------
- The ``pyramid.view.view_defaults`` decorator did not work properly when
  more than one view relied on the defaults being different for configuration
  conflict resolution.  See https://github.com/Pylons/pyramid/issues/394.
Backwards Incompatibilities
---------------------------
- The ``path_info`` route and view predicates now match against
  ``request.upath_info`` (Unicode) rather than ``request.path_info``
  (indeterminate value based on Python 3 vs. Python 2).  This has to be done
  to normalize matching on Python 2 and Python 3.
1.3a4 (2012-01-05)
==================
Features
--------
- New API: ``pyramid.request.Request.set_property``. Add lazy property
  descriptors to a request without changing the request factory. New
  properties may be reified, effectively caching the value for the lifetime
  of the instance. Common use-cases for this would be to  get a database
  connection for the request or identify the current user.
- Use the ``waitress`` WSGI server instead of ``wsgiref`` in scaffolding.
Bug Fixes
---------
- The documentation of ``pyramid.events.subscriber`` indicated that using it
  as a decorator with no arguments like this::
    @subscriber()
    def somefunc(event):
        pass
  Would register ``somefunc`` to receive all events sent via the registry,
  but this was untrue.  Instead, it would receive no events at all.  This has
  now been fixed and the code matches the documentation.  See also
  https://github.com/Pylons/pyramid/issues/386
- Literal portions of route patterns were not URL-quoted when ``route_url``
  or ``route_path`` was used to generate a URL or path.
- The result of ``route_path`` or ``route_url`` might have been ``unicode``
  or ``str`` depending on the input.  It is now guaranteed to always be
  ``str``.
- URL matching when the pattern contained non-ASCII characters in literal
  parts was indeterminate.  Now the pattern supplied to ``add_route`` is
  assumed to be either: a ``unicode`` value, or a ``str`` value that contains
  only ASCII characters.  If you now want to match the path info from a URL
  that contains high order characters, you can pass the Unicode
  representation of the decoded path portion in the pattern.
- When using a ``traverse=`` route predicate, traversal would fail with a
  URLDecodeError if there were any high-order characters in the traversal
  pattern or in the matched dynamic segments.
- Using a dynamic segment named ``traverse`` in a route pattern like this::
    config.add_route('trav_route', 'traversal/{traverse:.*}')
  Would cause a ``UnicodeDecodeError`` when the route was matched and the
  matched portion of the URL contained any high-order characters.  See
  https://github.com/Pylons/pyramid/issues/385 .
- When using a ``*traverse`` stararg in a route pattern, a URL that matched
  that possessed a ``@@`` in its name (signifying a view name) would be
  inappropriately quoted by the traversal machinery during traversal,
  resulting in the view not being found properly. See
  https://github.com/Pylons/pyramid/issues/382 and
  https://github.com/Pylons/pyramid/issues/375 .
Backwards Incompatibilities
---------------------------
- String values passed to ``route_url`` or ``route_path`` that are meant to
  replace "remainder" matches will now be URL-quoted except for embedded
  slashes. For example::
     config.add_route('remain', '/foo*remainder')
     request.route_path('remain', remainder='abc / def')
     # -> '/foo/abc%20/%20def'
  Previously string values passed as remainder replacements were tacked on
  untouched, without any URL-quoting.  But this doesn't really work logically
  if the value passed is Unicode (raw unicode cannot be placed in a URL or in
  a path) and it is inconsistent with the rest of the URL generation
  machinery if the value is a string (it won't be quoted unless by the
  caller).
  Some folks will have been relying on the older behavior to tack on query
  string elements and anchor portions of the URL; sorry, you'll need to
  change your code to use the ``_query`` and/or ``_anchor`` arguments to
  ``route_path`` or ``route_url`` to do this now.
- If you pass a bytestring that contains non-ASCII characters to
  ``add_route`` as a pattern, it will now fail at startup time.  Use Unicode
  instead.
1.3a3 (2011-12-21)
==================
Features
--------
@@ -7,6 +114,11 @@
- Added a ``prequest`` script (along the lines of ``paster request``).  It is
  documented in the "Command-Line Pyramid" chapter in the section entitled
  "Invoking a Request".
- Add undocumented ``__discriminator__`` API to derived view callables.
  e.g. ``adapters.lookup(...).__discriminator__(context, request)``.  It will
  be used by superdynamic systems that require the discriminator to be used
  for introspection after manual view lookup.
Bug Fixes
---------
@@ -20,6 +132,12 @@
- Added a section named "Making Your Script into a Console Script" in the
  "Command-Line Pyramid" chapter.
- Removed the "Running Pyramid on Google App Engine" tutorial from the main
  docs.  It survives on in the Cookbook
  (http://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest/gae.html).
  Rationale: it provides the correct info for the Python 2.5 version of GAE
  only, and this version of Pyramid does not support Python 2.5.
1.3a2 (2011-12-14)
==================
TODO.txt
@@ -4,9 +4,25 @@
Must-Have
---------
- Introspection:
- Fix deployment recipes in cookbook (discourage proxying without changing
  server).
  * Review narrative docs.
- Use waitress instead of wsgiref.
- pyramid.config.util.ActionInfo.__str__ potentially returns Unicode under
  Py2, fix.
- Tests for view names/route patterns that contain Unicode.
Nice-to-Have
------------
- Modify the urldispatch chapter examples to assume a scan rather than
  ``add_view``.
- Decorator for append_slash_notfound_view_factory?
- Introspection:
  * ``default root factory`` category?
@@ -24,8 +40,6 @@
Nice-to-Have
------------
- Implement analogue of "paster request"?
- CherryPy server testing / exploded from CherryPy itself.
docs/api/renderers.rst
@@ -20,5 +20,5 @@
   as a view renderer argument, Pyramid avoids converting the view callable
   result into a Response object.  This is useful if you want to reuse the
   view configuration and lookup machinery outside the context of its use by
   the Pyramid router (e.g. the package named ``pyramid_rpc`` does this).
   the Pyramid router.
docs/api/request.rst
@@ -8,6 +8,10 @@
.. autoclass:: Request
   :members:
   :inherited-members:
   :exclude-members: add_response_callback, add_finished_callback,
                     route_url, route_path, current_route_url,
                     current_route_path, static_url, static_path,
                     model_url, resource_url, set_property
   .. attribute:: context
@@ -204,6 +208,57 @@
       body associated with this request, this property will raise an
       exception.  See also :ref:`request_json_body`.
   .. method:: set_property(func, name=None, reify=False)
       .. versionadded:: 1.3
       Add a callable or a property descriptor to the request instance.
       Properties, unlike attributes, are lazily evaluated by executing
       an underlying callable when accessed. They can be useful for
       adding features to an object without any cost if those features
       go unused.
       A property may also be reified via the
       :class:`pyramid.decorator.reify` decorator by setting
       ``reify=True``, allowing the result of the evaluation to be
       cached. Thus the value of the property is only computed once for
       the lifetime of the object.
       ``func`` can either be a callable that accepts the request as
       its single positional parameter, or it can be a property
       descriptor.
       If the ``func`` is a property descriptor a ``ValueError`` will
       be raised if ``name`` is ``None`` or ``reify`` is ``True``.
       If ``name`` is None, the name of the property will be computed
       from the name of the ``func``.
       .. code-block:: python
          :linenos:
          def _connect(request):
              conn = request.registry.dbsession()
              def cleanup(_):
                  conn.close()
              request.add_finished_callback(cleanup)
              return conn
          @subscriber(NewRequest)
          def new_request(event):
              request = event.request
              request.set_property(_connect, 'db', reify=True)
       The subscriber doesn't actually connect to the database, it just
       provides the API which, when accessed via ``request.db``, will
       create the connection. Thanks to reify, only one connection is
       made per-request even if ``request.db`` is accessed many times.
       This pattern provides a way to augment the ``request`` object
       without having to subclass it, which can be useful for extension
       authors.
.. note::
   For information about the API of a :term:`multidict` structure (such as
docs/conf.py
@@ -80,7 +80,7 @@
# other places throughout the built documents.
#
# The short X.Y version.
version = '1.3a2'
version = '1.3a5'
# The full version, including alpha/beta/rc tags.
release = version
docs/designdefense.rst
@@ -1628,8 +1628,8 @@
.. code-block:: python
   :linenos:
   from pyramid.response import Response      # explicit response objects, no TL
   from paste.httpserver import serve         # explicitly WSGI
   from pyramid.response import Response         # explicit response, no TL
   from wsgiref.simple_server import make_server # explicitly WSGI
   def hello_world(request):  # accepts a request; no request thread local reqd
       # explicit response object means no response threadlocal
@@ -1640,7 +1640,8 @@
       config = Configurator()       # no global application object.
       config.add_view(hello_world)  # explicit non-decorator registration
       app = config.make_wsgi_app()  # explicitly WSGI
       serve(app, host='0.0.0.0')    # explicitly WSGI
       server = make_server('0.0.0.0', 8080, app)
       server.serve_forever()        # explicitly WSGI
Pyramid Doesn't Offer Pluggable Apps
------------------------------------
@@ -1736,7 +1737,7 @@
.. code-block:: python
   :linenos:
   from paste.httpserver import serve
   from wsgiref.simple_server import make_server
   from pyramid.config import Configurator
   from pyramid.response import Response
@@ -1747,7 +1748,8 @@
       config = Configurator()
       config.add_view(hello_world)
       app = config.make_wsgi_app()
       serve(app)
       server = make_server('0.0.0.0', 8080, app)
       server.serve_forever()
Pyramid has ~ 650 pages of documentation (printed), covering topics from the
very basic to the most advanced.  *Nothing* is left undocumented, quite
docs/index.rst
@@ -109,7 +109,6 @@
   tutorials/wiki2/index.rst
   tutorials/wiki/index.rst
   tutorials/bfg/index.rst
   tutorials/gae/index.rst
   tutorials/modwsgi/index.rst
Reference Material
docs/latexindex.rst
@@ -73,7 +73,6 @@
   tutorials/wiki/index.rst
   tutorials/wiki2/index.rst
   tutorials/bfg/index.rst
   tutorials/gae/index.rst
   tutorials/modwsgi/index.rst
.. _api_reference:
docs/narr/MyProject/development.ini
@@ -10,7 +10,7 @@
pyramid.includes = pyramid_debugtoolbar
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/narr/MyProject/production.ini
@@ -9,7 +9,7 @@
pyramid.default_locale_name = en
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/narr/MyProject/setup.py
@@ -6,7 +6,11 @@
README = open(os.path.join(here, 'README.txt')).read()
CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
requires = ['pyramid', 'pyramid_debugtoolbar']
requires = [
    'pyramid',
    'pyramid_debugtoolbar',
    'waitress',
    ]
setup(name='MyProject',
      version='0.0',
docs/narr/advconfig.rst
@@ -27,7 +27,7 @@
.. code-block:: python
   :linenos:
   from paste.httpserver import serve
   from wsgiref.simple_server import make_server
   from pyramid.config import Configurator
   from pyramid.response import Response
@@ -38,7 +38,8 @@
       config = Configurator()
       config.add_view(hello_world)
       app = config.make_wsgi_app()
       serve(app, host='0.0.0.0')
       server = make_server('0.0.0.0', 8080, app)
       server.serve_forever()
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
@@ -47,7 +48,7 @@
.. code-block:: python
   :linenos:
   from paste.httpserver import serve
   from wsgiref.simple_server import make_server
   from pyramid.config import Configurator
   from pyramid.response import Response
@@ -66,7 +67,8 @@
       config.add_view(goodbye_world, name='hello')
       app = config.make_wsgi_app()
       serve(app, host='0.0.0.0')
       server = make_server('0.0.0.0', 8080, app)
       server.serve_forever()
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
@@ -170,7 +172,7 @@
.. code-block:: python
   :linenos:
   from paste.httpserver import serve
   from wsgiref.simple_server import make_server
   from pyramid.config import Configurator
   from pyramid.response import Response
@@ -189,7 +191,8 @@
       config.add_view(goodbye_world, name='hello')
       app = config.make_wsgi_app()
       serve(app, host='0.0.0.0')
       server = make_server('0.0.0.0', 8080, app)
       server.serve_forever()
We can prevent the two ``add_view`` calls from conflicting by issuing a call
to :meth:`~pyramid.config.Configurator.commit` between them:
@@ -197,7 +200,7 @@
.. code-block:: python
   :linenos:
   from paste.httpserver import serve
   from wsgiref.simple_server import make_server
   from pyramid.config import Configurator
   from pyramid.response import Response
@@ -218,7 +221,8 @@
       config.add_view(goodbye_world, name='hello')
       app = config.make_wsgi_app()
       serve(app, host='0.0.0.0')
       server = make_server('0.0.0.0', 8080, app)
       server.serve_forever()
In the above example we've issued a call to
:meth:`~pyramid.config.Configurator.commit` between the two ``add_view``
docs/narr/configuration.rst
@@ -36,7 +36,7 @@
.. code-block:: python
   :linenos:
   from paste.httpserver import serve
   from wsgiref.simple_server import make_server
   from pyramid.config import Configurator
   from pyramid.response import Response
@@ -47,7 +47,8 @@
       config = Configurator()
       config.add_view(hello_world)
       app = config.make_wsgi_app()
       serve(app, host='0.0.0.0')
       server = make_server('0.0.0.0', 8080, app)
       server.serve_forever()
We won't talk much about what this application does yet.  Just note that the
"configuration' statements take place underneath the ``if __name__ ==
@@ -105,7 +106,8 @@
   .. code-block:: python
      :linenos:
      from paste.httpserver import serve
      from wsgiref.simple_server import make_server
      from pyramid.config import Configurator
      from pyramid.response import Response
      from pyramid.view import view_config
     
@@ -118,7 +120,8 @@
          config = Configurator()
          config.scan()
          app = config.make_wsgi_app()
          serve(app, host='0.0.0.0')
          server = make_server('0.0.0.0', 8080, app)
          server.serve_forever()
The scanning machinery imports each module and subpackage in a package or
module recursively, looking for special attributes attached to objects
docs/narr/firstapp.rst
@@ -54,9 +54,8 @@
Like many other Python web frameworks, :app:`Pyramid` uses the :term:`WSGI`
protocol to connect an application and a web server together.  The
:mod:`paste.httpserver` server is used in this example as a WSGI server for
convenience, as the ``paste`` package is a dependency of :app:`Pyramid`
itself.
:mod:`wsgiref` server is used in this example as a WSGI server for
convenience, as it is shipped within the Python standard library.
The script also imports the :class:`pyramid.response.Response` class for
later use.  An instance of this class will be used to create a web response.
@@ -205,14 +204,17 @@
   :lines: 13
Finally, we actually serve the application to requestors by starting up a
WSGI server.  We happen to use the :func:`paste.httpserver.serve` WSGI server
runner, passing it the ``app`` object (a :term:`router`) as the application
we wish to serve.  We also pass in an argument ``host='0.0.0.0'``, meaning
"listen on all TCP interfaces."  By default, the HTTP server listens
only on the ``127.0.0.1`` interface, which is problematic if you're running
the server on a remote system and you wish to access it with a web browser
from a local system.  We don't specify a TCP port number to listen on; this
means we want to use the default TCP port, which is 8080.
WSGI server.  We happen to use the :mod:`wsgiref` ``make_server`` server
maker for this purpose.  We pass in as the first argument ``'0.0.0.0'``,
which means "listen on all TCP interfaces."  By default, the HTTP server
listens only on the ``127.0.0.1`` interface, which is problematic if you're
running the server on a remote system and you wish to access it with a web
browser from a local system.  We also specify a TCP port number to listen on,
which is 8080, passing it as the second argument.  The final argument ios ,
passing it the ``app`` object (a :term:`router`), which is the the
application we wish to serve.  Finally, we call the server's
``serve_forever`` method, which starts the main loop in which it will wait
for requests from the outside world.
When this line is invoked, it causes the server to start listening on TCP
port 8080.  The server will serve requests forever, or at least until we stop
docs/narr/hooks.rst
@@ -812,7 +812,7 @@
.. code-block:: python
   :linenos:
   from paste.httpserver import serve
   from wsgiref.simple_server import make_server
   from pyramid.config import Configurator
   from mypackage.interfaces import IMyUtility
@@ -831,7 +831,8 @@
       config.registry.registerUtility(UtilityImplementation())
       config.scan()
       app = config.make_wsgi_app()
       serve(app, host='0.0.0.0')
       server = make_server('0.0.0.0', 8080, app)
       server.serve_forever()
For full details, please read the `Venusian documentation
<http://docs.repoze.org/venusian>`_.
docs/narr/install.rst
@@ -20,7 +20,7 @@
:app:`Pyramid` is known to run on all popular UNIX-like systems such as
Linux, MacOS X, and FreeBSD as well as on Windows platforms.  It is also
known to run on Google's App Engine, and :term:`PyPy` (1.6+).
known to run on :term:`PyPy` (1.6+).
:app:`Pyramid` installation does not require the compilation of any
C code, so you need only a Python interpreter that meets the
@@ -314,15 +314,6 @@
   .. code-block:: text
      c:\env> Scripts\easy_install pyramid
.. index::
   single: installing on Google App Engine
Installing :app:`Pyramid` on Google App Engine
-------------------------------------------------
:ref:`appengine_tutorial` documents the steps required to install a
:app:`Pyramid` application on Google App Engine.
What Gets Installed
-------------------
docs/narr/paste.rst
@@ -17,7 +17,7 @@
:ref:`firstapp_chapter`.  However, all Pyramid scaffolds render PasteDeploy
configuration files, to provide new developers with a standardized way of
setting deployment values, and to provide new users with a standardized way
of starting, stopping, and debugging an application.
of starting, stopping, and debugging an application.
This chapter is not a replacement for documentation about PasteDeploy; it
only contextualizes the use of PasteDeploy within Pyramid.  For detailed
docs/narr/project.rst
@@ -283,7 +283,7 @@
   $ ../bin/pserve development.ini --reload
   Starting subprocess with file monitor
   Starting server in PID 16601.
   serving on 0.0.0.0:6543 view at http://127.0.0.1:6543
   Starting HTTP server on http://0.0.0.0:6543
For more detailed information about the startup process, see
:ref:`startup_chapter`.  For more information about environment variables and
@@ -888,26 +888,11 @@
The code generated by :app:`Pyramid` scaffolding assumes that you will be
using the ``pserve`` command to start your application while you do
development.  The default rendering of Pyramid scaffolding uses the *wsgiref*
WSGI server, which is a server that is ill-suited for production usage: its
main feature is that it works on all platforms and all systems, making it a
good choice as a default server from the perspective of Pyramid's developers.
To use a server more suitable for production, you have a number of choices.
Replace the ``use = egg:pyramid#wsgref`` line in your ``production.ini`` with
one of the following.
``use = egg:Paste#http``
  ``paste.httpserver`` is Windows, UNIX, and Python 2 compatible.  You'll
  need to ``easy_install Paste`` into your Pyramid virtualenv for this server
  to work.
``use = egg:pyramid#cherrypy``
  The ``CherryPy`` WSGI server is Windows, UNIX, Python 2, and Python 3
  compatible.  You'll need to ``easy_install CherryPy`` into your Pyramid
  virtualenv for this server to work.
development.  The default rendering of Pyramid scaffolding uses the
*waitress* WSGI server, which is a server that is suited for production
usage.  It's not very fast, or very featureful: its main feature is that it
works on all platforms and all systems, making it a good choice as a default
server from the perspective of Pyramid's developers.
``pserve`` is by no means the only way to start up and serve a :app:`Pyramid`
application.  As we saw in :ref:`firstapp_chapter`, ``pserve`` needn't be
docs/narr/startup.rst
@@ -133,8 +133,8 @@
   far as ``pserve`` is concerned, it is "just another WSGI application".
#. ``pserve`` starts the WSGI *server* defined within the ``[server:main]``
   section.  In our case, this is the ``egg:pyramid#wsgiref`` server (``use =
   egg:pyramid#wsgiref``), and it will listen on all interfaces (``host =
   section.  In our case, this is the Waitress server (``use =
   egg:waitress#main``), and it will listen on all interfaces (``host =
   0.0.0.0``), on port number 6543 (``port = 6543``).  The server code itself
   is what prints ``serving on 0.0.0.0:6543 view at http://127.0.0.1:6543``.
   The server serves the application, and the application is running, waiting
docs/narr/urldispatch.rst
@@ -235,13 +235,58 @@
.. code-block:: text
   foo/La%20Pe%C3%B1a
   http://example.com/foo/La%20Pe%C3%B1a
The matchdict will look like so (the value is URL-decoded / UTF-8 decoded):
.. code-block:: text
   {'bar':u'La Pe\xf1a'}
Literal strings in the path segment should represent the *decoded* value of
the ``PATH_INFO`` provided to Pyramid.  You don't want to use a URL-encoded
value or a bytestring representing the literal's UTF-8 in the pattern.  For
example, rather than this:
.. code-block:: text
   /Foo%20Bar/{baz}
You'll want to use something like this:
.. code-block:: text
   /Foo Bar/{baz}
For patterns that contain "high-order" characters in its literals, you'll
want to use a Unicode value as the pattern as opposed to any URL-encoded or
UTF-8-encoded value.  For example, you might be tempted to use a bytestring
pattern like this:
.. code-block:: text
   /La Pe\xc3\xb1a/{x}
But this will either cause an error at startup time or it won't match
properly.  You'll want to use a Unicode value as the pattern instead rather
than raw bytestring escapes.  You can use a high-order Unicode value as the
pattern by using `Python source file encoding
<http://www.python.org/dev/peps/pep-0263/>`_ plus the "real" character in the
Unicode pattern in the source, like so:
.. code-block:: text
   /La Peña/{x}
Or you can ignore source file encoding and use equivalent Unicode escape
characters in the pattern.
.. code-block:: text
   /La Pe\xf1a/{x}
Dynamic segment names cannot contain high-order characters, so this applies
only to literals in the pattern.
If the pattern has a ``*`` in it, the name which follows it is considered a
"remainder match".  A remainder match *must* come at the end of the pattern.
@@ -612,7 +657,6 @@
based on route patterns.  For example, if you've configured a route with the
``name`` "foo" and the ``pattern`` "{a}/{b}/{c}", you might do this.
.. ignore-next-block
.. code-block:: python
   :linenos:
@@ -620,8 +664,74 @@
This would return something like the string ``http://example.com/1/2/3`` (at
least if the current protocol and hostname implied ``http://example.com``).
See the :meth:`~pyramid.request.Request.route_url` API documentation for more
information.
To generate only the *path* portion of a URL from a route, use the
:meth:`pyramid.request.Request.route_path` API instead of
:meth:`~pyramid.request.Request.route_url`.
.. code-block:: python
   url = request.route_path('foo', a='1', b='2', c='3')
This will return the string ``/1/2/3`` rather than a full URL.
Replacement values passed to ``route_url`` or ``route_path`` must be Unicode
or bytestrings encoded in UTF-8.  One exception to this rule exists: if
you're trying to replace a "remainder" match value (a ``*stararg``
replacement value), the value may be a tuple containing Unicode strings or
UTF-8 strings.
Note that URLs and paths generated by ``route_path`` and ``route_url`` are
always URL-quoted string types (they contain no non-ASCII characters).
Therefore, if you've added a route like so:
.. code-block:: python
   config.add_route('la', u'/La Peña/{city}')
And you later generate a URL using ``route_path`` or ``route_url`` like so:
.. code-block:: python
   url = request.route_path('la', city=u'Québec')
You will wind up with the path encoded to UTF-8 and URL quoted like so:
.. code-block:: text
   /La%20Pe%C3%B1a/Qu%C3%A9bec
If you have a ``*stararg`` remainder dynamic part of your route pattern:
.. code-block:: python
   config.add_route('abc', 'a/b/c/*foo')
And you later generate a URL using ``route_path`` or ``route_url`` using a
*string* as the replacement value:
.. code-block:: python
   url = request.route_path('abc', foo=u'Québec/biz')
The value you pass will be URL-quoted except for embedded slashes in the
result:
.. code-block:: text
   /a/b/c/Qu%C3%A9bec/biz
You can get a similar result by passing a tuple composed of path elements:
.. code-block:: python
   url = request.route_path('abc', foo=(u'Québec', u'biz'))
Each value in the tuple will be url-quoted and joined by slashes in this case:
.. code-block:: text
   /a/b/c/Qu%C3%A9bec/biz
.. index::
   single: static routes
docs/tutorials/gae/index.rst
File was deleted
docs/tutorials/wiki/src/authorization/development.ini
@@ -13,7 +13,7 @@
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki/src/authorization/production.ini
@@ -12,7 +12,7 @@
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki/src/authorization/setup.py
@@ -12,6 +12,7 @@
    'pyramid_tm',
    'pyramid_debugtoolbar',
    'ZODB3',
    'waitress',
    'docutils',
    ]
docs/tutorials/wiki/src/basiclayout/development.ini
@@ -13,7 +13,7 @@
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki/src/basiclayout/production.ini
@@ -12,7 +12,7 @@
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki/src/basiclayout/setup.py
@@ -12,6 +12,7 @@
    'pyramid_tm',
    'pyramid_debugtoolbar',
    'ZODB3',
    'waitress',
    ]
setup(name='tutorial',
docs/tutorials/wiki/src/models/development.ini
@@ -13,7 +13,7 @@
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki/src/models/production.ini
@@ -12,7 +12,7 @@
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki/src/models/setup.py
@@ -12,6 +12,7 @@
    'pyramid_tm',
    'pyramid_debugtoolbar',
    'ZODB3',
    'waitress',
    ]
setup(name='tutorial',
docs/tutorials/wiki/src/tests/development.ini
@@ -13,7 +13,7 @@
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki/src/tests/production.ini
@@ -12,7 +12,7 @@
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki/src/tests/setup.py
@@ -12,6 +12,7 @@
    'pyramid_tm',
    'pyramid_debugtoolbar',
    'ZODB3',
    'waitress',
    'docutils',
    'WebTest', # add this
    ]
docs/tutorials/wiki/src/views/development.ini
@@ -13,7 +13,7 @@
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki/src/views/production.ini
@@ -12,7 +12,7 @@
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki/src/views/setup.py
@@ -12,6 +12,7 @@
    'pyramid_tm',
    'pyramid_debugtoolbar',
    'ZODB3',
    'waitress',
    'docutils',
    ]
docs/tutorials/wiki2/src/authorization/development.ini
@@ -13,7 +13,7 @@
sqlalchemy.url = sqlite:///%(here)s/tutorial.db
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki2/src/authorization/production.ini
@@ -12,7 +12,7 @@
sqlalchemy.url = sqlite:///%(here)s/tutorial.db
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki2/src/authorization/setup.py
@@ -1,5 +1,4 @@
import os
import sys
from setuptools import setup, find_packages
@@ -15,10 +14,8 @@
    'pyramid_debugtoolbar',
    'zope.sqlalchemy',
    'docutils',
    'waitress',
    ]
if sys.version_info[:3] < (2,5,0):
    requires.append('pysqlite')
setup(name='tutorial',
      version='0.0',
docs/tutorials/wiki2/src/basiclayout/development.ini
@@ -13,7 +13,7 @@
sqlalchemy.url = sqlite:///%(here)s/tutorial.db
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki2/src/basiclayout/production.ini
@@ -12,7 +12,7 @@
sqlalchemy.url = sqlite:///%(here)s/tutorial.db
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki2/src/basiclayout/setup.py
@@ -1,5 +1,4 @@
import os
import sys
from setuptools import setup, find_packages
@@ -14,10 +13,8 @@
    'pyramid_tm',
    'pyramid_debugtoolbar',
    'zope.sqlalchemy',
    'waitress',
    ]
if sys.version_info[:3] < (2,5,0):
    requires.append('pysqlite')
setup(name='tutorial',
      version='0.0',
docs/tutorials/wiki2/src/models/development.ini
@@ -13,7 +13,7 @@
sqlalchemy.url = sqlite:///%(here)s/tutorial.db
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki2/src/models/production.ini
@@ -12,7 +12,7 @@
sqlalchemy.url = sqlite:///%(here)s/tutorial.db
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki2/src/models/setup.py
@@ -1,5 +1,4 @@
import os
import sys
from setuptools import setup, find_packages
@@ -14,10 +13,8 @@
    'pyramid_tm',
    'pyramid_debugtoolbar',
    'zope.sqlalchemy',
    'waitress',
    ]
if sys.version_info[:3] < (2,5,0):
    requires.append('pysqlite')
setup(name='tutorial',
      version='0.0',
docs/tutorials/wiki2/src/tests/development.ini
@@ -13,7 +13,7 @@
sqlalchemy.url = sqlite:///%(here)s/tutorial.db
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki2/src/tests/production.ini
@@ -12,7 +12,7 @@
sqlalchemy.url = sqlite:///%(here)s/tutorial.db
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki2/src/tests/setup.py
@@ -1,5 +1,4 @@
import os
import sys
from setuptools import setup, find_packages
@@ -14,12 +13,10 @@
    'pyramid_tm',
    'pyramid_debugtoolbar',
    'zope.sqlalchemy',
    'waitress',
    'docutils',
    'WebTest', # add this
    ]
if sys.version_info[:3] < (2,5,0):
    requires.append('pysqlite')
setup(name='tutorial',
      version='0.0',
docs/tutorials/wiki2/src/views/development.ini
@@ -13,7 +13,7 @@
sqlalchemy.url = sqlite:///%(here)s/tutorial.db
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki2/src/views/production.ini
@@ -12,7 +12,7 @@
sqlalchemy.url = sqlite:///%(here)s/tutorial.db
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
docs/tutorials/wiki2/src/views/setup.py
@@ -1,5 +1,4 @@
import os
import sys
from setuptools import setup, find_packages
@@ -14,11 +13,9 @@
    'pyramid_tm',
    'pyramid_debugtoolbar',
    'zope.sqlalchemy',
    'waitress',
    'docutils',
    ]
if sys.version_info[:3] < (2,5,0):
    requires.append('pysqlite')
setup(name='tutorial',
      version='0.0',
docs/tutorials/wiki2/tests.rst
@@ -55,7 +55,7 @@
.. literalinclude:: src/tests/setup.py
   :linenos:
   :language: python
   :lines: 10-19
   :lines: 9-20
After we've added a dependency on WebTest in ``setup.py``, we need to rerun
``setup.py develop`` to get WebTest installed into our virtualenv.  Assuming
docs/whatsnew-1.3.rst
@@ -52,7 +52,7 @@
- We've replaced the ``paster`` command with Pyramid-specific analogues.
- We've made the default WSGI server the ``wsgiref`` server.
- We've made the default WSGI server the ``waitress`` server.
Previously (in Pyramid 1.0, 1.1 and 1.2), you created a Pyramid application
using ``paster create``, like so::
@@ -85,37 +85,25 @@
``paster ptweens`` also exist under the respective console script names
``pshell``, ``pviews``, ``prequest`` and ``ptweens``.
We've replaced use of the Paste ``httpserver`` with the ``wsgiref`` server in
We've replaced use of the Paste ``httpserver`` with the ``waitress`` server in
the scaffolds, so once you create a project from a scaffold, its
``development.ini`` and ``production.ini`` will have the following line::
    use = egg:pyramid#wsgiref
    use = egg:waitress#main
Instead of this (which was the default in older versions)::
    use = egg:Paste#http
Using ``wsgiref`` as the default WSGI server is purely a default to make it
possible to use the same scaffolding under Python 2 and Python 3; people
running Pyramid under Python 2 can still manually install ``Paste`` and use
the Paste ``httpserver`` by replacing the former line with the latter.  This is
actually recommended if you rely on proxying from Apache or Nginx to a
``pserve`` -invoked application.  **The wsgiref server is not a production
quality server.** See :ref:`alternate_wsgi_server` for more information.
New releases in every older major Pyramid series (1.0.2, 1.1.3, 1.2.5) also
have the ``egg:pyramid#wsgiref`` entry point, so scaffold-writers can depend
on it being there even in older major Pyramid versions.
.. warning::
   Previously, paste.httpserver "helped" by converting header values that weren't
   strings to strings. The wsgiref server, on the other hand implements the spec
   more fully. This specifically may affect you if you are modifying headers on
   your response. The following error might be an indicator of this problem:
   **AssertionError: Header values must be strings, please check the type of
   the header being returned.** A common case would be returning unicode headers
   instead of string headers.
   Previously, paste.httpserver "helped" by converting header values that
   weren't strings to strings. The ``waitress`` server, on the other hand
   implements the spec more fully. This specifically may affect you if you
   are modifying headers on your response. The following error might be an
   indicator of this problem: **AssertionError: Header values must be
   strings, please check the type of the header being returned.** A common
   case would be returning unicode headers instead of string headers.
A new :mod:`pyramid.compat` module was added which provides Python 2/3
straddling support for Pyramid add-ons and development environments.
@@ -206,6 +194,16 @@
See :ref:`view_defaults` for more information.
Extending a Request without Subclassing
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is now possible to extend a :class:`pyramid.request.Request` object
with property descriptors without having to create a subclass via
:meth:`pyramid.request.Request.set_property`. New properties may be
reified, effectively caching the value for the lifetime of the instance.
Common use-cases for this would be to get a database connection for the
request or identify the current user.
Minor Feature Additions
-----------------------
@@ -254,12 +252,9 @@
  Python 3.
- The default WSGI server run as the result of ``pserve`` from newly rendered
  scaffolding is now the ``wsgiref`` WSGI server instead of the
  ``paste.httpserver`` server.  ``wsgiref``, unlike the server it replaced
  (``paste.httpserver``) is not a production quality server.  See
  :ref:`alternate_wsgi_server` for information about how to use another WSGI
  server in production. Rationale: the Paste and PasteScript packages do not
  run under Python 3.
  scaffolding is now the ``waitress`` WSGI server instead of the
  ``paste.httpserver`` server.  Rationale: the Paste and PasteScript packages
  do not run under Python 3.
- The ``pshell`` command (see "paster pshell") no longer accepts a
  ``--disable-ipython`` command-line argument.  Instead, it accepts a ``-p``
@@ -275,6 +270,30 @@
  Pyramid 1.0, so you won't be warned if you have older versions installed
  and upgrade Pyramid itself "in-place"; it may simply break instead
  (particularly if you use ZCML's ``includeOverrides`` directive).
- String values passed to ``route_url`` or ``route_path`` that are meant to
  replace "remainder" matches will now be URL-quoted except for embedded
  slashes. For example::
     config.add_route('remain', '/foo*remainder')
     request.route_path('remain', remainder='abc / def')
     # -> '/foo/abc%20/%20def'
  Previously string values passed as remainder replacements were tacked on
  untouched, without any URL-quoting.  But this doesn't really work logically
  if the value passed is Unicode (raw unicode cannot be placed in a URL or in
  a path) and it is inconsistent with the rest of the URL generation
  machinery if the value is a string (it won't be quoted unless by the
  caller).
  Some folks will have been relying on the older behavior to tack on query
  string elements and anchor portions of the URL; sorry, you'll need to
  change your code to use the ``_query`` and/or ``_anchor`` arguments to
  ``route_path`` or ``route_url`` to do this now.
- If you pass a bytestring that contains non-ASCII characters to
  ``add_route`` as a pattern, it will now fail at startup time.  Use Unicode
  instead.
Documentation Enhancements
--------------------------
@@ -302,6 +321,12 @@
- Added a section to the "Command-Line Pyramid" chapter named
  :ref:`making_a_console_script`.
- Removed the "Running Pyramid on Google App Engine" tutorial from the main
  docs.  It survives on in the Cookbook
  (http://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest/gae.html).
  Rationale: it provides the correct info for the Python 2.5 version of GAE
  only, and this version of Pyramid does not support Python 2.5.
Dependency Changes
------------------
pyramid/compat.py
@@ -33,7 +33,7 @@
def text_(s, encoding='latin-1', errors='strict'):
    """ If ``s`` is an instance of ``binary_type``, return
    ``s.encode(encoding, errors)``, otherwise return ``s``"""
    ``s.decode(encoding, errors)``, otherwise return ``s``"""
    if isinstance(s, binary_type):
        return s.decode(encoding, errors)
    return s # pragma: no cover
@@ -41,7 +41,7 @@
def bytes_(s, encoding='latin-1', errors='strict'):
    """ If ``s`` is an instance of ``text_type``, return
    ``s.encode(encoding, errors)``, otherwise return ``s``"""
    if isinstance(s, text_type):
    if isinstance(s, text_type): # pragma: no cover
        return s.encode(encoding, errors)
    return s
@@ -105,10 +105,10 @@
    from urllib import unquote as url_unquote
    from urllib import urlencode as url_encode
    from urllib2 import urlopen as url_open
    def url_unquote_text(v, encoding='utf-8', errors='replace'):
    def url_unquote_text(v, encoding='utf-8', errors='replace'): # pragma: no cover
        v = url_unquote(v)
        return v.decode(encoding, errors)
    def url_unquote_native(v, encoding='utf-8', errors='replace'):
    def url_unquote_native(v, encoding='utf-8', errors='replace'): # pragma: no cover
        return native_(url_unquote_text(v, encoding, errors))
        
@@ -213,3 +213,21 @@
import json
    
if PY3: # pragma: no cover
    # see PEP 3333 for why we encode WSGI PATH_INFO to latin-1 before
    # decoding it to utf-8
    def decode_path_info(path):
        return path.encode('latin-1').decode('utf-8')
else:
    def decode_path_info(path):
        return path.decode('utf-8')
if PY3: # pragma: no cover
    # see PEP 3333 for why we decode the path to latin-1
    from urllib.parse import unquote_to_bytes
    def unquote_bytes_to_wsgi(bytestring):
        return unquote_to_bytes(bytestring).decode('latin-1')
else:
    from urlparse import unquote as unquote_to_bytes
    def unquote_bytes_to_wsgi(bytestring):
        return unquote_to_bytes(bytestring)
pyramid/config/testing.py
@@ -8,7 +8,11 @@
    )
from pyramid.renderers import RendererHelper
from pyramid.traversal import traversal_path_info
from pyramid.traversal import (
    decode_path_info,
    split_path_info,
    )
from pyramid.config.util import action_method
@@ -66,9 +70,9 @@
                self.context = context
            def __call__(self, request):
                path = request.environ['PATH_INFO']
                path = decode_path_info(request.environ['PATH_INFO'])
                ob = resources[path]
                traversed = traversal_path_info(path)
                traversed = split_path_info(path)
                return {'context':ob, 'view_name':'','subpath':(),
                        'traversed':traversed, 'virtual_root':ob,
                        'virtual_root_path':(), 'root':ob}
pyramid/config/util.py
@@ -15,7 +15,7 @@
from pyramid.traversal import (
    find_interface,
    traversal_path_info,
    traversal_path,
    )
from hashlib import md5
@@ -145,7 +145,7 @@
        except re.error as why:
            raise ConfigurationError(why.args[0])
        def path_info_predicate(context, request):
            return path_info_val.match(request.path_info) is not None
            return path_info_val.match(request.upath_info) is not None
        text = "path_info = %s"
        path_info_predicate.__text__ = text % path_info
        weights.append(1 << 3)
@@ -268,8 +268,8 @@
            if 'traverse' in context:
                return True
            m = context['match']
            tvalue = tgenerate(m)
            m['traverse'] = traversal_path_info(tvalue)
            tvalue = tgenerate(m) # tvalue will be urlquoted string
            m['traverse'] = traversal_path(tvalue) # will be seq of unicode
            return True
        # This isn't actually a predicate, it's just a infodict
        # modifier that injects ``traverse`` into the matchdict.  As a
pyramid/config/views.py
@@ -492,6 +492,15 @@
        self.views = []
        self.accepts = []
    def __discriminator__(self, context, request):
        # used by introspection systems like so:
        # view = adapters.lookup(....)
        # view.__discriminator__(context, request) -> view's discriminator
        # so that superdynamic systems can feed the discriminator to
        # the introspection system to get info about it
        view = self.match(context, request)
        return view.__discriminator__(context, request)
    def add(self, view, order, accept=None, phash=None):
        if phash is not None:
            for i, (s, v, h) in enumerate(list(self.views)):
@@ -1034,6 +1043,10 @@
                                  decorator=decorator,
                                  http_cache=http_cache)
            derived_view = deriver(view)
            derived_view.__discriminator__ = lambda *arg: discriminator
            # __discriminator__ is used by superdynamic systems
            # that require it for introspection after manual view lookup;
            # see also MultiView.__discriminator__
            view_intr['derived_callable'] = derived_view
            registered = self.registry.adapters.registered
pyramid/events.py
@@ -1,6 +1,9 @@
import venusian
from zope.interface import implementer
from zope.interface import (
    implementer,
    Interface
    )
from pyramid.interfaces import (
    IContextFound,
@@ -26,7 +29,7 @@
       def mysubscriber(event):
           event.request.foo = 1
    More than one event type can be passed as a construtor argument.  The
    More than one event type can be passed as a constructor argument.  The
    decorated subscriber will be called for each event type.
    .. code-block:: python
@@ -66,7 +69,7 @@
    def register(self, scanner, name, wrapped):
        config = scanner.config
        for iface in self.ifaces:
        for iface in self.ifaces or (Interface,):
            config.add_subscriber(wrapped, iface)
    def __call__(self, wrapped):
pyramid/request.py
@@ -26,6 +26,7 @@
from pyramid.decorator import reify
from pyramid.response import Response
from pyramid.url import URLMethodsMixin
from pyramid.util import InstancePropertyMixin
class TemplateContext(object):
    pass
@@ -301,7 +302,7 @@
@implementer(IRequest)
class Request(BaseRequest, DeprecatedRequestMethodsMixin, URLMethodsMixin,
              CallbackMethodsMixin):
              CallbackMethodsMixin, InstancePropertyMixin):
    """
    A subclass of the :term:`WebOb` Request class.  An instance of
    this class is created by the :term:`router` and is provided to a
pyramid/scaffolds/alchemy/development.ini_tmpl
@@ -13,7 +13,7 @@
sqlalchemy.url = sqlite:///%(here)s/{{project}}.db
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
pyramid/scaffolds/alchemy/production.ini_tmpl
@@ -12,7 +12,7 @@
sqlalchemy.url = sqlite:///%(here)s/{{project}}.db
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
pyramid/scaffolds/alchemy/setup.py_tmpl
@@ -1,5 +1,4 @@
import os
import sys
from setuptools import setup, find_packages
@@ -14,6 +13,7 @@
    'pyramid_tm',
    'pyramid_debugtoolbar',
    'zope.sqlalchemy',
    'waitress',
    ]
setup(name='{{project}}',
pyramid/scaffolds/starter/development.ini_tmpl
@@ -10,7 +10,7 @@
pyramid.includes = pyramid_debugtoolbar
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
pyramid/scaffolds/starter/production.ini_tmpl
@@ -9,7 +9,7 @@
pyramid.default_locale_name = en
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
pyramid/scaffolds/starter/setup.py_tmpl
@@ -6,7 +6,11 @@
README = open(os.path.join(here, 'README.txt')).read()
CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
requires = ['pyramid', 'pyramid_debugtoolbar']
requires = [
    'pyramid',
    'pyramid_debugtoolbar',
    'waitress',
    ]
setup(name='{{project}}',
      version='0.0',
pyramid/scaffolds/zodb/development.ini_tmpl
@@ -14,7 +14,7 @@
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
pyramid/scaffolds/zodb/production.ini_tmpl
@@ -13,7 +13,7 @@
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:pyramid#wsgiref
use = egg:waitress#main
host = 0.0.0.0
port = 6543
pyramid/scaffolds/zodb/setup.py_tmpl
@@ -12,6 +12,7 @@
    'pyramid_tm',
    'pyramid_debugtoolbar',
    'ZODB3',
    'waitress',
    ]
setup(name='{{project}}',
pyramid/tests/test_config/test_routes.py
@@ -2,6 +2,7 @@
from pyramid.tests.test_config import dummyfactory
from pyramid.tests.test_config import DummyContext
from pyramid.compat import text_
class RoutesConfiguratorMixinTests(unittest.TestCase):
    def _makeOne(self, *arg, **kw):
@@ -107,10 +108,36 @@
        route = self._assertRoute(config, 'name', 'path', 1)
        predicate = route.predicates[0]
        request = self._makeRequest(config)
        request.path_info = '/foo'
        request.upath_info = '/foo'
        self.assertEqual(predicate(None, request), True)
        request = self._makeRequest(config)
        request.path_info = '/'
        request.upath_info = '/'
        self.assertEqual(predicate(None, request), False)
    def test_add_route_with_path_info_highorder(self):
        config = self._makeOne(autocommit=True)
        config.add_route('name', 'path',
                         path_info=text_(b'/La Pe\xc3\xb1a', 'utf-8'))
        route = self._assertRoute(config, 'name', 'path', 1)
        predicate = route.predicates[0]
        request = self._makeRequest(config)
        request.upath_info = text_(b'/La Pe\xc3\xb1a', 'utf-8')
        self.assertEqual(predicate(None, request), True)
        request = self._makeRequest(config)
        request.upath_info = text_('/')
        self.assertEqual(predicate(None, request), False)
    def test_add_route_with_path_info_regex(self):
        config = self._makeOne(autocommit=True)
        config.add_route('name', 'path',
                         path_info=text_(br'/La Pe\w*', 'utf-8'))
        route = self._assertRoute(config, 'name', 'path', 1)
        predicate = route.predicates[0]
        request = self._makeRequest(config)
        request.upath_info = text_(b'/La Pe\xc3\xb1a', 'utf-8')
        self.assertEqual(predicate(None, request), True)
        request = self._makeRequest(config)
        request.upath_info = text_('/')
        self.assertEqual(predicate(None, request), False)
    def test_add_route_with_request_param(self):
pyramid/tests/test_config/test_util.py
@@ -1,4 +1,5 @@
import unittest
from pyramid.compat import text_
class Test__make_predicates(unittest.TestCase):
    def _callFUT(self, **kw):
@@ -227,6 +228,21 @@
        self.assertEqual(info, {'match':
                                {'a':'a', 'b':'b', 'traverse':('1', 'a', 'b')}})
    def test_traverse_matches_with_highorder_chars(self):
        order, predicates, phash = self._callFUT(
            traverse=text_(b'/La Pe\xc3\xb1a/{x}', 'utf-8'))
        self.assertEqual(len(predicates), 1)
        pred = predicates[0]
        info = {'match':{'x':text_(b'Qu\xc3\xa9bec', 'utf-8')}}
        request = DummyRequest()
        result = pred(info, request)
        self.assertEqual(result, True)
        self.assertEqual(
            info['match']['traverse'],
            (text_(b'La Pe\xc3\xb1a', 'utf-8'),
             text_(b'Qu\xc3\xa9bec', 'utf-8'))
             )
    def test_custom_predicates_can_affect_traversal(self):
        def custom(info, request):
            m = info['match']
pyramid/tests/test_config/test_views.py
@@ -5,7 +5,10 @@
from pyramid.tests.test_config import dummy_view
from pyramid.compat import im_func
from pyramid.compat import (
    im_func,
    text_,
    )
from pyramid.exceptions import ConfigurationError
from pyramid.exceptions import ConfigurationExecutionError
from pyramid.exceptions import ConfigurationConflictError
@@ -115,6 +118,7 @@
        self.assertEqual(wrapper.__module__, view.__module__)
        self.assertEqual(wrapper.__name__, view.__name__)
        self.assertEqual(wrapper.__doc__, view.__doc__)
        self.assertEqual(wrapper.__discriminator__(None, None)[0], 'view')
    def test_add_view_view_callable_dottedname(self):
        from pyramid.renderers import null_renderer
@@ -140,6 +144,25 @@
        config = self._makeOne(autocommit=True)
        config.add_view(view=view, renderer=null_renderer)
        wrapper = self._getViewCallable(config)
        result = wrapper(None, None)
        self.assertEqual(result, 'OK')
    def test_add_view_with_name(self):
        from pyramid.renderers import null_renderer
        view = lambda *arg: 'OK'
        config = self._makeOne(autocommit=True)
        config.add_view(view=view, name='abc', renderer=null_renderer)
        wrapper = self._getViewCallable(config, name='abc')
        result = wrapper(None, None)
        self.assertEqual(result, 'OK')
    def test_add_view_with_name_unicode(self):
        from pyramid.renderers import null_renderer
        view = lambda *arg: 'OK'
        config = self._makeOne(autocommit=True)
        name = text_(b'La Pe\xc3\xb1a', 'utf-8')
        config.add_view(view=view, name=name, renderer=null_renderer)
        wrapper = self._getViewCallable(config, name=name)
        result = wrapper(None, None)
        self.assertEqual(result, 'OK')
@@ -875,6 +898,41 @@
        request.params = {'param':'1'}
        self.assertEqual(wrapper(ctx, request), 'view8')
    def test_add_view_multiview___discriminator__(self):
        from pyramid.renderers import null_renderer
        from zope.interface import Interface
        class IFoo(Interface):
            pass
        class IBar(Interface):
            pass
        @implementer(IFoo)
        class Foo(object):
            pass
        @implementer(IBar)
        class Bar(object):
            pass
        foo = Foo()
        bar = Bar()
        from pyramid.interfaces import IRequest
        from pyramid.interfaces import IView
        from pyramid.interfaces import IViewClassifier
        from pyramid.interfaces import IMultiView
        view = lambda *arg: 'OK'
        view.__phash__ = 'abc'
        config = self._makeOne(autocommit=True)
        config.registry.registerAdapter(
            view, (IViewClassifier, IRequest, Interface), IView, name='')
        config.add_view(view=view, renderer=null_renderer,
                        containment=IFoo)
        config.add_view(view=view, renderer=null_renderer,
                        containment=IBar)
        wrapper = self._getViewCallable(config)
        self.assertTrue(IMultiView.providedBy(wrapper))
        request = self._makeRequest(config)
        self.assertEqual(wrapper.__discriminator__(foo, request)[5], IFoo)
        self.assertEqual(wrapper.__discriminator__(bar, request)[5], IBar)
    def test_add_view_with_template_renderer(self):
        from pyramid.tests import test_config
        from pyramid.interfaces import ISettings
@@ -1225,7 +1283,7 @@
        config.add_view(view=view, path_info='/foo', renderer=null_renderer)
        wrapper = self._getViewCallable(config)
        request = self._makeRequest(config)
        request.path_info = '/foo'
        request.upath_info = text_(b'/foo')
        self.assertEqual(wrapper(None, request), 'OK')
    def test_add_view_with_path_info_nomatch(self):
@@ -1234,7 +1292,7 @@
        config.add_view(view=view, path_info='/foo')
        wrapper = self._getViewCallable(config)
        request = self._makeRequest(config)
        request.path_info = '/'
        request.upath_info = text_('/')
        self._assertNotFound(wrapper, None, request)
    def test_add_view_with_custom_predicates_match(self):
@@ -1397,11 +1455,47 @@
        directlyProvides(context, IDummy)
        request = self._makeRequest(config)
        self.assertEqual(wrapper(context, request), 'OK')
        context = DummyContext()
        request = self._makeRequest(config)
        self.assertRaises(PredicateMismatch, wrapper, context, request)
        
    def test_add_view_with_view_config_and_view_defaults_doesnt_conflict(self):
        from pyramid.renderers import null_renderer
        class view(object):
            __view_defaults__ = {
                'containment':'pyramid.tests.test_config.IDummy'
                }
        class view2(object):
            __view_defaults__ = {
                'containment':'pyramid.tests.test_config.IFactory'
                }
        config = self._makeOne(autocommit=False)
        config.add_view(
            view=view,
            renderer=null_renderer)
        config.add_view(
            view=view2,
            renderer=null_renderer)
        config.commit() # does not raise
    def test_add_view_with_view_config_and_view_defaults_conflicts(self):
        from pyramid.renderers import null_renderer
        class view(object):
            __view_defaults__ = {
                'containment':'pyramid.tests.test_config.IDummy'
                }
        class view2(object):
            __view_defaults__ = {
                'containment':'pyramid.tests.test_config.IDummy'
                }
        config = self._makeOne(autocommit=False)
        config.add_view(
            view=view,
            renderer=null_renderer)
        config.add_view(
            view=view2,
            renderer=null_renderer)
        self.assertRaises(ConfigurationConflictError, config.commit)
    def test_derive_view_function(self):
        from pyramid.renderers import null_renderer
pyramid/tests/test_events.py
@@ -155,6 +155,16 @@
        dec.register(scanner, None, foo)
        self.assertEqual(config.subscribed, [(foo, IFoo), (foo, IBar)])
    def test_register_none_means_all(self):
        from zope.interface import Interface
        dec = self._makeOne()
        def foo(): pass
        config = DummyConfigurator()
        scanner = Dummy()
        scanner.config = config
        dec.register(scanner, None, foo)
        self.assertEqual(config.subscribed, [(foo, Interface)])
    def test_register_objectevent(self):
        from zope.interface import Interface
        class IFoo(Interface): pass
pyramid/tests/test_request.py
@@ -267,6 +267,24 @@
        request = self._makeOne({'REQUEST_METHOD':'GET'})
        self.assertRaises(ValueError, getattr, request, 'json_body')
    def test_set_property(self):
        request = self._makeOne({})
        opts = [2, 1]
        def connect(obj):
            return opts.pop()
        request.set_property(connect, name='db')
        self.assertEqual(1, request.db)
        self.assertEqual(2, request.db)
    def test_set_property_reify(self):
        request = self._makeOne({})
        opts = [2, 1]
        def connect(obj):
            return opts.pop()
        request.set_property(connect, name='db', reify=True)
        self.assertEqual(1, request.db)
        self.assertEqual(1, request.db)
class TestRequestDeprecatedMethods(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
pyramid/tests/test_traversal.py
@@ -1,10 +1,13 @@
import unittest
from pyramid.testing import cleanUp
from pyramid.compat import text_
from pyramid.compat import native_
from pyramid.compat import text_type
from pyramid.compat import url_quote
from pyramid.compat import (
    text_,
    native_,
    text_type,
    url_quote,
    PY3,
    )
class TraversalPathTests(unittest.TestCase):
    def _callFUT(self, path):
@@ -127,6 +130,28 @@
        self.assertEqual(result['view_name'], '')
        self.assertEqual(result['subpath'], ())
        self.assertEqual(result['traversed'], ())
        self.assertEqual(result['root'], policy.root)
        self.assertEqual(result['virtual_root'], policy.root)
        self.assertEqual(result['virtual_root_path'], ())
    def test_call_with_pathinfo_highorder(self):
        foo = DummyContext(None, text_(b'Qu\xc3\xa9bec', 'utf-8'))
        root = DummyContext(foo, 'root')
        policy = self._makeOne(root)
        if PY3: # pragma: no cover
            path_info = b'/Qu\xc3\xa9bec'.decode('latin-1')
        else:
            path_info = b'/Qu\xc3\xa9bec'
        environ = self._getEnviron(PATH_INFO=path_info)
        request = DummyRequest(environ)
        result = policy(request)
        self.assertEqual(result['context'], foo)
        self.assertEqual(result['view_name'], '')
        self.assertEqual(result['subpath'], ())
        self.assertEqual(
            result['traversed'],
            (text_(b'Qu\xc3\xa9bec', 'utf-8'),)
            )
        self.assertEqual(result['root'], policy.root)
        self.assertEqual(result['virtual_root'], policy.root)
        self.assertEqual(result['virtual_root_path'], ())
@@ -295,6 +320,33 @@
        self.assertEqual(result['virtual_root'], policy.root)
        self.assertEqual(result['virtual_root_path'], ())
    def test_call_with_vh_root_highorder(self):
        bar = DummyContext(None, 'bar')
        foo = DummyContext(bar, text_(b'Qu\xc3\xa9bec', 'utf-8'))
        root = DummyContext(foo, 'root')
        policy = self._makeOne(root)
        if PY3: # pragma: no cover
            vhm_root = b'/Qu\xc3\xa9bec'.decode('latin-1')
        else:
            vhm_root = b'/Qu\xc3\xa9bec'
        environ = self._getEnviron(HTTP_X_VHM_ROOT=vhm_root,
                                   PATH_INFO='/bar')
        request = DummyRequest(environ)
        result = policy(request)
        self.assertEqual(result['context'], bar)
        self.assertEqual(result['view_name'], '')
        self.assertEqual(result['subpath'], ())
        self.assertEqual(
            result['traversed'],
            (text_(b'Qu\xc3\xa9bec', 'utf-8'), text_('bar'))
            )
        self.assertEqual(result['root'], policy.root)
        self.assertEqual(result['virtual_root'], foo)
        self.assertEqual(
            result['virtual_root_path'],
            (text_(b'Qu\xc3\xa9bec', 'utf-8'),)
            )
    def test_non_utf8_path_segment_unicode_path_segments_fails(self):
        from pyramid.exceptions import URLDecodeError
        foo = DummyContext()
pyramid/tests/test_urldispatch.py
@@ -1,7 +1,9 @@
import unittest
from pyramid import testing
from pyramid.compat import text_
from pyramid.compat import native_
from pyramid.compat import (
    text_,
    PY3,
    )
class TestRoute(unittest.TestCase):
    def _getTargetClass(self):
@@ -114,6 +116,16 @@
                         'archives/:action/:article2')
        self.assertEqual(mapper.routelist[0].pattern,
                         'archives/:action/:article2')
    def test___call__pathinfo_cant_be_decoded(self):
        from pyramid.exceptions import URLDecodeError
        mapper = self._makeOne()
        if PY3: # pragma: no cover
            path_info = b'\xff\xfe\xe6\x00'.decode('latin-1')
        else:
            path_info = b'\xff\xfe\xe6\x00'
        request = self._getRequest(PATH_INFO=path_info)
        self.assertRaises(URLDecodeError, mapper, request)
    def test___call__route_matches(self):
        mapper = self._makeOne()
@@ -292,12 +304,6 @@
        self.assertEqual(matcher('foo/baz/biz/buz/bar'), None)
        self.assertEqual(generator({'baz':1, 'buz':2}), '/foo/1/biz/2/bar')
    def test_url_decode_error(self):
        from pyramid.exceptions import URLDecodeError
        matcher, generator = self._callFUT('/:foo')
        self.assertRaises(URLDecodeError, matcher,
                          native_(b'/\xff\xfe\x8b\x00'))
    def test_custom_regex(self):
        matcher, generator = self._callFUT('foo/{baz}/biz/{buz:[^/\.]+}.{bar}')
        self.assertEqual(matcher('/foo/baz/biz/buz.bar'),
@@ -328,7 +334,8 @@
        self.assertEqual(generator({'buz':2001}), '/2001')
    def test_custom_regex_with_embedded_squigglies3(self):
        matcher, generator = self._callFUT('/{buz:(\d{2}|\d{4})-[a-zA-Z]{3,4}-\d{2}}')
        matcher, generator = self._callFUT(
            '/{buz:(\d{2}|\d{4})-[a-zA-Z]{3,4}-\d{2}}')
        self.assertEqual(matcher('/2001-Nov-15'), {'buz':'2001-Nov-15'})
        self.assertEqual(matcher('/99-June-10'), {'buz':'99-June-10'})
        self.assertEqual(matcher('/2-Nov-15'), None)
@@ -336,6 +343,63 @@
        self.assertEqual(matcher('/2001-No-15'), None)
        self.assertEqual(generator({'buz':'2001-Nov-15'}), '/2001-Nov-15')
        self.assertEqual(generator({'buz':'99-June-10'}), '/99-June-10')
    def test_pattern_with_high_order_literal(self):
        pattern = text_(b'/La Pe\xc3\xb1a/{x}', 'utf-8')
        matcher, generator = self._callFUT(pattern)
        self.assertEqual(matcher(text_(b'/La Pe\xc3\xb1a/x', 'utf-8')),
                         {'x':'x'})
        self.assertEqual(generator({'x':'1'}), '/La%20Pe%C3%B1a/1')
    def test_pattern_generate_with_high_order_dynamic(self):
        pattern = '/{x}'
        _, generator = self._callFUT(pattern)
        self.assertEqual(
            generator({'x':text_(b'La Pe\xc3\xb1a', 'utf-8')}),
            '/La%20Pe%C3%B1a')
    def test_docs_sample_generate(self):
        # sample from urldispatch.rst
        pattern = text_(b'/La Pe\xc3\xb1a/{city}', 'utf-8')
        _, generator = self._callFUT(pattern)
        self.assertEqual(
            generator({'city':text_(b'Qu\xc3\xa9bec', 'utf-8')}),
            '/La%20Pe%C3%B1a/Qu%C3%A9bec')
    def test_generate_with_mixedtype_values(self):
        pattern = '/{city}/{state}'
        _, generator = self._callFUT(pattern)
        result = generator(
            {'city': text_(b'Qu\xc3\xa9bec', 'utf-8'),
             'state': b'La Pe\xc3\xb1a'}
            )
        self.assertEqual(result, '/Qu%C3%A9bec/La%20Pe%C3%B1a')
        # should be a native string
        self.assertEqual(type(result), str)
    def test_highorder_pattern_utf8(self):
        pattern = b'/La Pe\xc3\xb1a/{city}'
        self.assertRaises(ValueError, self._callFUT, pattern)
    def test_generate_with_string_remainder_and_unicode_replacement(self):
        pattern = text_(b'/abc*remainder', 'utf-8')
        _, generator = self._callFUT(pattern)
        result = generator(
            {'remainder': text_(b'/Qu\xc3\xa9bec/La Pe\xc3\xb1a', 'utf-8')}
            )
        self.assertEqual(result, '/abc/Qu%C3%A9bec/La%20Pe%C3%B1a')
        # should be a native string
        self.assertEqual(type(result), str)
    def test_generate_with_string_remainder_and_nonstring_replacement(self):
        pattern = text_(b'/abc/*remainder', 'utf-8')
        _, generator = self._callFUT(pattern)
        result = generator(
            {'remainder': None}
            )
        self.assertEqual(result, '/abc/None')
        # should be a native string
        self.assertEqual(type(result), str)
class TestCompileRouteFunctional(unittest.TestCase):
    def matches(self, pattern, path, expected):
@@ -368,11 +432,11 @@
        self.matches('*traverse', '/zzz/abc', {'traverse':('zzz', 'abc')})
        self.matches('*traverse', '/zzz/ abc', {'traverse':('zzz', ' abc')})
        #'/La%20Pe%C3%B1a'
        self.matches('{x}', native_(b'/La Pe\xc3\xb1a'),
                     {'x':text_(b'La Pe\xf1a')})
        self.matches('{x}', text_(b'/La Pe\xc3\xb1a', 'utf-8'),
                     {'x':text_(b'La Pe\xc3\xb1a', 'utf-8')})
        # '/La%20Pe%C3%B1a/x'
        self.matches('*traverse', native_(b'/La Pe\xc3\xb1a/x'),
                     {'traverse':(text_(b'La Pe\xf1a'), 'x')})
        self.matches('*traverse', text_(b'/La Pe\xc3\xb1a/x'),
                     {'traverse':(text_(b'La Pe\xc3\xb1a'), 'x')})
        self.matches('/foo/{id}.html', '/foo/bar.html', {'id':'bar'})
        self.matches('/{num:[0-9]+}/*traverse', '/555/abc/def',
                     {'num':'555', 'traverse':('abc', 'def')})
@@ -394,11 +458,12 @@
        self.matches('*traverse', '/zzz/abc', {'traverse':('zzz', 'abc')})
        self.matches('*traverse', '/zzz/ abc', {'traverse':('zzz', ' abc')})
        #'/La%20Pe%C3%B1a'
        self.matches(':x', native_(b'/La Pe\xc3\xb1a'),
                     {'x':text_(b'La Pe\xf1a')})
        # pattern, path, expected
        self.matches(':x', text_(b'/La Pe\xc3\xb1a', 'utf-8'),
                     {'x':text_(b'La Pe\xc3\xb1a', 'utf-8')})
        # '/La%20Pe%C3%B1a/x'
        self.matches('*traverse', native_(b'/La Pe\xc3\xb1a/x'),
                     {'traverse':(text_(b'La Pe\xf1a'), 'x')})
        self.matches('*traverse', text_(b'/La Pe\xc3\xb1a/x', 'utf-8'),
                     {'traverse':(text_(b'La Pe\xc3\xb1a', 'utf-8'), 'x')})
        self.matches('/foo/:id.html', '/foo/bar.html', {'id':'bar'})
        self.matches('/foo/:id_html', '/foo/bar_html', {'id_html':'bar_html'})
        self.matches('zzz/:_', '/zzz/abc', {'_':'abc'})
pyramid/tests/test_util.py
@@ -1,6 +1,114 @@
import unittest
from pyramid.compat import PY3
class Test_InstancePropertyMixin(unittest.TestCase):
    def _makeOne(self):
        cls = self._targetClass()
        class Foo(cls):
            pass
        return Foo()
    def _targetClass(self):
        from pyramid.util import InstancePropertyMixin
        return InstancePropertyMixin
    def test_callable(self):
        def worker(obj):
            return obj.bar
        foo = self._makeOne()
        foo.set_property(worker)
        foo.bar = 1
        self.assertEqual(1, foo.worker)
        foo.bar = 2
        self.assertEqual(2, foo.worker)
    def test_callable_with_name(self):
        def worker(obj):
            return obj.bar
        foo = self._makeOne()
        foo.set_property(worker, name='x')
        foo.bar = 1
        self.assertEqual(1, foo.x)
        foo.bar = 2
        self.assertEqual(2, foo.x)
    def test_callable_with_reify(self):
        def worker(obj):
            return obj.bar
        foo = self._makeOne()
        foo.set_property(worker, reify=True)
        foo.bar = 1
        self.assertEqual(1, foo.worker)
        foo.bar = 2
        self.assertEqual(1, foo.worker)
    def test_callable_with_name_reify(self):
        def worker(obj):
            return obj.bar
        foo = self._makeOne()
        foo.set_property(worker, name='x')
        foo.set_property(worker, name='y', reify=True)
        foo.bar = 1
        self.assertEqual(1, foo.y)
        self.assertEqual(1, foo.x)
        foo.bar = 2
        self.assertEqual(2, foo.x)
        self.assertEqual(1, foo.y)
    def test_property_without_name(self):
        def worker(obj): pass
        foo = self._makeOne()
        self.assertRaises(ValueError, foo.set_property, property(worker))
    def test_property_with_name(self):
        def worker(obj):
            return obj.bar
        foo = self._makeOne()
        foo.set_property(property(worker), name='x')
        foo.bar = 1
        self.assertEqual(1, foo.x)
        foo.bar = 2
        self.assertEqual(2, foo.x)
    def test_property_with_reify(self):
        def worker(obj): pass
        foo = self._makeOne()
        self.assertRaises(ValueError, foo.set_property,
                          property(worker), name='x', reify=True)
    def test_override_property(self):
        def worker(obj): pass
        foo = self._makeOne()
        foo.set_property(worker, name='x')
        def doit():
            foo.x = 1
        self.assertRaises(AttributeError, doit)
    def test_override_reify(self):
        def worker(obj): pass
        foo = self._makeOne()
        foo.set_property(worker, name='x', reify=True)
        foo.x = 1
        self.assertEqual(1, foo.x)
        foo.x = 2
        self.assertEqual(2, foo.x)
    def test_reset_property(self):
        foo = self._makeOne()
        foo.set_property(lambda _: 1, name='x')
        self.assertEqual(1, foo.x)
        foo.set_property(lambda _: 2, name='x')
        self.assertEqual(2, foo.x)
    def test_reset_reify(self):
        """ This is questionable behavior, but may as well get notified
        if it changes."""
        foo = self._makeOne()
        foo.set_property(lambda _: 1, name='x', reify=True)
        self.assertEqual(1, foo.x)
        foo.set_property(lambda _: 2, name='x', reify=True)
        self.assertEqual(1, foo.x)
class Test_WeakOrderedSet(unittest.TestCase):
    def _makeOne(self):
        from pyramid.config import WeakOrderedSet
pyramid/tests/test_view.py
@@ -260,10 +260,7 @@
    def test_create_defaults(self):
        decorator = self._makeOne()
        self.assertEqual(decorator.name, '')
        self.assertEqual(decorator.request_type, None)
        self.assertEqual(decorator.context, None)
        self.assertEqual(decorator.permission, None)
        self.assertEqual(decorator.__dict__, {})
    def test_create_context_trumps_for(self):
        decorator = self._makeOne(context='123', for_='456')
@@ -274,9 +271,11 @@
        self.assertEqual(decorator.context, '456')
        
    def test_create_nondefaults(self):
        decorator = self._makeOne(name=None, request_type=None, for_=None,
                                  permission='foo', mapper='mapper',
                                  decorator='decorator', match_param='match_param')
        decorator = self._makeOne(
            name=None, request_type=None, for_=None,
            permission='foo', mapper='mapper',
            decorator='decorator', match_param='match_param'
            )
        self.assertEqual(decorator.name, None)
        self.assertEqual(decorator.request_type, None)
        self.assertEqual(decorator.context, None)
@@ -295,9 +294,11 @@
        config = call_venusian(venusian)
        settings = config.settings
        self.assertEqual(len(settings), 1)
        self.assertEqual(settings[0]['permission'], None)
        self.assertEqual(settings[0]['context'], None)
        self.assertEqual(settings[0]['request_type'], None)
        self.assertEqual(len(settings), 1)
        self.assertEqual(len(settings[0]), 3)
        self.assertEqual(settings[0]['venusian'], venusian)
        self.assertEqual(settings[0]['view'], None) # comes from call_venusian
        self.assertEqual(settings[0]['_info'], 'codeinfo')
    def test_call_class(self):
        decorator = self._makeOne()
@@ -310,10 +311,11 @@
        config = call_venusian(venusian)
        settings = config.settings
        self.assertEqual(len(settings), 1)
        self.assertEqual(settings[0]['permission'], None)
        self.assertEqual(settings[0]['context'], None)
        self.assertEqual(settings[0]['request_type'], None)
        self.assertEqual(len(settings[0]), 4)
        self.assertEqual(settings[0]['venusian'], venusian)
        self.assertEqual(settings[0]['view'], None) # comes from call_venusian
        self.assertEqual(settings[0]['attr'], 'foo')
        self.assertEqual(settings[0]['_info'], 'codeinfo')
    def test_call_class_attr_already_set(self):
        decorator = self._makeOne(attr='abc')
@@ -326,10 +328,11 @@
        config = call_venusian(venusian)
        settings = config.settings
        self.assertEqual(len(settings), 1)
        self.assertEqual(settings[0]['permission'], None)
        self.assertEqual(settings[0]['context'], None)
        self.assertEqual(settings[0]['request_type'], None)
        self.assertEqual(len(settings[0]), 4)
        self.assertEqual(settings[0]['venusian'], venusian)
        self.assertEqual(settings[0]['view'], None) # comes from call_venusian
        self.assertEqual(settings[0]['attr'], 'abc')
        self.assertEqual(settings[0]['_info'], 'codeinfo')
    def test_stacking(self):
        decorator1 = self._makeOne(name='1')
@@ -593,7 +596,7 @@
        @view_defaults(route_name='ghi')
        class Bar(Foo): pass
        self.assertEqual(Bar.__view_defaults__['route_name'],'ghi')
        self.assertEqual(Bar.__view_defaults__['renderer'], None)
        self.assertFalse('renderer' in Bar.__view_defaults__)
    def test_it_inheritance_overriden_empty(self):
        from pyramid.view import view_defaults
@@ -601,8 +604,7 @@
        class Foo(object): pass
        @view_defaults()
        class Bar(Foo): pass
        self.assertEqual(Bar.__view_defaults__['route_name'], None)
        self.assertEqual(Bar.__view_defaults__['renderer'], None)
        self.assertEqual(Bar.__view_defaults__, {})
class ExceptionResponse(Exception):
    status = '404 Not Found'
pyramid/traversal.py
@@ -16,12 +16,12 @@
    PY3,
    native_,
    text_,
    bytes_,
    ascii_native_,
    text_type,
    binary_type,
    url_unquote_native,
    is_nonstr_iter,
    decode_path_info,
    unquote_bytes_to_wsgi,
    )
from pyramid.encode import url_quote
@@ -429,33 +429,46 @@
def traversal_path(path):
    """ Variant of :func:`pyramid.traversal.traversal_path_info` suitable for
    decoding paths that are URL-encoded."""
    path = ascii_native_(path)
    path = url_unquote_native(path, 'latin-1', 'strict')
    return traversal_path_info(path)
    decoding paths that are URL-encoded.
    If this function is passed a Unicode object instead of a sequence of
    bytes as ``path``, that Unicode object *must* directly encodeable to
    ASCII.  For example, u'/foo' will work but u'/<unprintable unicode>' (a
    Unicode object with characters that cannot be encoded to ascii) will
    not. A :exc:`UnicodeEncodeError` will be raised if the Unicode cannot be
    encoded directly to ASCII.
    """
    if isinstance(path, text_type):
        # must not possess characters outside ascii
        path = path.encode('ascii')
    # we unquote this path exactly like a PEP 3333 server would
    path = unquote_bytes_to_wsgi(path) # result will be a native string
    return traversal_path_info(path) # result will be a tuple of unicode
@lru_cache(1000)
def traversal_path_info(path):
    """ Given a ``PATH_INFO`` environ value (slash-separated path segments),
    return a tuple representing that path which can be used to traverse a
    resource tree.
    """ Given``path``, return a tuple representing that path which can be
    used to traverse a resource tree.  ``path`` is assumed to be an
    already-URL-decoded ``str`` type as if it had come to us from an upstream
    WSGI server as the ``PATH_INFO`` environ variable.
    ``PATH_INFO`` is assumed to already be URL-decoded.  It is encoded to
    bytes using the Latin-1 encoding; the resulting set of bytes is
    subsequently decoded to text using the UTF-8 encoding; a
    :exc:`pyramid.exc.URLDecodeError` is raised if a the URL cannot be
    decoded.
    The ``path`` is first decoded to from its WSGI representation to Unicode;
    it is decoded differently depending on platform:
    The ``PATH_INFO`` is split on slashes, creating a list of segments.  Each
    segment subsequently decoded into Unicode.  If a segment name is empty or
    if it is ``.``, it is ignored.  If a segment name is ``..``, the previous
    segment is deleted, and the ``..`` is ignored.
    - On Python 2, ``path`` is decoded to Unicode from bytes using the UTF-8
      decoding directly; a :exc:`pyramid.exc.URLDecodeError` is raised if a the
      URL cannot be decoded.
    If this function is passed a Unicode object instead of a string, that
    Unicode object *must* directly encodeable to ASCII.  For example, u'/foo'
    will work but u'/<unprintable unicode>' (a Unicode object with characters
    that cannot be encoded to ascii) will not. A :exc:`UnicodeError` will be
    raised if the Unicode cannot be encoded directly to ASCII.
    - On Python 3, as per the PEP 3333 spec, ``path`` is first encoded to
      bytes using the Latin-1 encoding; the resulting set of bytes is
      subsequently decoded to text using the UTF-8 encoding; a
      :exc:`pyramid.exc.URLDecodeError` is raised if a the URL cannot be
      decoded.
    The ``path`` is split on slashes, creating a list of segments.  If a
    segment name is empty or if it is ``.``, it is ignored.  If a segment
    name is ``..``, the previous segment is deleted, and the ``..`` is
    ignored.
    Examples:
@@ -504,9 +517,15 @@
      applications in :app:`Pyramid`.
    """
    try:
        path = bytes_(path, 'latin-1').decode('utf-8')
        path = decode_path_info(path) # result will be Unicode
    except UnicodeDecodeError as e:
        raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason)
    return split_path_info(path) # result will be tuple of Unicode
@lru_cache(1000)
def split_path_info(path):
    # suitable for splitting an already-unquoted-already-decoded (unicode)
    # path value
    path = path.strip('/')
    clean = []
    for segment in path.split('/'):
@@ -622,25 +641,34 @@
            path = matchdict.get('traverse', '/') or '/'
            if is_nonstr_iter(path):
                # this is a *traverse stararg (not a {traverse})
                path = '/'.join([quote_path_segment(x) for x in path]) or '/'
                # routing has already decoded these elements, so we just
                # need to join them
                path = '/'.join(path) or '/'
            subpath = matchdict.get('subpath', ())
            if not is_nonstr_iter(subpath):
                # this is not a *subpath stararg (just a {subpath})
                subpath = traversal_path_info(subpath)
                # routing has already decoded this string, so we just need
                # to split it
                subpath = split_path_info(subpath)
        else:
            # this request did not match a route
            subpath = ()
            try:
                path = environ['PATH_INFO'] or '/'
                # empty if mounted under a path in mod_wsgi, for example
                path = decode_path_info(environ['PATH_INFO'] or '/')
            except KeyError:
                path = '/'
            except UnicodeDecodeError as e:
                raise URLDecodeError(e.encoding, e.object, e.start, e.end,
                                     e.reason)
        if VH_ROOT_KEY in environ:
            vroot_path = environ[VH_ROOT_KEY]
            vroot_tuple = traversal_path_info(vroot_path)
            vpath = vroot_path + path
            # HTTP_X_VHM_ROOT
            vroot_path = decode_path_info(environ[VH_ROOT_KEY])
            vroot_tuple = split_path_info(vroot_path)
            vpath = vroot_path + path # both will (must) be unicode or asciistr
            vroot_idx = len(vroot_tuple) -1
        else:
            vroot_tuple = ()
@@ -660,7 +688,7 @@
            # and this hurts readability; apologies
            i = 0
            view_selector = self.VIEW_SELECTOR
            vpath_tuple = traversal_path_info(vpath)
            vpath_tuple = split_path_info(vpath)
            for segment in vpath_tuple:
                if segment[:2] == view_selector:
                    return {'context':ob,
pyramid/url.py
@@ -67,7 +67,7 @@
        encoded to UTF-8.  The resulting strings are joined with slashes
        and rendered into the URL.  If a string is passed as a
        ``*remainder`` replacement value, it is tacked on to the URL
        untouched.
        after being URL-quoted-except-for-embedded-slashes.
        If a keyword argument ``_query`` is present, it will be used to
        compose a query string that will be tacked on to the end of the
pyramid/urldispatch.py
@@ -7,19 +7,21 @@
    )
from pyramid.compat import (
    PY3,
    native_,
    bytes_,
    text_,
    text_type,
    string_types,
    binary_type,
    is_nonstr_iter,
    url_quote,
    )
from pyramid.exceptions import URLDecodeError
from pyramid.traversal import (
    traversal_path_info,
    quote_path_segment,
    decode_path_info,
    split_path_info,
    )
_marker = object()
@@ -70,9 +72,11 @@
        environ = request.environ
        try:
            # empty if mounted under a path in mod_wsgi, for example
            path = environ['PATH_INFO'] or '/'
            path = decode_path_info(environ['PATH_INFO'] or '/')
        except KeyError:
            path = '/'
        except UnicodeDecodeError as e:
            raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason)
        for route in self.routelist:
            match = route.match(path)
@@ -100,80 +104,128 @@
    return '{%s}' % name[1:]
def _compile_route(route):
    # This function really wants to consume Unicode patterns natively, but if
    # someone passes us a bytestring, we allow it by converting it to Unicode
    # using the ASCII decoding.  We decode it using ASCII because we dont
    # want to accept bytestrings with high-order characters in them here as
    # we have no idea what the encoding represents.
    if route.__class__ is not text_type:
        try:
            route = text_(route, 'ascii')
        except UnicodeDecodeError:
            raise ValueError(
                'The pattern value passed to add_route must be '
                'either a Unicode string or a plain string without '
                'any non-ASCII characters (you provided %r).' % route)
    if old_route_re.search(route) and not route_re.search(route):
        route = old_route_re.sub(update_pattern, route)
    if not route.startswith('/'):
        route = '/' + route
    star = None
    remainder = None
    if star_at_end.search(route):
        route, star = route.rsplit('*', 1)
        route, remainder = route.rsplit('*', 1)
    pat = route_re.split(route)
    # every element in "pat" will be Unicode (regardless of whether the
    # route_re regex pattern is itself Unicode or str)
    pat.reverse()
    rpat = []
    gen = []
    prefix = pat.pop() # invar: always at least one element (route='/'+route)
    rpat.append(re.escape(prefix))
    gen.append(prefix)
    # We want to generate URL-encoded URLs, so we url-quote the prefix, being
    # careful not to quote any embedded slashes.  We have to replace '%' with
    # '%%' afterwards, as the strings that go into "gen" are used as string
    # replacement targets.
    gen.append(quote_path_segment(prefix, safe='/').replace('%', '%%')) # native
    rpat.append(re.escape(prefix)) # unicode
    while pat:
        name = pat.pop()
        name = pat.pop() # unicode
        name = name[1:-1]
        if ':' in name:
            name, reg = name.split(':')
        else:
            reg = '[^/]+'
        gen.append('%%(%s)s' % name)
        name = '(?P<%s>%s)' % (name, reg)
        gen.append('%%(%s)s' % native_(name)) # native
        name = '(?P<%s>%s)' % (name, reg) # unicode
        rpat.append(name)
        s = pat.pop()
        s = pat.pop() # unicode
        if s:
            rpat.append(re.escape(s))
            gen.append(s)
            rpat.append(re.escape(s)) # unicode
            # We want to generate URL-encoded URLs, so we url-quote this
            # literal in the pattern, being careful not to quote the embedded
            # slashes.  We have to replace '%' with '%%' afterwards, as the
            # strings that go into "gen" are used as string replacement
            # targets.  What is appended to gen is a native string.
            gen.append(quote_path_segment(s, safe='/').replace('%', '%%'))
    if star:
        rpat.append('(?P<%s>.*?)' % star)
        gen.append('%%(%s)s' % star)
    if remainder:
        rpat.append('(?P<%s>.*?)' % remainder) # unicode
        gen.append('%%(%s)s' % native_(remainder)) # native
    pattern = ''.join(rpat) + '$'
    pattern = ''.join(rpat) + '$' # unicode
    match = re.compile(pattern).match
    def matcher(path):
        # This function really wants to consume Unicode patterns natively,
        # but if someone passes us a bytestring, we allow it by converting it
        # to Unicode using the ASCII decoding.  We decode it using ASCII
        # because we dont want to accept bytestrings with high-order
        # characters in them here as we have no idea what the encoding
        # represents.
        if path.__class__ is not text_type:
            path = text_(path, 'ascii')
        m = match(path)
        if m is None:
            return m
            return None
        d = {}
        for k, v in m.groupdict().items():
            if k == star:
                d[k] = traversal_path_info(v)
            # k and v will be Unicode 2.6.4 and lower doesnt accept unicode
            # kwargs as **kw, so we explicitly cast the keys to native
            # strings in case someone wants to pass the result as **kw
            nk = native_(k, 'ascii')
            if k == remainder:
                d[nk] = split_path_info(v)
            else:
                try:
                    val = bytes_(v).decode('utf-8', 'strict')
                    d[k] = val
                except UnicodeDecodeError as e:
                    raise URLDecodeError(
                        e.encoding, e.object, e.start, e.end, e.reason
                        )
                d[nk] = v
        return d
    gen = ''.join(gen)
    def generator(dict):
        newdict = {}
        for k, v in dict.items():
            if v.__class__ is text_type:
                v = native_(v, 'utf-8')
            if k == star and is_nonstr_iter(v):
                v = '/'.join([quote_path_segment(x) for x in v])
            elif k != star:
            if PY3: # pragma: no cover
                if v.__class__ is binary_type:
                    # url_quote below needs a native string, not bytes on Py3
                    v = v.decode('utf-8')
            else:
                if v.__class__ is text_type:
                    # url_quote below needs bytes, not unicode on Py2
                    v = v.encode('utf-8')
            if k == remainder:
                # a stararg argument
                if is_nonstr_iter(v):
                    v = '/'.join([quote_path_segment(x) for x in v]) # native
                else:
                    if v.__class__ not in string_types:
                        v = str(v)
                    v = quote_path_segment(v, safe='/')
            else:
                if v.__class__ not in string_types:
                    v = str(v)
                v = url_quote(v, safe='')
                # v may be bytes (py2) or native string (py3)
                v = quote_path_segment(v)
            # at this point, the value will be a native string
            newdict[k] = v
        return gen % newdict
        result = gen % newdict # native string result
        return result
    return matcher, generator
pyramid/util.py
@@ -14,6 +14,89 @@
    def __init__(self, package=None): # default to package = None for bw compat
        return _DottedNameResolver.__init__(self, package)
class InstancePropertyMixin(object):
    """ Mixin that will allow an instance to add properties at
    run-time as if they had been defined via @property or @reify
    on the class itself.
    """
    def set_property(self, func, name=None, reify=False):
        """ Add a callable or a property descriptor to the instance.
        Properties, unlike attributes, are lazily evaluated by executing
        an underlying callable when accessed. They can be useful for
        adding features to an object without any cost if those features
        go unused.
        A property may also be reified via the
        :class:`pyramid.decorator.reify` decorator by setting
        ``reify=True``, allowing the result of the evaluation to be
        cached. Thus the value of the property is only computed once for
        the lifetime of the object.
        ``func`` can either be a callable that accepts the instance as
        its single positional parameter, or it can be a property
        descriptor.
        If the ``func`` is a property descriptor, the ``name`` parameter
        must be supplied or a ``ValueError`` will be raised. Also note
        that a property descriptor cannot be reified, so ``reify`` must
        be ``False``.
        If ``name`` is None, the name of the property will be computed
        from the name of the ``func``.
        .. code-block:: python
           :linenos:
           class Foo(InstancePropertyMixin):
               _x = 1
           def _get_x(self):
               return _x
           def _set_x(self, value):
               self._x = value
           foo = Foo()
           foo.set_property(property(_get_x, _set_x), name='x')
           foo.set_property(_get_x, name='y', reify=True)
           >>> foo.x
           1
           >>> foo.y
           1
           >>> foo.x = 5
           >>> foo.x
           5
           >>> foo.y # notice y keeps the original value
           1
        """
        is_property = isinstance(func, property)
        if is_property:
            fn = func
            if name is None:
                raise ValueError('must specify "name" for a property')
            if reify:
                raise ValueError('cannot reify a property')
        elif name is not None:
            fn = lambda this: func(this)
            fn.__name__ = name
            fn.__doc__ = func.__doc__
        else:
            name = func.__name__
            fn = func
        if reify:
            import pyramid.decorator
            fn = pyramid.decorator.reify(fn)
        elif not is_property:
            fn = property(fn)
        attrs = { name: fn }
        parent = self.__class__
        cls = type(parent.__name__, (parent, object), attrs)
        self.__class__ = cls
class WeakOrderedSet(object):
    """ Maintain a set of items.
pyramid/view.py
@@ -133,6 +133,15 @@
        return None
    return ''.join(iterable)
class _default(object):
    def __nonzero__(self):
        return False
    __bool__ = __nonzero__
    def __repr__(self): # pragma: no cover
        return '(default)'
default = _default()
class view_config(object):
    """ A function, class or method :term:`decorator` which allows a
    developer to create view registrations nearer to a :term:`view
@@ -168,39 +177,29 @@
    and ``match_param``.
    The meanings of these arguments are the same as the arguments passed to
    :meth:`pyramid.config.Configurator.add_view`.
    :meth:`pyramid.config.Configurator.add_view`.  If any argument is left
    out, its default will be the equivalent ``add_view`` default.
    See :ref:`mapping_views_using_a_decorator_section` for details about
    using :class:`view_config`.
    """
    venusian = venusian # for testing injection
    def __init__(self, name='', request_type=None, for_=None, permission=None,
                 route_name=None, request_method=None, request_param=None,
                 containment=None, attr=None, renderer=None, wrapper=None,
                 xhr=False, accept=None, header=None, path_info=None,
                 custom_predicates=(), context=None, decorator=None,
                 mapper=None, http_cache=None, match_param=None):
        self.name = name
        self.request_type = request_type
        self.context = context or for_
        self.permission = permission
        self.route_name = route_name
        self.request_method = request_method
        self.request_param = request_param
        self.containment = containment
        self.attr = attr
        self.renderer = renderer
        self.wrapper = wrapper
        self.xhr = xhr
        self.accept = accept
        self.header = header
        self.path_info = path_info
        self.custom_predicates = custom_predicates
        self.decorator = decorator
        self.mapper = mapper
        self.http_cache = http_cache
        self.match_param = match_param
    def __init__(self, name=default, request_type=default, for_=default,
                 permission=default, route_name=default,
                 request_method=default, request_param=default,
                 containment=default, attr=default, renderer=default,
                 wrapper=default, xhr=default, accept=default,
                 header=default, path_info=default,
                 custom_predicates=default, context=default,
                 decorator=default, mapper=default, http_cache=default,
                 match_param=default):
        L = locals()
        if (context is not default) or (for_ is not default):
            L['context'] = context or for_
        for k, v in L.items():
            if k not in ('self', 'L') and v is not default:
                setattr(self, k, v)
    def __call__(self, wrapped):
        settings = self.__dict__.copy()
@@ -215,7 +214,7 @@
            # if the decorator was attached to a method in a class, or
            # otherwise executed at class scope, we need to set an
            # 'attr' into the settings if one isn't already in there
            if settings['attr'] is None:
            if settings.get('attr') is None:
                settings['attr'] = wrapped.__name__
        settings['_info'] = info.codeinfo # fbo "action_method"
setup.py
@@ -56,7 +56,7 @@
        ])
setup(name='pyramid',
      version='1.3a2',
      version='1.3a5',
      description=('The Pyramid web application development framework, a '
                   'Pylons project'),
      long_description=README + '\n\n' +  CHANGES,