John Anderson
2015-03-06 50a8a077f403b89ff43ad745f101213c2cfeea8f
Merge branch 'master' of https://github.com/Pylons/pyramid into start_pep8

Conflicts:
pyramid/config/views.py
pyramid/scaffolds/tests.py
tox.ini
3 files deleted
1 files added
49 files modified
1530 ■■■■ changed files
.gitignore 4 ●●●● patch | view | raw | blame | history
.travis.yml 4 ●●● patch | view | raw | blame | history
CHANGES.txt 53 ●●●●● patch | view | raw | blame | history
CONTRIBUTORS.txt 2 ●●●●● patch | view | raw | blame | history
HACKING.txt 2 ●●● patch | view | raw | blame | history
docs/api/config.rst 5 ●●●●● patch | view | raw | blame | history
docs/api/request.rst 1 ●●●● patch | view | raw | blame | history
docs/api/static.rst 6 ●●●●● patch | view | raw | blame | history
docs/narr/assets.rst 15 ●●●● patch | view | raw | blame | history
docs/narr/extconfig.rst 104 ●●●●● patch | view | raw | blame | history
docs/narr/security.rst 60 ●●●●● patch | view | raw | blame | history
docs/narr/urldispatch.rst 58 ●●●● patch | view | raw | blame | history
pyramid/compat.py 65 ●●●● patch | view | raw | blame | history
pyramid/config/__init__.py 108 ●●●● patch | view | raw | blame | history
pyramid/config/assets.py 26 ●●●●● patch | view | raw | blame | history
pyramid/config/factories.py 4 ●●●● patch | view | raw | blame | history
pyramid/config/views.py 14 ●●●●● patch | view | raw | blame | history
pyramid/httpexceptions.py 8 ●●●●● patch | view | raw | blame | history
pyramid/i18n.py 8 ●●●● patch | view | raw | blame | history
pyramid/interfaces.py 22 ●●●●● patch | view | raw | blame | history
pyramid/renderers.py 64 ●●●● patch | view | raw | blame | history
pyramid/request.py 26 ●●●●● patch | view | raw | blame | history
pyramid/router.py 3 ●●●● patch | view | raw | blame | history
pyramid/scaffolds/copydir.py 8 ●●●● patch | view | raw | blame | history
pyramid/scaffolds/tests.py 8 ●●●● patch | view | raw | blame | history
pyramid/scripting.py 8 ●●●●● patch | view | raw | blame | history
pyramid/scripts/pserve.py 41 ●●●● patch | view | raw | blame | history
pyramid/session.py 4 ●●●● patch | view | raw | blame | history
pyramid/static.py 73 ●●●● patch | view | raw | blame | history
pyramid/tests/test_config/pkgs/asset/models.py 8 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_config/pkgs/asset/views.py 22 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_adapters.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_assets.py 48 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_init.py 67 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_views.py 28 ●●●● patch | view | raw | blame | history
pyramid/tests/test_path.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/test_renderers.py 51 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_request.py 47 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_router.py 8 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_scripting.py 16 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/pystartup.py 1 ●●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/pystartup.txt 3 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/test_pserve.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/test_pshell.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/test_static.py 16 ●●●● patch | view | raw | blame | history
pyramid/tests/test_traversal.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/test_urldispatch.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/test_util.py 240 ●●●● patch | view | raw | blame | history
pyramid/traversal.py 2 ●●● patch | view | raw | blame | history
pyramid/urldispatch.py 2 ●●● patch | view | raw | blame | history
pyramid/util.py 81 ●●●●● patch | view | raw | blame | history
setup.cfg 3 ●●●●● patch | view | raw | blame | history
tox.ini 71 ●●●● patch | view | raw | blame | history
.gitignore
@@ -1,5 +1,6 @@
*.egg
*.egg-info
.eggs/
*.pyc
*$py.class
*.pt.py
@@ -7,9 +8,12 @@
*~
.*.swp
.coverage
.coverage.*
.tox/
nosetests.xml
coverage.xml
nosetests-*.xml
coverage-*.xml
tutorial.db
build/
dist/
.travis.yml
@@ -10,7 +10,9 @@
  - TOXENV=py34
  - TOXENV=pypy
  - TOXENV=pypy3
  - TOXENV=cover
  - TOXENV=py2-docs
  - TOXENV=py3-docs
  - TOXENV=py2-cover,py3-cover,coverage
install:
  - travis_retry pip install tox
CHANGES.txt
@@ -4,6 +4,20 @@
Features
--------
- The ``pyramid.config.Configurator`` has grown the ability to allow
  actions to call other actions during a commit-cycle. This enables much more
  logic to be placed into actions, such as the ability to invoke other actions
  or group them for improved conflict detection. We have also exposed and
  documented the config phases that Pyramid uses in order to further assist
  in building conforming addons.
  See https://github.com/Pylons/pyramid/pull/1513
- Add ``pyramid.request.apply_request_extensions`` function which can be
  used in testing to apply any request extensions configured via
  ``config.add_request_method``. Previously it was only possible to test
  the extensions by going through Pyramid's router.
  See https://github.com/Pylons/pyramid/pull/1581
- pcreate when run without a scaffold argument will now print information on
  the missing flag, as well as a list of available scaffolds.
  See https://github.com/Pylons/pyramid/pull/1566 and
@@ -12,9 +26,15 @@
- Added support / testing for 'pypy3' under Tox and Travis.
  See https://github.com/Pylons/pyramid/pull/1469
- Automate code coverage metrics across py2 and py3 instead of just py2.
  See https://github.com/Pylons/pyramid/pull/1471
- Cache busting for static resources has been added and is available via a new
  argument to ``pyramid.config.Configurator.add_static_view``: ``cachebust``.
  See https://github.com/Pylons/pyramid/pull/1380
  Core APIs are shipped for both cache busting via query strings and
  path segments and may be extended to fit into custom asset pipelines.
  See https://github.com/Pylons/pyramid/pull/1380 and
  https://github.com/Pylons/pyramid/pull/1583
- Add ``pyramid.config.Configurator.root_package`` attribute and init
  parameter to assist with includeable packages that wish to resolve
@@ -85,8 +105,27 @@
- Support keyword-only arguments and function annotations in views in
  Python 3. See https://github.com/Pylons/pyramid/pull/1556
- ``request.response`` will no longer be mutated when using the
  ``pyramid.renderers.render_to_response()`` API.  It is now necessary to
  pass in a ``response=`` argument to ``render_to_response`` if you wish to
  supply the renderer with a custom response object for it to use. If you
  do not pass one then a response object will be created using the
  application's ``IResponseFactory``. Almost all renderers
  mutate the ``request.response`` response object (for example, the JSON
  renderer sets ``request.response.content_type`` to ``application/json``).
  However, when invoking ``render_to_response`` it is not expected that the
  response object being returned would be the same one used later in the
  request. The response object returned from ``render_to_response`` is now
  explicitly different from ``request.response``. This does not change the
  API of a renderer. See https://github.com/Pylons/pyramid/pull/1563
Bug Fixes
---------
- Work around an issue where ``pserve --reload`` would leave terminal echo
  disabled if it reloaded during a pdb session.
  See https://github.com/Pylons/pyramid/pull/1577,
  https://github.com/Pylons/pyramid/pull/1592
- ``pyramid.wsgi.wsgiapp`` and ``pyramid.wsgi.wsgiapp2`` now raise
  ``ValueError`` when accidentally passed ``None``.
@@ -127,12 +166,22 @@
- Allow the ``pyramid.renderers.JSONP`` renderer to work even if there is no
  valid request object. In this case it will not wrap the object in a
  callback and thus behave just like the ``pyramid.renderers.JSON` renderer.
  callback and thus behave just like the ``pyramid.renderers.JSON`` renderer.
  See https://github.com/Pylons/pyramid/pull/1561
- Prevent "parameters to load are deprecated" ``DeprecationWarning``
  from setuptools>=11.3. See https://github.com/Pylons/pyramid/pull/1541
- Avoiding sharing the ``IRenderer`` objects across threads when attached to
  a view using the `renderer=` argument. These renderers were instantiated
  at time of first render and shared between requests, causing potentially
  subtle effects like `pyramid.reload_templates = true` failing to work
  in `pyramid_mako`. See https://github.com/Pylons/pyramid/pull/1575
  and https://github.com/Pylons/pyramid/issues/1268
- Avoiding timing attacks against CSRF tokens.
  See https://github.com/Pylons/pyramid/pull/1574
Deprecations
------------
CONTRIBUTORS.txt
@@ -242,3 +242,5 @@
- Ilja Everila, 2015/02/05
- Geoffrey T. Dairiki, 2015/02/06
- David Glick, 2015/02/12
HACKING.txt
@@ -195,7 +195,7 @@
-------------
- The codebase *must* have 100% test statement coverage after each commit.
  You can test coverage via ``tox -e coverage``, or alternately by installing
  You can test coverage via ``tox -e cover``, or alternately by installing
  ``nose`` and ``coverage`` into your virtualenv (easiest via ``setup.py
  dev``) , and running ``setup.py nosetests --with-coverage``.
docs/api/config.rst
@@ -132,3 +132,8 @@
   are being used.
.. autoclass:: not_
.. attribute:: PHASE0_CONFIG
.. attribute:: PHASE1_CONFIG
.. attribute:: PHASE2_CONFIG
.. attribute:: PHASE3_CONFIG
docs/api/request.rst
@@ -369,3 +369,4 @@
   that used as ``request.GET``, ``request.POST``, and ``request.params``),
   see :class:`pyramid.interfaces.IMultiDict`.
.. autofunction:: apply_request_extensions(request)
docs/api/static.rst
@@ -9,6 +9,12 @@
     :members:
     :inherited-members:
  .. autoclass:: PathSegmentCacheBuster
     :members:
  .. autoclass:: QueryStringCacheBuster
     :members:
  .. autoclass:: PathSegmentMd5CacheBuster
     :members:
docs/narr/assets.rst
@@ -446,19 +446,20 @@
scratch which implements the :class:`~pyramid.interfaces.ICacheBuster`
interface.  Alternatively you may choose to subclass one of the existing
implementations.  One of the most likely scenarios is you'd want to change the
way the asset token is generated.  To do this just subclass an existing
implementation and replace the :meth:`~pyramid.interfaces.ICacheBuster.token`
method.  Here is an example which just uses Git to get the hash of the
currently checked out code:
way the asset token is generated.  To do this just subclass either
:class:`~pyramid.static.PathSegmentCacheBuster` or
:class:`~pyramid.static.QueryStringCacheBuster` and define a
``tokenize(pathspec)`` method. Here is an example which just uses Git to get
the hash of the currently checked out code:
.. code-block:: python
   :linenos:
   import os
   import subprocess
   from pyramid.static import PathSegmentMd5CacheBuster
   from pyramid.static import PathSegmentCacheBuster
   class GitCacheBuster(PathSegmentMd5CacheBuster):
   class GitCacheBuster(PathSegmentCacheBuster):
       """
       Assuming your code is installed as a Git checkout, as opposed to as an
       egg from an egg repository like PYPI, you can use this cachebuster to
@@ -470,7 +471,7 @@
               ['git', 'rev-parse', 'HEAD'],
               cwd=here).strip()
       def token(self, pathspec):
       def tokenize(self, pathspec):
           return self.sha1
   
Choosing a Cache Buster
docs/narr/extconfig.rst
@@ -215,13 +215,115 @@
passed to it, that a route by this name was already registered by
``add_route``, and if such a route has not already been registered, it's a
configuration error (a view that names a nonexistent route via its
``route_name`` parameter will never be called).
``route_name`` parameter will never be called). As of Pyramid 1.6 it is
possible for one action to invoke another. See :ref:`ordering_actions` for
more information.
``introspectables`` is a sequence of :term:`introspectable` objects.  You can
pass a sequence of introspectables to the
:meth:`~pyramid.config.Configurator.action` method, which allows you to
augment Pyramid's configuration introspection system.
.. _ordering_actions:
Ordering Actions
----------------
In Pyramid every :term:`action` has an inherent ordering relative to other
actions. The logic within actions is deferred until a call to
:meth:`pyramid.config.Configurator.commit` (which is automatically invoked by
:meth:`pyramid.config.Configurator.make_wsgi_app`). This means you may call
``config.add_view(route_name='foo')`` **before**
``config.add_route('foo', '/foo')`` because nothing actually happens until
commit-time. During a commit cycle conflicts are resolved, actions are ordered
and executed.
By default, almost every action in Pyramid has an ``order`` of
:const:`pyramid.config.PHASE3_CONFIG`. Every action within the same order-level
will be executed in the order it was called.
This means that if an action must be reliably executed before or after another
action, the ``order`` must be defined explicitly to make this work. For
example, views are dependent on routes being defined. Thus the action created
by :meth:`pyramid.config.Configurator.add_route` has an ``order`` of
:const:`pyramid.config.PHASE2_CONFIG`.
Pre-defined Phases
~~~~~~~~~~~~~~~~~~
:const:`pyramid.config.PHASE0_CONFIG`
- This phase is reserved for developers who want to execute actions prior
  to Pyramid's core directives.
:const:`pyramid.config.PHASE1_CONFIG`
- :meth:`pyramid.config.Configurator.add_renderer`
- :meth:`pyramid.config.Configurator.add_route_predicate`
- :meth:`pyramid.config.Configurator.add_subscriber_predicate`
- :meth:`pyramid.config.Configurator.add_view_predicate`
- :meth:`pyramid.config.Configurator.set_authorization_policy`
- :meth:`pyramid.config.Configurator.set_default_permission`
- :meth:`pyramid.config.Configurator.set_view_mapper`
:const:`pyramid.config.PHASE2_CONFIG`
- :meth:`pyramid.config.Configurator.add_route`
- :meth:`pyramid.config.Configurator.set_authentication_policy`
:const:`pyramid.config.PHASE3_CONFIG`
- The default for all builtin or custom directives unless otherwise specified.
Calling Actions From Actions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 1.6
Pyramid's configurator allows actions to be added during a commit-cycle as
long as they are added to the current or a later ``order`` phase. This means
that your custom action can defer decisions until commit-time and then do
things like invoke :meth:`pyramid.config.Configurator.add_route`. It can also
provide better conflict detection if your addon needs to call more than one
other action.
For example, let's make an addon that invokes ``add_route`` and ``add_view``,
but we want it to conflict with any other call to our addon:
.. code-block:: python
   :linenos:
   from pyramid.config import PHASE0_CONFIG
   def includeme(config):
       config.add_directive('add_auto_route', add_auto_route)
   def add_auto_route(config, name, view):
       def register():
           config.add_view(route_name=name, view=view)
           config.add_route(name, '/' + name)
       config.action(('auto route', name), register, order=PHASE0_CONFIG)
Now someone else can use your addon and be informed if there is a conflict
between this route and another, or two calls to ``add_auto_route``.
Notice how we had to invoke our action **before** ``add_view`` or
``add_route``. If we tried to invoke this afterward, the subsequent calls to
``add_view`` and ``add_route`` would cause conflicts because that phase had
already been executed, and the configurator cannot go back in time to add more
views during that commit-cycle.
.. code-block:: python
   :linenos:
   from pyramid.config import Configurator
   def main(global_config, **settings):
       config = Configurator()
       config.include('auto_route_addon')
       config.add_auto_route('foo', my_view)
   def my_view(request):
       return request.response
.. _introspection:
Adding Configuration Introspection
docs/narr/security.rst
@@ -341,9 +341,7 @@
A principal is usually a user id, however it also may be a group id if your
authentication system provides group information and the effective
:term:`authentication policy` policy is written to respect group information.
For example, the
:class:`pyramid.authentication.RepozeWho1AuthenticationPolicy` respects group
information if you configure it with a ``callback``.
See :ref:`extending_default_authentication_policies`.
Each ACE in an ACL is processed by an authorization policy *in the
order dictated by the ACL*.  So if you have an ACL like this:
@@ -583,6 +581,60 @@
:meth:`~pyramid.request.Request.has_permission` fails is often useful.
.. index::
   single: authentication policy (extending)
.. _extending_default_authentication_policies:
Extending Default Authentication Policies
-----------------------------------------
Pyramid ships with some builtin authentication policies for use in your
applications. See :mod:`pyramid.authentication` for the available
policies. They differ on their mechanisms for tracking authentication
credentials between requests, however they all interface with your
application in mostly the same way.
Above you learned about :ref:`assigning_acls`. Each :term:`principal` used
in the :term:`ACL` is matched against the list returned from
:meth:`pyramid.interfaces.IAuthenticationPolicy.effective_principals`.
Similarly, :meth:`pyramid.request.Request.authenticated_userid` maps to
:meth:`pyramid.interfaces.IAuthenticationPolicy.authenticated_userid`.
You may control these values by subclassing the default authentication
policies. For example, below we subclass the
:class:`pyramid.authentication.AuthTktAuthenticationPolicy` and define
extra functionality to query our database before confirming that the
:term:`userid` is valid in order to avoid blindly trusting the value in the
cookie (what if the cookie is still valid but the user has deleted their
account?). We then use that :term:`userid` to augment the
``effective_principals`` with information about groups and other state for
that user.
.. code-block:: python
   :linenos:
   from pyramid.authentication import AuthTktAuthenticationPolicy
   class MyAuthenticationPolicy(AuthTktAuthenticationPolicy):
       def authenticated_userid(self, request):
           userid = self.unauthenticated_userid(request)
           if userid:
               if request.verify_userid_is_still_valid(userid):
                   return userid
       def effective_principals(self, request):
           principals = [Everyone]
           userid = self.authenticated_userid(request)
           if userid:
               principals += [Authenticated, str(userid)]
           return principals
In most instances ``authenticated_userid`` and ``effective_principals`` are
application-specific whereas ``unauthenticated_userid``, ``remember`` and
``forget`` are generic and focused on transport/serialization of data
between consecutive requests.
.. index::
   single: authentication policy (creating)
.. _creating_an_authentication_policy:
@@ -653,7 +705,7 @@
           """
After you do so, you can pass an instance of such a class into the
:class:`~pyramid.config.Configurator.set_authentication_policy` method
:class:`~pyramid.config.Configurator.set_authentication_policy` method at
configuration time to use it.
.. index::
docs/narr/urldispatch.rst
@@ -495,17 +495,21 @@
    :linenos:
    config.add_route('idea', 'site/{id}')
    config.add_view('mypackage.views.site_view', route_name='idea')
    config.scan()
When a route configuration with a ``view`` attribute is added to the system,
and an incoming request matches the *pattern* of the route configuration, the
:term:`view callable` named as the ``view`` attribute of the route
configuration will be invoked.
In the case of the above example, when the URL of a request matches
``/site/{id}``, the view callable at the Python dotted path name
``mypackage.views.site_view`` will be called with the request.  In other
words, we've associated a view callable directly with a route pattern.
Recall that the ``@view_config`` is equivalent to calling ``config.add_view``,
because the ``config.scan()`` call will import ``mypackage.views``, shown
below, and execute ``config.add_view`` under the hood. Each view then maps the
route name to the matching view callable. In the case of the above
example, when the URL of a request matches ``/site/{id}``, the view callable at
the Python dotted path name ``mypackage.views.site_view`` will be called with
the request.  In other words, we've associated a view callable directly with a
route pattern.
When the ``/site/{id}`` route pattern matches during a request, the
``site_view`` view callable is invoked with that request as its sole
@@ -519,8 +523,10 @@
.. code-block:: python
   :linenos:
   from pyramid.view import view_config
   from pyramid.response import Response
   @view_config(route_name='idea')
   def site_view(request):
       return Response(request.matchdict['id'])
@@ -542,11 +548,30 @@
   config.add_route('idea', 'ideas/{idea}')
   config.add_route('user', 'users/{user}')
   config.add_route('tag', 'tags/{tag}')
   config.scan()
   config.add_view('mypackage.views.idea_view', route_name='idea')
   config.add_view('mypackage.views.user_view', route_name='user')
   config.add_view('mypackage.views.tag_view', route_name='tag')
Here is an example of a corresponding ``mypackage.views`` module:
.. code-block:: python
   :linenos:
   from pyramid.view import view_config
   from pyramid.response import Response
   @view_config(route_name='idea')
   def idea_view(request):
       return Response(request.matchdict['id'])
   @view_config(route_name='user')
   def user_view(request):
       user = request.matchdict['user']
       return Response(u'The user is {}.'.format(user))
   @view_config(route_name='tag')
   def tag_view(request):
       tag = request.matchdict['tag']
       return Response(u'The tag is {}.'.format(tag))
The above configuration will allow :app:`Pyramid` to service URLs in these
forms:
@@ -596,7 +621,7 @@
   :linenos:
   config.add_route('idea', 'ideas/{idea}', factory='myproject.resources.Idea')
   config.add_view('myproject.views.idea_view', route_name='idea')
   config.scan()
The above route will manufacture an ``Idea`` resource as a :term:`context`,
assuming that ``mypackage.resources.Idea`` resolves to a class that accepts a
@@ -610,7 +635,20 @@
           pass
In a more complicated application, this root factory might be a class
representing a :term:`SQLAlchemy` model.
representing a :term:`SQLAlchemy` model. The view ``mypackage.views.idea_view``
might look like this:
.. code-block:: python
   :linenos:
   @view_config(route_name='idea')
   def idea_view(request):
       idea = request.context
       return Response(idea)
Here, ``request.context`` is an instance of ``Idea``. If indeed the resource
object is a SQLAlchemy model, you do not even have to perform a query in the
view callable, since you have access to the resource via ``request.context``.
See :ref:`route_factories` for more details about how to use route factories.
pyramid/compat.py
@@ -23,7 +23,7 @@
# True if we are running on Python 3.
PY3 = sys.version_info[0] == 3
if PY3:  # pragma: no cover
if PY3:
    string_types = str,
    integer_types = int,
    class_types = type,
@@ -38,23 +38,21 @@
    binary_type = str
    long = long
def text_(s, encoding='latin-1', errors='strict'):
    """ If ``s`` is an instance of ``binary_type``, return
    ``s.decode(encoding, errors)``, otherwise return ``s``"""
    if isinstance(s, binary_type):
        return s.decode(encoding, errors)
    return s  # pragma: no cover
    return s
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):  # pragma: no cover
    if isinstance(s, text_type):
        return s.encode(encoding, errors)
    return s
if PY3:  # pragma: no cover
if PY3:
    def ascii_native_(s):
        if isinstance(s, text_type):
            s = s.encode('ascii')
@@ -74,7 +72,7 @@
"""
if PY3:  # pragma: no cover
if PY3:
    def native_(s, encoding='latin-1', errors='strict'):
        """ If ``s`` is an instance of ``text_type``, return
        ``s``, otherwise return ``str(s, encoding, errors)``"""
@@ -97,7 +95,7 @@
``s.encode(encoding, errors)``, otherwise return ``str(s)``
"""
if PY3:  # pragma: no cover
if PY3:
    from urllib import parse
    urlparse = parse
    from urllib.parse import quote as url_quote
@@ -174,13 +172,13 @@
        return d.iterkeys()
if PY3:  # pragma: no cover
if PY3:
    def map_(*arg):
        return list(map(*arg))
else:
    map_ = map
if PY3:  # pragma: no cover
if PY3:
    def is_nonstr_iter(v):
        if isinstance(v, str):
            return False
@@ -189,51 +187,48 @@
    def is_nonstr_iter(v):
        return hasattr(v, '__iter__')
if PY3:  # pragma: no cover
if PY3:
    im_func = '__func__'
    im_self = '__self__'
else:
    im_func = 'im_func'
    im_self = 'im_self'
try:  # pragma: no cover
try:
    import configparser
except ImportError:  # pragma: no cover
except ImportError:
    import ConfigParser as configparser
try:
    from Cookie import SimpleCookie
except ImportError:  # pragma: no cover
    from http.cookies import SimpleCookie
except ImportError:
    from Cookie import SimpleCookie
if PY3:  # pragma: no cover
if PY3:
    from html import escape
else:
    from cgi import escape
try:  # pragma: no cover
    input_ = raw_input
except NameError:  # pragma: no cover
if PY3:
    input_ = input
else:
    input_ = raw_input
# support annotations and keyword-only arguments in PY3
if PY3:  # pragma: no cover
if PY3:
    from inspect import getfullargspec as getargspec
else:
    from inspect import getargspec
try:
    from StringIO import StringIO as NativeIO
except ImportError:  # pragma: no cover
if PY3:
    from io import StringIO as NativeIO
else:
    from io import BytesIO as NativeIO
# "json" is not an API; it's here to support older pyramid_debugtoolbar
# versions which attempt to import it
import json
if PY3:  # pragma: no cover
if PY3:
    # see PEP 3333 for why we encode WSGI PATH_INFO to latin-1 before
    # decoding it to utf-8
    def decode_path_info(path):
@@ -242,8 +237,8 @@
    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
if PY3:
    # 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):
@@ -258,6 +253,16 @@
def is_bound_method(ob):
    return inspect.ismethod(ob) and getattr(ob, im_self, None) is not None
# support annotations and keyword-only arguments in PY3
if PY3: # pragma: no cover
    from inspect import getfullargspec as getargspec
else:
    from inspect import getargspec
if PY3: # pragma: no cover
    from itertools import zip_longest
else:
    from itertools import izip_longest as zip_longest
def is_unbound_method(fn):
    """
@@ -267,7 +272,7 @@
    is_bound = is_bound_method(fn)
    if not is_bound and inspect.isroutine(fn):
        spec = inspect.getargspec(fn)
        spec = getargspec(fn)
        has_self = len(spec.args) > 0 and spec.args[0] == 'self'
        if PY3 and inspect.isfunction(fn) and has_self:  # pragma: no cover
pyramid/config/__init__.py
@@ -12,7 +12,10 @@
    IDebugLogger,
    IExceptionResponse,
    IPredicateList,
    PHASE0_CONFIG,
    PHASE1_CONFIG,
    PHASE2_CONFIG,
    PHASE3_CONFIG,
    )
from pyramid.asset import resolve_asset_spec
@@ -23,6 +26,7 @@
    text_,
    reraise,
    string_types,
    zip_longest,
    )
from pyramid.events import ApplicationCreated
@@ -54,7 +58,9 @@
from pyramid.threadlocal import manager
from pyramid.util import (
    ActionInfo,
    WeakOrderedSet,
    action_method,
    object_description,
    )
@@ -74,11 +80,6 @@
from pyramid.path import DottedNameResolver
from pyramid.util import (
    action_method,
    ActionInfo,
    )
empty = text_('')
_marker = object()
@@ -86,6 +87,10 @@
not_ = not_ # pyflakes, this is an API
PHASE0_CONFIG = PHASE0_CONFIG  # api
PHASE1_CONFIG = PHASE1_CONFIG  # api
PHASE2_CONFIG = PHASE2_CONFIG  # api
PHASE3_CONFIG = PHASE3_CONFIG  # api
class Configurator(
    TestingConfiguratorMixin,
@@ -1072,10 +1077,82 @@
        >>> output
        [('f', (1,), {}), ('f', (2,), {})]
        """
        The execution is re-entrant such that actions may be added by other
        actions with the one caveat that the order of any added actions must
        be equal to or larger than the current action.
        >>> output = []
        >>> def f(*a, **k):
        ...   output.append(('f', a, k))
        ...   context.actions.append((3, g, (8,), {}))
        >>> def g(*a, **k):
        ...    output.append(('g', a, k))
        >>> context.actions = [
        ...   (1, f, (1,)),
        ...   ]
        >>> context.execute_actions()
        >>> output
        [('f', (1,), {}), ('g', (8,), {})]
        """
        try:
            for action in resolveConflicts(self.actions):
            all_actions = []
            executed_actions = []
            pending_actions = iter([])
            # resolve the new action list against what we have already
            # executed -- if a new action appears intertwined in the list
            # of already-executed actions then someone wrote a broken
            # re-entrant action because it scheduled the action *after* it
            # should have been executed (as defined by the action order)
            def resume(actions):
                for a, b in zip_longest(actions, executed_actions):
                    if b is None and a is not None:
                        # common case is that we are executing every action
                        yield a
                    elif b is not None and a != b:
                        raise ConfigurationError(
                            'During execution a re-entrant action was added '
                            'that modified the planned execution order in a '
                            'way that is incompatible with what has already '
                            'been executed.')
                    else:
                        # resolved action is in the same location as before,
                        # so we are in good shape, but the action is already
                        # executed so we skip it
                        assert b is not None and a == b
            while True:
                # We clear the actions list prior to execution so if there
                # are some new actions then we add them to the mix and resolve
                # conflicts again. This orders the new actions as well as
                # ensures that the previously executed actions have no new
                # conflicts.
                if self.actions:
                    # Only resolve the new actions against executed_actions
                    # and pending_actions instead of everything to avoid
                    # redundant checks.
                    # Assume ``actions = resolveConflicts([A, B, C])`` which
                    # after conflict checks, resulted in ``actions == [A]``
                    # then we know action A won out or a conflict would have
                    # been raised. Thus, when action D is added later, we only
                    # need to check the new action against A.
                    # ``actions = resolveConflicts([A, D]) should drop the
                    # number of redundant checks down from O(n^2) closer to
                    # O(n lg n).
                    all_actions.extend(self.actions)
                    pending_actions = resume(resolveConflicts(
                        executed_actions +
                        list(pending_actions) +
                        self.actions
                    ))
                    self.actions = []
                action = next(pending_actions, None)
                if action is None:
                    # we are done!
                    break
                callable = action['callable']
                args = action['args']
                kw = action['kw']
@@ -1101,10 +1178,14 @@
                if introspector is not None:
                    for introspectable in introspectables:
                        introspectable.register(introspector, info)
                executed_actions.append(action)
        finally:
            if clear:
                del self.actions[:]
            else:
                self.actions = all_actions
# this function is licensed under the ZPL (stolen from Zope)
def resolveConflicts(actions):
@@ -1197,18 +1278,20 @@
            for _, _, action in rest:
                includepath = action['includepath']
                # Test whether path is a prefix of opath
                if (includepath[:len(basepath)] != basepath # not a prefix
                    or includepath == basepath):
                if (includepath[:len(basepath)] != basepath or  # not a prefix
                        includepath == basepath):
                    L = conflicts.setdefault(discriminator, [baseinfo])
                    L.append(action['info'])
        if conflicts:
            raise ConfigurationConflictError(conflicts)
        # sort conflict-resolved actions by (order, i) and yield them one by one
        # sort conflict-resolved actions by (order, i) and yield them one
        # by one
        for a in [x[2] for x in sorted(output, key=operator.itemgetter(0, 1))]:
            yield a
def expand_action(discriminator, callable=None, args=(), kw=None,
                  includepath=(), info=None, order=0, introspectables=()):
    if kw is None:
@@ -1225,4 +1308,3 @@
        )
global_registries = WeakOrderedSet()
pyramid/config/assets.py
@@ -214,6 +214,10 @@
    """
    def __init__(self, package, prefix):
        self.package = package
        if hasattr(package, '__name__'):
            self.pkg_name = package.__name__
        else:
            self.pkg_name = package
        self.prefix = prefix
    def get_path(self, resource_name):
@@ -221,33 +225,33 @@
    def get_filename(self, resource_name):
        path = self.get_path(resource_name)
        if pkg_resources.resource_exists(self.package, path):
            return pkg_resources.resource_filename(self.package, path)
        if pkg_resources.resource_exists(self.pkg_name, path):
            return pkg_resources.resource_filename(self.pkg_name, path)
    def get_stream(self, resource_name):
        path = self.get_path(resource_name)
        if pkg_resources.resource_exists(self.package, path):
            return pkg_resources.resource_stream(self.package, path)
        if pkg_resources.resource_exists(self.pkg_name, path):
            return pkg_resources.resource_stream(self.pkg_name, path)
    def get_string(self, resource_name):
        path = self.get_path(resource_name)
        if pkg_resources.resource_exists(self.package, path):
            return pkg_resources.resource_string(self.package, path)
        if pkg_resources.resource_exists(self.pkg_name, path):
            return pkg_resources.resource_string(self.pkg_name, path)
    def exists(self, resource_name):
        path = self.get_path(resource_name)
        if pkg_resources.resource_exists(self.package, path):
        if pkg_resources.resource_exists(self.pkg_name, path):
            return True
    def isdir(self, resource_name):
        path = self.get_path(resource_name)
        if pkg_resources.resource_exists(self.package, path):
            return pkg_resources.resource_isdir(self.package, path)
        if pkg_resources.resource_exists(self.pkg_name, path):
            return pkg_resources.resource_isdir(self.pkg_name, path)
    def listdir(self, resource_name):
        path = self.get_path(resource_name)
        if pkg_resources.resource_exists(self.package, path):
            return pkg_resources.resource_listdir(self.package, path)
        if pkg_resources.resource_exists(self.pkg_name, path):
            return pkg_resources.resource_listdir(self.pkg_name, path)
class FSAssetSource(object):
pyramid/config/factories.py
@@ -14,8 +14,8 @@
from pyramid.util import (
    action_method,
    InstancePropertyMixin,
    get_callable_name,
    InstancePropertyHelper,
    )
@@ -174,7 +174,7 @@
        property = property or reify
        if property:
            name, callable = InstancePropertyMixin._make_property(
            name, callable = InstancePropertyHelper.make_property(
                callable, name=name, reify=reify)
        elif name is None:
            name = callable.__name__
pyramid/config/views.py
@@ -356,9 +356,8 @@
    def _rendered_view(self, view, view_renderer):
        def rendered_view(context, request):
            renderer = view_renderer
            result = view(context, request)
            if result.__class__ is Response: # potential common case
            if result.__class__ is Response:  # potential common case
                response = result
            else:
                registry = self.registry
@@ -374,6 +373,9 @@
                            name=renderer_name,
                            package=self.kw.get('package'),
                            registry=registry)
                    else:
                        renderer = view_renderer.clone()
                    if '__view__' in attrs:
                        view_inst = attrs.pop('__view__')
                    else:
@@ -386,9 +388,10 @@
    def _response_resolved_view(self, view):
        registry = self.registry
        def viewresult_to_response(context, request):
            result = view(context, request)
            if result.__class__ is Response: # common case
            if result.__class__ is Response:  # common case
                response = result
            else:
                response = registry.queryAdapterOrSelf(result, IResponse)
@@ -418,6 +421,7 @@
        if decorator is None:
            return view
        return decorator(view)
@implementer(IViewMapper)
@provider(IViewMapperFactory)
@@ -1992,9 +1996,9 @@
            cb = self._default_cachebust()
        if cb:
            def cachebust(subpath, kw):
                token = cb.token(spec + subpath)
                subpath_tuple = tuple(subpath.split('/'))
                subpath_tuple, kw = cb.pregenerate(token, subpath_tuple, kw)
                subpath_tuple, kw = cb.pregenerate(
                    spec + subpath, subpath_tuple, kw)
                return '/'.join(subpath_tuple), kw
        else:
            cachebust = None
pyramid/httpexceptions.py
@@ -1103,9 +1103,11 @@
status_map = {}
code = None
for name, value in list(globals().items()):
    if (isinstance(value, class_types) and
        issubclass(value, HTTPException)
        and not name.startswith('_')):
    if (
            isinstance(value, class_types) and
            issubclass(value, HTTPException) and
            not name.startswith('_')
    ):
        code = getattr(value, 'code', None)
        if code:
            status_map[code] = value
pyramid/i18n.py
@@ -332,9 +332,9 @@
        """Like ``ugettext()``, but look the message up in the specified
        domain.
        """
        if PY3: # pragma: no cover
        if PY3:
            return self._domains.get(domain, self).gettext(message)
        else: # pragma: no cover
        else:
            return self._domains.get(domain, self).ugettext(message)
    
    def dngettext(self, domain, singular, plural, num):
@@ -353,10 +353,10 @@
        """Like ``ungettext()`` but look the message up in the specified
        domain.
        """
        if PY3: # pragma: no cover
        if PY3:
            return self._domains.get(domain, self).ngettext(
                singular, plural, num)
        else: # pragma: no cover
        else:
            return self._domains.get(domain, self).ungettext(
                singular, plural, num)
pyramid/interfaces.py
@@ -382,6 +382,9 @@
    settings = Attribute('The deployment settings dictionary related '
                         'to the current application')
    def clone():
        """ Return a shallow copy that does not share any mutable state."""
class IRendererFactory(Interface):
    def __call__(info):
        """ Return an object that implements
@@ -591,8 +594,7 @@
class IRequestFactory(Interface):
    """ A utility which generates a request """
    def __call__(environ):
        """ Return an object implementing IRequest, e.g. an instance
        of ``pyramid.request.Request``"""
        """ Return an instance of ``pyramid.request.Request``"""
    def blank(path):
        """ Return an empty request object (see
@@ -1193,18 +1195,11 @@
    .. versionadded:: 1.6
    """
    def token(pathspec):
        """
        Computes and returns a token string used for cache busting.
        ``pathspec`` is the path specification for the resource to be cache
        busted.  """
    def pregenerate(token, subpath, kw):
    def pregenerate(pathspec, subpath, kw):
        """
        Modifies a subpath and/or keyword arguments from which a static asset
        URL will be computed during URL generation.  The ``token`` argument is
        a token string computed by
        :meth:`~pyramid.interfaces.ICacheBuster.token` for a particular asset.
        URL will be computed during URL generation.  The ``pathspec`` argument
        is the path specification for the resource to be cache busted.
        The ``subpath`` argument is a tuple of path elements that represent the
        portion of the asset URL which is used to find the asset.  The ``kw``
        argument is a dict of keywords that are to be passed eventually to
@@ -1236,6 +1231,7 @@
# with this phase will be executed earlier than those with later phase
# numbers.  The default phase number is 0, FTR.
PHASE0_CONFIG = -30
PHASE1_CONFIG = -20
PHASE2_CONFIG = -10
PHASE3_CONFIG = 0
pyramid/renderers.py
@@ -1,3 +1,4 @@
import contextlib
import json
import os
@@ -73,24 +74,16 @@
    helper = RendererHelper(name=renderer_name, package=package,
                            registry=registry)
    saved_response = None
    # save the current response, preventing the renderer from affecting it
    attrs = request.__dict__ if request is not None else {}
    if 'response' in attrs:
        saved_response = attrs['response']
        del attrs['response']
    result = helper.render(value, None, request=request)
    # restore the original response, overwriting any changes
    if saved_response is not None:
        attrs['response'] = saved_response
    elif 'response' in attrs:
        del attrs['response']
    with temporary_response(request):
        result = helper.render(value, None, request=request)
    return result
def render_to_response(renderer_name, value, request=None, package=None):
def render_to_response(renderer_name,
                       value,
                       request=None,
                       package=None,
                       response=None):
    """ Using the renderer ``renderer_name`` (a template
    or a static renderer), render the value (or set of values) using
    the result of the renderer's ``__call__`` method (usually a string
@@ -121,9 +114,16 @@
    Supply a ``request`` parameter in order to provide the renderer
    with the most correct 'system' values (``request`` and ``context``
    in particular). Keep in mind that if the ``request`` parameter is
    not passed in, any changes to ``request.response`` attributes made
    before calling this function will be ignored.
    in particular). Keep in mind that any changes made to ``request.response``
    prior to calling this function will not be reflected in the resulting
    response object. A new response object will be created for each call
    unless one is passed as the ``response`` argument.
    .. versionchanged:: 1.6
       In previous versions, any changes made to ``request.response`` outside
       of this function call would affect the returned response. This is no
       longer the case. If you wish to send in a pre-initialized response
       then you may pass one in the ``response`` argument.
    """
    try:
@@ -134,7 +134,33 @@
        package = caller_package()
    helper = RendererHelper(name=renderer_name, package=package,
                            registry=registry)
    return helper.render_to_response(value, None, request=request)
    with temporary_response(request):
        if response is not None:
            request.response = response
        result = helper.render_to_response(value, None, request=request)
    return result
@contextlib.contextmanager
def temporary_response(request):
    """
    Temporarily delete request.response and restore it afterward.
    """
    saved_response = None
    # save the current response, preventing the renderer from affecting it
    attrs = request.__dict__ if request is not None else {}
    if 'response' in attrs:
        saved_response = attrs['response']
        del attrs['response']
    yield
    # restore the original response, overwriting any changes
    if saved_response is not None:
        attrs['response'] = saved_response
    elif 'response' in attrs:
        del attrs['response']
def get_renderer(renderer_name, package=None):
    """ Return the renderer object for the renderer ``renderer_name``.
pyramid/request.py
@@ -8,6 +8,7 @@
from pyramid.interfaces import (
    IRequest,
    IRequestExtensions,
    IResponse,
    ISessionFactory,
    )
@@ -16,6 +17,7 @@
    text_,
    bytes_,
    native_,
    iteritems_,
    )
from pyramid.decorator import reify
@@ -26,7 +28,10 @@
    AuthorizationAPIMixin,
    )
from pyramid.url import URLMethodsMixin
from pyramid.util import InstancePropertyMixin
from pyramid.util import (
    InstancePropertyHelper,
    InstancePropertyMixin,
)
class TemplateContext(object):
    pass
@@ -308,3 +313,22 @@
    new_request.environ['PATH_INFO'] = new_path_info
    return new_request.get_response(app)
def apply_request_extensions(request, extensions=None):
    """Apply request extensions (methods and properties) to an instance of
    :class:`pyramid.interfaces.IRequest`. This method is dependent on the
    ``request`` containing a properly initialized registry.
    After invoking this method, the ``request`` should have the methods
    and properties that were defined using
    :meth:`pyramid.config.Configurator.add_request_method`.
    """
    if extensions is None:
        extensions = request.registry.queryUtility(IRequestExtensions)
    if extensions is not None:
        for name, fn in iteritems_(extensions.methods):
            method = fn.__get__(request, request.__class__)
            setattr(request, name, method)
        InstancePropertyHelper.apply_properties(
            request, extensions.descriptors)
pyramid/router.py
@@ -27,6 +27,7 @@
from pyramid.exceptions import PredicateMismatch
from pyramid.httpexceptions import HTTPNotFound
from pyramid.request import Request
from pyramid.request import apply_request_extensions
from pyramid.threadlocal import manager
from pyramid.traversal import (
@@ -213,7 +214,7 @@
            try:
                extensions = self.request_extensions
                if extensions is not None:
                    request._set_extensions(extensions)
                    apply_request_extensions(request, extensions=extensions)
                response = handle_request(request)
                if request.response_callbacks:
pyramid/scaffolds/copydir.py
@@ -186,10 +186,10 @@
        dest_content.splitlines(),
        src_content.splitlines(),
        dest_fn, src_fn))
    added = len([l for l in u_diff if l.startswith('+')
                 and not l.startswith('+++')])
    removed = len([l for l in u_diff if l.startswith('-')
                   and not l.startswith('---')])
    added = len([l for l in u_diff if l.startswith('+') and
                 not l.startswith('+++')])
    removed = len([l for l in u_diff if l.startswith('-') and
                   not l.startswith('---')])
    if added > removed:
        msg = '; %i lines added' % (added - removed)
    elif removed > added:
pyramid/scaffolds/tests.py
@@ -6,13 +6,13 @@
import time
try:
    import http.client as httplib
except ImportError:
    import httplib
except ImportError: # pragma: no cover
    import http.client as httplib # py3
class TemplateTest(object):
    def make_venv(self, directory): # pragma: no cover
    def make_venv(self, directory):  # pragma: no cover
        import virtualenv
        from virtualenv import Logger
        logger = Logger([(Logger.level_for_integer(2), sys.stdout)])
@@ -22,7 +22,7 @@
                                      clear=False,
                                      unzip_setuptools=True)
    def install(self, tmpl_name): # pragma: no cover
    def install(self, tmpl_name):  # pragma: no cover
        try:
            self.old_cwd = os.getcwd()
            self.directory = tempfile.mkdtemp()
pyramid/scripting.py
@@ -1,12 +1,12 @@
from pyramid.config import global_registries
from pyramid.exceptions import ConfigurationError
from pyramid.request import Request
from pyramid.interfaces import (
    IRequestExtensions,
    IRequestFactory,
    IRootFactory,
    )
from pyramid.request import Request
from pyramid.request import apply_request_extensions
from pyramid.threadlocal import manager as threadlocal_manager
from pyramid.traversal import DefaultRootFactory
@@ -77,9 +77,7 @@
    request.registry = registry 
    threadlocals = {'registry':registry, 'request':request}
    threadlocal_manager.push(threadlocals)
    extensions = registry.queryUtility(IRequestExtensions)
    if extensions is not None:
        request._set_extensions(extensions)
    apply_request_extensions(request)
    def closer():
        threadlocal_manager.pop()
    root_factory = registry.queryUtility(IRootFactory,
pyramid/scripts/pserve.py
@@ -36,6 +36,11 @@
MAXFD = 1024
try:
    import termios
except ImportError: # pragma: no cover
    termios = None
if WIN and not hasattr(os, 'kill'): # pragma: no cover
    # py 2.6 on windows
    def kill(pid, sig=None):
@@ -183,15 +188,17 @@
            print(msg)
    def get_options(self):
        if (len(self.args) > 1
            and self.args[1] in self.possible_subcommands):
        if (
                len(self.args) > 1 and
                self.args[1] in self.possible_subcommands
        ):
            restvars = self.args[2:]
        else:
            restvars = self.args[1:]
        return parse_vars(restvars)
    def run(self): # pragma: no cover
    def run(self):  # pragma: no cover
        if self.options.stop_daemon:
            return self.stop_daemon()
@@ -208,8 +215,10 @@
            return 2
        app_spec = self.args[0]
        if (len(self.args) > 1
            and self.args[1] in self.possible_subcommands):
        if (
                len(self.args) > 1 and
                self.args[1] in self.possible_subcommands
        ):
            cmd = self.args[1]
        else:
            cmd = None
@@ -294,8 +303,10 @@
                    self.out(str(ex))
                return 2
        if (self.options.monitor_restart
            and not os.environ.get(self._monitor_environ_key)):
        if (
                self.options.monitor_restart and
                not os.environ.get(self._monitor_environ_key)
        ):
            return self.restart_with_monitor()
        if self.options.pid_file:
@@ -345,7 +356,7 @@
            def open_browser():
                context = loadcontext(SERVER, app_spec, name=app_name, relative_to=base,
                        global_conf=vars)
                url = 'http://{host}:{port}/'.format(**context.config())
                url = 'http://127.0.0.1:{port}/'.format(**context.config())
                time.sleep(1)
                webbrowser.open(url)
            t = threading.Thread(target=open_browser)
@@ -712,15 +723,23 @@
        raise SystemExit
    signal.signal(signal.SIGTERM, handle_term)
def ensure_echo_on(): # pragma: no cover
    if termios:
        fd = sys.stdin
        if fd.isatty():
            attr_list = termios.tcgetattr(fd)
            if not attr_list[3] & termios.ECHO:
                attr_list[3] |= termios.ECHO
                termios.tcsetattr(fd, termios.TCSANOW, attr_list)
def install_reloader(poll_interval=1, extra_files=None): # pragma: no cover
    """
    Install the reloading monitor.
    On some platforms server threads may not terminate when the main
    thread does, causing ports to remain open/locked.  The
    ``raise_keyboard_interrupt`` option creates a unignorable signal
    which causes the whole application to shut-down (rudely).
    thread does, causing ports to remain open/locked.
    """
    ensure_echo_on()
    mon = Monitor(poll_interval=poll_interval)
    if extra_files is None:
        extra_files = []
pyramid/session.py
@@ -125,8 +125,8 @@
    .. versionadded:: 1.4a2
    """
    supplied_token = request.params.get(token, request.headers.get(header))
    if supplied_token != request.session.get_csrf_token():
    supplied_token = request.params.get(token, request.headers.get(header, ""))
    if strings_differ(request.session.get_csrf_token(), supplied_token):
        if raises:
            raise BadCSRFToken('check_csrf_token(): Invalid token')
        return False
pyramid/static.py
@@ -176,7 +176,7 @@
    def __init__(self):
        self.token_cache = {}
    def token(self, pathspec):
    def tokenize(self, pathspec):
        # An astute observer will notice that this use of token_cache doesn't
        # look particularly thread safe.  Basic read/write operations on Python
        # dicts, however, are atomic, so simply accessing and writing values
@@ -194,7 +194,25 @@
            self.token_cache[pathspec] = token = _generate_md5(pathspec)
        return token
class PathSegmentMd5CacheBuster(Md5AssetTokenGenerator):
class PathSegmentCacheBuster(object):
    """
    An implementation of :class:`~pyramid.interfaces.ICacheBuster` which
    inserts a token for cache busting in the path portion of an asset URL.
    To use this class, subclass it and provide a ``tokenize`` method which
    accepts a ``pathspec`` and returns a token.
    .. versionadded:: 1.6
    """
    def pregenerate(self, pathspec, subpath, kw):
        token = self.tokenize(pathspec)
        return (token,) + subpath, kw
    def match(self, subpath):
        return subpath[1:]
class PathSegmentMd5CacheBuster(PathSegmentCacheBuster,
                                Md5AssetTokenGenerator):
    """
    An implementation of :class:`~pyramid.interfaces.ICacheBuster` which
    inserts an md5 checksum token for cache busting in the path portion of an
@@ -203,13 +221,36 @@
    .. versionadded:: 1.6
    """
    def pregenerate(self, token, subpath, kw):
        return (token,) + subpath, kw
    def __init__(self):
        super(PathSegmentMd5CacheBuster, self).__init__()
    def match(self, subpath):
        return subpath[1:]
class QueryStringCacheBuster(object):
    """
    An implementation of :class:`~pyramid.interfaces.ICacheBuster` which adds
    a token for cache busting in the query string of an asset URL.
class QueryStringMd5CacheBuster(Md5AssetTokenGenerator):
    The optional ``param`` argument determines the name of the parameter added
    to the query string and defaults to ``'x'``.
    To use this class, subclass it and provide a ``tokenize`` method which
    accepts a ``pathspec`` and returns a token.
    .. versionadded:: 1.6
    """
    def __init__(self, param='x'):
        self.param = param
    def pregenerate(self, pathspec, subpath, kw):
        token = self.tokenize(pathspec)
        query = kw.setdefault('_query', {})
        if isinstance(query, dict):
            query[self.param] = token
        else:
            kw['_query'] = tuple(query) + ((self.param, token),)
        return subpath, kw
class QueryStringMd5CacheBuster(QueryStringCacheBuster,
                                Md5AssetTokenGenerator):
    """
    An implementation of :class:`~pyramid.interfaces.ICacheBuster` which adds
    an md5 checksum token for cache busting in the query string of an asset
@@ -222,18 +263,9 @@
    .. versionadded:: 1.6
    """
    def __init__(self, param='x'):
        super(QueryStringMd5CacheBuster, self).__init__()
        self.param = param
        super(QueryStringMd5CacheBuster, self).__init__(param=param)
    def pregenerate(self, token, subpath, kw):
        query = kw.setdefault('_query', {})
        if isinstance(query, dict):
            query[self.param] = token
        else:
            kw['_query'] = tuple(query) + ((self.param, token),)
        return subpath, kw
class QueryStringConstantCacheBuster(QueryStringMd5CacheBuster):
class QueryStringConstantCacheBuster(QueryStringCacheBuster):
    """
    An implementation of :class:`~pyramid.interfaces.ICacheBuster` which adds
    an arbitrary token for cache busting in the query string of an asset URL.
@@ -247,9 +279,8 @@
    .. versionadded:: 1.6
    """
    def __init__(self, token, param='x'):
        super(QueryStringConstantCacheBuster, self).__init__(param=param)
        self._token = token
        self.param = param
    def token(self, pathspec):
    def tokenize(self, pathspec):
        return self._token
pyramid/tests/test_config/pkgs/asset/models.py
File was deleted
pyramid/tests/test_config/pkgs/asset/views.py
File was deleted
pyramid/tests/test_config/test_adapters.py
@@ -219,7 +219,7 @@
    def test_add_response_adapter_dottednames(self):
        from pyramid.interfaces import IResponse
        config = self._makeOne(autocommit=True)
        if PY3: # pragma: no cover
        if PY3:
            str_name = 'builtins.str'
        else:
            str_name = '__builtin__.str'
pyramid/tests/test_config/test_assets.py
@@ -54,6 +54,12 @@
        self.assertEqual(source.package, subpackage)
        self.assertEqual(source.prefix, 'templates/bar.pt')
        resource_name = ''
        expected = os.path.join(here, 'pkgs', 'asset',
                                'subpackage', 'templates', 'bar.pt')
        self.assertEqual(override.source.get_filename(resource_name),
                         expected)
    def test_override_asset_package_with_package(self):
        from pyramid.config.assets import PackageAssetSource
        config = self._makeOne(autocommit=True)
@@ -70,6 +76,12 @@
        self.assertTrue(isinstance(source, PackageAssetSource))
        self.assertEqual(source.package, subpackage)
        self.assertEqual(source.prefix, '')
        resource_name = 'templates/bar.pt'
        expected = os.path.join(here, 'pkgs', 'asset',
                                'subpackage', 'templates', 'bar.pt')
        self.assertEqual(override.source.get_filename(resource_name),
                         expected)
    def test_override_asset_directory_with_directory(self):
        from pyramid.config.assets import PackageAssetSource
@@ -88,6 +100,12 @@
        self.assertEqual(source.package, subpackage)
        self.assertEqual(source.prefix, 'templates/')
        resource_name = 'bar.pt'
        expected = os.path.join(here, 'pkgs', 'asset',
                                'subpackage', 'templates', 'bar.pt')
        self.assertEqual(override.source.get_filename(resource_name),
                         expected)
    def test_override_asset_directory_with_package(self):
        from pyramid.config.assets import PackageAssetSource
        config = self._makeOne(autocommit=True)
@@ -105,6 +123,12 @@
        self.assertEqual(source.package, subpackage)
        self.assertEqual(source.prefix, '')
        resource_name = 'templates/bar.pt'
        expected = os.path.join(here, 'pkgs', 'asset',
                                'subpackage', 'templates', 'bar.pt')
        self.assertEqual(override.source.get_filename(resource_name),
                         expected)
    def test_override_asset_package_with_directory(self):
        from pyramid.config.assets import PackageAssetSource
        config = self._makeOne(autocommit=True)
@@ -121,6 +145,12 @@
        self.assertTrue(isinstance(source, PackageAssetSource))
        self.assertEqual(source.package, subpackage)
        self.assertEqual(source.prefix, 'templates/')
        resource_name = 'bar.pt'
        expected = os.path.join(here, 'pkgs', 'asset',
                                'subpackage', 'templates', 'bar.pt')
        self.assertEqual(override.source.get_filename(resource_name),
                         expected)
    def test_override_asset_directory_with_absfile(self):
        from pyramid.exceptions import ConfigurationError
@@ -161,6 +191,12 @@
        self.assertTrue(isinstance(source, FSAssetSource))
        self.assertEqual(source.prefix, abspath)
        resource_name = ''
        expected = os.path.join(here, 'pkgs', 'asset',
                                'subpackage', 'templates', 'bar.pt')
        self.assertEqual(override.source.get_filename(resource_name),
                         expected)
    def test_override_asset_directory_with_absdirectory(self):
        from pyramid.config.assets import FSAssetSource
        config = self._makeOne(autocommit=True)
@@ -177,6 +213,12 @@
        self.assertTrue(isinstance(source, FSAssetSource))
        self.assertEqual(source.prefix, abspath)
        resource_name = 'bar.pt'
        expected = os.path.join(here, 'pkgs', 'asset',
                                'subpackage', 'templates', 'bar.pt')
        self.assertEqual(override.source.get_filename(resource_name),
                         expected)
    def test_override_asset_package_with_absdirectory(self):
        from pyramid.config.assets import FSAssetSource
        config = self._makeOne(autocommit=True)
@@ -193,6 +235,12 @@
        self.assertTrue(isinstance(source, FSAssetSource))
        self.assertEqual(source.prefix, abspath)
        resource_name = 'bar.pt'
        expected = os.path.join(here, 'pkgs', 'asset',
                                'subpackage', 'templates', 'bar.pt')
        self.assertEqual(override.source.get_filename(resource_name),
                         expected)
    def test__override_not_yet_registered(self):
        from pyramid.interfaces import IPackageOverrides
        package = DummyPackage('package')
pyramid/tests/test_config/test_init.py
@@ -1515,6 +1515,73 @@
        self.assertRaises(ConfigurationExecutionError, c.execute_actions)
        self.assertEqual(output, [('f', (1,), {}), ('f', (2,), {})])
    def test_reentrant_action(self):
        output = []
        c = self._makeOne()
        def f(*a, **k):
            output.append(('f', a, k))
            c.actions.append((3, g, (8,), {}))
        def g(*a, **k):
            output.append(('g', a, k))
        c.actions = [
            (1, f, (1,)),
        ]
        c.execute_actions()
        self.assertEqual(output, [('f', (1,), {}), ('g', (8,), {})])
    def test_reentrant_action_error(self):
        from pyramid.exceptions import ConfigurationError
        c = self._makeOne()
        def f(*a, **k):
            c.actions.append((3, g, (8,), {}, (), None, -1))
        def g(*a, **k): pass
        c.actions = [
            (1, f, (1,)),
        ]
        self.assertRaises(ConfigurationError, c.execute_actions)
    def test_reentrant_action_without_clear(self):
        c = self._makeOne()
        def f(*a, **k):
            c.actions.append((3, g, (8,)))
        def g(*a, **k): pass
        c.actions = [
            (1, f, (1,)),
        ]
        c.execute_actions(clear=False)
        self.assertEqual(c.actions, [
            (1, f, (1,)),
            (3, g, (8,)),
        ])
class Test_reentrant_action_functional(unittest.TestCase):
    def _makeConfigurator(self, *arg, **kw):
        from pyramid.config import Configurator
        config = Configurator(*arg, **kw)
        return config
    def test_functional(self):
        def add_auto_route(config, name, view):
               def register():
                   config.add_view(route_name=name, view=view)
                   config.add_route(name, '/' + name)
               config.action(
                   ('auto route', name), register, order=-30
                   )
        config = self._makeConfigurator()
        config.add_directive('add_auto_route', add_auto_route)
        def my_view(request): return request.response
        config.add_auto_route('foo', my_view)
        config.commit()
        from pyramid.interfaces import IRoutesMapper
        mapper = config.registry.getUtility(IRoutesMapper)
        routes = mapper.get_routes()
        route = routes[0]
        self.assertEqual(len(routes), 1)
        self.assertEqual(route.name, 'foo')
        self.assertEqual(route.path, '/foo')
class Test_resolveConflicts(unittest.TestCase):
    def _callFUT(self, actions):
        from pyramid.config import resolveConflicts
pyramid/tests/test_config/test_views.py
@@ -2548,6 +2548,8 @@
                self.assertEqual(view_inst, view)
                self.assertEqual(ctx, context)
                return response
            def clone(self):
                return self
        def view(request):
            return 'OK'
        deriver = self._makeOne(renderer=moo())
@@ -2585,6 +2587,8 @@
                self.assertEqual(view_inst, 'view')
                self.assertEqual(ctx, context)
                return response
            def clone(self):
                return self
        def view(request):
            return 'OK'
        deriver = self._makeOne(renderer=moo())
@@ -3179,6 +3183,8 @@
                self.assertEqual(view_inst.__class__, View)
                self.assertEqual(ctx, context)
                return response
            def clone(self):
                return self
        class View(object):
            def __init__(self, context, request):
                pass
@@ -3203,6 +3209,8 @@
                self.assertEqual(view_inst.__class__, View)
                self.assertEqual(ctx, context)
                return response
            def clone(self):
                return self
        class View(object):
            def __init__(self, request):
                pass
@@ -3227,6 +3235,8 @@
                self.assertEqual(view_inst.__class__, View)
                self.assertEqual(ctx, context)
                return response
            def clone(self):
                return self
        class View:
            def __init__(self, context, request):
                pass
@@ -3251,6 +3261,8 @@
                self.assertEqual(view_inst.__class__, View)
                self.assertEqual(ctx, context)
                return response
            def clone(self):
                return self
        class View:
            def __init__(self, request):
                pass
@@ -3275,6 +3287,8 @@
                self.assertEqual(view_inst, view)
                self.assertEqual(ctx, context)
                return response
            def clone(self):
                return self
        class View:
            def index(self, context, request):
                return {'a':'1'}
@@ -3297,6 +3311,8 @@
                self.assertEqual(view_inst, view)
                self.assertEqual(ctx, context)
                return response
            def clone(self):
                return self
        class View:
            def index(self, request):
                return {'a':'1'}
@@ -3995,7 +4011,7 @@
    def test_add_cachebust_default(self):
        config = self._makeConfig()
        inst = self._makeOne()
        inst._default_cachebust = DummyCacheBuster
        inst._default_cachebust = lambda: DummyCacheBuster('foo')
        inst.add(config, 'view', 'mypackage:path', cachebust=True)
        cachebust = config.registry._static_url_registrations[0][3]
        subpath, kw = cachebust('some/path', {})
@@ -4014,7 +4030,7 @@
        config = self._makeConfig()
        inst = self._makeOne()
        inst.add(config, 'view', 'mypackage:path',
                 cachebust=DummyCacheBuster())
                 cachebust=DummyCacheBuster('foo'))
        cachebust = config.registry._static_url_registrations[0][3]
        subpath, kw = cachebust('some/path', {})
        self.assertEqual(subpath, 'some/path')
@@ -4127,10 +4143,10 @@
        """ """
class DummyCacheBuster(object):
    def token(self, pathspec):
        return 'foo'
    def pregenerate(self, token, subpath, kw):
        kw['x'] = token
    def __init__(self, token):
        self.token = token
    def pregenerate(self, pathspec, subpath, kw):
        kw['x'] = self.token
        return subpath, kw
def parse_httpdate(s):
pyramid/tests/test_path.py
@@ -376,7 +376,7 @@
    def test_zope_dottedname_style_resolve_builtin(self):
        typ = self._makeOne()
        if PY3: # pragma: no cover
        if PY3:
            result = typ._zope_dottedname_style('builtins.str', None)
        else:
            result = typ._zope_dottedname_style('__builtin__.str', None)
pyramid/tests/test_renderers.py
@@ -517,10 +517,11 @@
    def tearDown(self):
        testing.tearDown()
    def _callFUT(self, renderer_name, value, request=None, package=None):
    def _callFUT(self, renderer_name, value, request=None, package=None,
                 response=None):
        from pyramid.renderers import render_to_response
        return render_to_response(renderer_name, value, request=request,
                                  package=package)
                                  package=package, response=response)
    def test_it_no_request(self):
        renderer = self.config.testing_add_renderer(
@@ -553,6 +554,43 @@
        self.assertEqual(response.body, b'abc')
        renderer.assert_(a=1)
        renderer.assert_(request=request)
    def test_response_preserved(self):
        request = testing.DummyRequest()
        response = object() # should error if mutated
        request.response = response
        # use a json renderer, which will mutate the response
        result = self._callFUT('json', dict(a=1), request=request)
        self.assertEqual(result.body, b'{"a": 1}')
        self.assertNotEqual(request.response, result)
        self.assertEqual(request.response, response)
    def test_no_response_to_preserve(self):
        from pyramid.decorator import reify
        class DummyRequestWithClassResponse(object):
            _response = DummyResponse()
            _response.content_type = None
            _response.default_content_type = None
            @reify
            def response(self):
                return self._response
        request = DummyRequestWithClassResponse()
        # use a json renderer, which will mutate the response
        result = self._callFUT('json', dict(a=1), request=request)
        self.assertEqual(result.body, b'{"a": 1}')
        self.assertFalse('response' in request.__dict__)
    def test_custom_response_object(self):
        class DummyRequestWithClassResponse(object):
            pass
        request = DummyRequestWithClassResponse()
        response = DummyResponse()
        # use a json renderer, which will mutate the response
        result = self._callFUT('json', dict(a=1), request=request,
                               response=response)
        self.assertTrue(result is response)
        self.assertEqual(result.body, b'{"a": 1}')
        self.assertFalse('response' in request.__dict__)
class Test_get_renderer(unittest.TestCase):
    def setUp(self):
@@ -614,7 +652,14 @@
class DummyResponse:
    status = '200 OK'
    default_content_type = 'text/html'
    content_type = default_content_type
    headerlist = ()
    app_iter = ()
    body = ''
    body = b''
    # compat for renderer that will set unicode on py3
    def _set_text(self, val): # pragma: no cover
        self.body = val.encode('utf8')
    text = property(fset=_set_text)
pyramid/tests/test_request.py
@@ -310,7 +310,7 @@
            b'/\xe6\xb5\x81\xe8\xa1\x8c\xe8\xb6\x8b\xe5\x8a\xbf',
            'utf-8'
            )
        if PY3: # pragma: no cover
        if PY3:
            body = bytes(json.dumps({'a':inp}), 'utf-16')
        else:
            body = json.dumps({'a':inp}).decode('utf-8').encode('utf-16')
@@ -435,7 +435,50 @@
        self.assertEqual(request.environ['SCRIPT_NAME'], '/' + encoded)
        self.assertEqual(request.environ['PATH_INFO'], '/' + encoded)
class DummyRequest:
class Test_apply_request_extensions(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def _callFUT(self, request, extensions=None):
        from pyramid.request import apply_request_extensions
        return apply_request_extensions(request, extensions=extensions)
    def test_it_with_registry(self):
        from pyramid.interfaces import IRequestExtensions
        extensions = Dummy()
        extensions.methods = {'foo': lambda x, y: y}
        extensions.descriptors = {'bar': property(lambda x: 'bar')}
        self.config.registry.registerUtility(extensions, IRequestExtensions)
        request = DummyRequest()
        request.registry = self.config.registry
        self._callFUT(request)
        self.assertEqual(request.bar, 'bar')
        self.assertEqual(request.foo('abc'), 'abc')
    def test_it_override_extensions(self):
        from pyramid.interfaces import IRequestExtensions
        ignore = Dummy()
        ignore.methods = {'x': lambda x, y, z: 'asdf'}
        ignore.descriptors = {'bar': property(lambda x: 'asdf')}
        self.config.registry.registerUtility(ignore, IRequestExtensions)
        request = DummyRequest()
        request.registry = self.config.registry
        extensions = Dummy()
        extensions.methods = {'foo': lambda x, y: y}
        extensions.descriptors = {'bar': property(lambda x: 'bar')}
        self._callFUT(request, extensions=extensions)
        self.assertRaises(AttributeError, lambda: request.x)
        self.assertEqual(request.bar, 'bar')
        self.assertEqual(request.foo('abc'), 'abc')
class Dummy(object):
    pass
class DummyRequest(object):
    def __init__(self, environ=None):
        if environ is None:
            environ = {}
pyramid/tests/test_router.py
@@ -317,6 +317,7 @@
        from pyramid.interfaces import IRequestExtensions
        from pyramid.interfaces import IRequest
        from pyramid.request import Request
        from pyramid.util import InstancePropertyHelper
        context = DummyContext()
        self._registerTraverserFactory(context)
        class Extensions(object):
@@ -324,11 +325,12 @@
                self.methods = {}
                self.descriptors = {}
        extensions = Extensions()
        L = []
        ext_method = lambda r: 'bar'
        name, fn = InstancePropertyHelper.make_property(ext_method, name='foo')
        extensions.descriptors[name] = fn
        request = Request.blank('/')
        request.request_iface = IRequest
        request.registry = self.registry
        request._set_extensions = lambda *x: L.extend(x)
        def request_factory(environ):
            return request
        self.registry.registerUtility(extensions, IRequestExtensions)
@@ -342,7 +344,7 @@
        router.request_factory = request_factory
        start_response = DummyStartResponse()
        router(environ, start_response)
        self.assertEqual(L, [extensions])
        self.assertEqual(view.request.foo, 'bar')
    def test_call_view_registered_nonspecific_default_path(self):
        from pyramid.interfaces import IViewClassifier
pyramid/tests/test_scripting.py
@@ -122,11 +122,15 @@
        self.assertEqual(request.context, context)
    def test_it_with_extensions(self):
        exts = Dummy()
        from pyramid.util import InstancePropertyHelper
        exts = DummyExtensions()
        ext_method = lambda r: 'bar'
        name, fn = InstancePropertyHelper.make_property(ext_method, 'foo')
        exts.descriptors[name] = fn
        request = DummyRequest({})
        registry = request.registry = self._makeRegistry([exts, DummyFactory])
        info = self._callFUT(request=request, registry=registry)
        self.assertEqual(request.extensions, exts)
        self.assertEqual(request.foo, 'bar')
        root, closer = info['root'], info['closer']
        closer()
@@ -199,11 +203,13 @@
    def pop(self):
        self.popped.append(True)
        
class DummyRequest:
class DummyRequest(object):
    matchdict = None
    matched_route = None
    def __init__(self, environ):
        self.environ = environ
    def _set_extensions(self, exts):
        self.extensions = exts
class DummyExtensions:
    def __init__(self):
        self.descriptors = {}
        self.methods = {}
pyramid/tests/test_scripts/pystartup.py
File was deleted
pyramid/tests/test_scripts/pystartup.txt
New file
@@ -0,0 +1,3 @@
# this file has a .txt extension to avoid coverage reports
# since it is not imported but rather the contents are read and exec'd
foo = 1
pyramid/tests/test_scripts/test_pserve.py
@@ -4,7 +4,7 @@
import unittest
from pyramid.compat import PY3
if PY3: # pragma: no cover
if PY3:
    import builtins as __builtin__
else:
    import __builtin__
pyramid/tests/test_scripts/test_pshell.py
@@ -379,7 +379,7 @@
            os.path.abspath(
                os.path.join(
                    os.path.dirname(__file__),
                    'pystartup.py')))
                    'pystartup.txt')))
        shell = dummy.DummyShell()
        command.run(shell)
        self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp')
pyramid/tests/test_static.py
@@ -393,13 +393,13 @@
        return cls()
    def test_package_resource(self):
        fut = self._makeOne().token
        fut = self._makeOne().tokenize
        expected = '76d653a3a044e2f4b38bb001d283e3d9'
        token = fut('pyramid.tests:fixtures/static/index.html')
        self.assertEqual(token, expected)
    def test_filesystem_resource(self):
        fut = self._makeOne().token
        fut = self._makeOne().tokenize
        expected = 'd5155f250bef0e9923e894dbc713c5dd'
        with open(self.fspath, 'w') as f:
            f.write("Are we rich yet?")
@@ -407,7 +407,7 @@
        self.assertEqual(token, expected)
    def test_cache(self):
        fut = self._makeOne().token
        fut = self._makeOne().tokenize
        expected = 'd5155f250bef0e9923e894dbc713c5dd'
        with open(self.fspath, 'w') as f:
            f.write("Are we rich yet?")
@@ -425,11 +425,11 @@
    def _makeOne(self):
        from pyramid.static import PathSegmentMd5CacheBuster as cls
        inst = cls()
        inst.token = lambda pathspec: 'foo'
        inst.tokenize = lambda pathspec: 'foo'
        return inst
    def test_token(self):
        fut = self._makeOne().token
        fut = self._makeOne().tokenize
        self.assertEqual(fut('whatever'), 'foo')
    def test_pregenerate(self):
@@ -448,11 +448,11 @@
            inst = cls(param)
        else:
            inst = cls()
        inst.token = lambda pathspec: 'foo'
        inst.tokenize = lambda pathspec: 'foo'
        return inst
    def test_token(self):
        fut = self._makeOne().token
        fut = self._makeOne().tokenize
        self.assertEqual(fut('whatever'), 'foo')
    def test_pregenerate(self):
@@ -490,7 +490,7 @@
        return inst
    def test_token(self):
        fut = self._makeOne().token
        fut = self._makeOne().tokenize
        self.assertEqual(fut('whatever'), 'foo')
    def test_pregenerate(self):
pyramid/tests/test_traversal.py
@@ -335,7 +335,7 @@
        foo = DummyContext(bar, path)
        root = DummyContext(foo, 'root')
        policy = self._makeOne(root)
        if PY3: # pragma: no cover
        if PY3:
            vhm_root = b'/Qu\xc3\xa9bec'.decode('latin-1')
        else:
            vhm_root = b'/Qu\xc3\xa9bec'
pyramid/tests/test_urldispatch.py
@@ -120,7 +120,7 @@
    def test___call__pathinfo_cant_be_decoded(self):
        from pyramid.exceptions import URLDecodeError
        mapper = self._makeOne()
        if PY3: # pragma: no cover
        if PY3:
            path_info = b'\xff\xfe\xe6\x00'.decode('latin-1')
        else:
            path_info = b'\xff\xfe\xe6\x00'
pyramid/tests/test_util.py
@@ -2,6 +2,188 @@
from pyramid.compat import PY3
class Test_InstancePropertyHelper(unittest.TestCase):
    def _makeOne(self):
        cls = self._getTargetClass()
        return cls()
    def _getTargetClass(self):
        from pyramid.util import InstancePropertyHelper
        return InstancePropertyHelper
    def test_callable(self):
        def worker(obj):
            return obj.bar
        foo = Dummy()
        helper = self._getTargetClass()
        helper.set_property(foo, 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 = Dummy()
        helper = self._getTargetClass()
        helper.set_property(foo, 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 = Dummy()
        helper = self._getTargetClass()
        helper.set_property(foo, 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 = Dummy()
        helper = self._getTargetClass()
        helper.set_property(foo, worker, name='x')
        helper.set_property(foo, 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 = Dummy()
        helper = self._getTargetClass()
        self.assertRaises(ValueError, helper.set_property, foo, property(worker))
    def test_property_with_name(self):
        def worker(obj):
            return obj.bar
        foo = Dummy()
        helper = self._getTargetClass()
        helper.set_property(foo, 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 = Dummy()
        helper = self._getTargetClass()
        self.assertRaises(ValueError, helper.set_property,
                          foo, property(worker), name='x', reify=True)
    def test_override_property(self):
        def worker(obj): pass
        foo = Dummy()
        helper = self._getTargetClass()
        helper.set_property(foo, worker, name='x')
        def doit():
            foo.x = 1
        self.assertRaises(AttributeError, doit)
    def test_override_reify(self):
        def worker(obj): pass
        foo = Dummy()
        helper = self._getTargetClass()
        helper.set_property(foo, 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 = Dummy()
        helper = self._getTargetClass()
        helper.set_property(foo, lambda _: 1, name='x')
        self.assertEqual(1, foo.x)
        helper.set_property(foo, 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 = Dummy()
        helper = self._getTargetClass()
        helper.set_property(foo, lambda _: 1, name='x', reify=True)
        self.assertEqual(1, foo.x)
        helper.set_property(foo, lambda _: 2, name='x', reify=True)
        self.assertEqual(1, foo.x)
    def test_make_property(self):
        from pyramid.decorator import reify
        helper = self._getTargetClass()
        name, fn = helper.make_property(lambda x: 1, name='x', reify=True)
        self.assertEqual(name, 'x')
        self.assertTrue(isinstance(fn, reify))
    def test_apply_properties_with_iterable(self):
        foo = Dummy()
        helper = self._getTargetClass()
        x = helper.make_property(lambda _: 1, name='x', reify=True)
        y = helper.make_property(lambda _: 2, name='y')
        helper.apply_properties(foo, [x, y])
        self.assertEqual(1, foo.x)
        self.assertEqual(2, foo.y)
    def test_apply_properties_with_dict(self):
        foo = Dummy()
        helper = self._getTargetClass()
        x_name, x_fn = helper.make_property(lambda _: 1, name='x', reify=True)
        y_name, y_fn = helper.make_property(lambda _: 2, name='y')
        helper.apply_properties(foo, {x_name: x_fn, y_name: y_fn})
        self.assertEqual(1, foo.x)
        self.assertEqual(2, foo.y)
    def test_make_property_unicode(self):
        from pyramid.compat import text_
        from pyramid.exceptions import ConfigurationError
        cls = self._getTargetClass()
        if PY3:  # pragma: nocover
            name = b'La Pe\xc3\xb1a'
        else:  # pragma: nocover
            name = text_(b'La Pe\xc3\xb1a', 'utf-8')
        def make_bad_name():
            cls.make_property(lambda x: 1, name=name, reify=True)
        self.assertRaises(ConfigurationError, make_bad_name)
    def test_add_property(self):
        helper = self._makeOne()
        helper.add_property(lambda obj: obj.bar, name='x', reify=True)
        helper.add_property(lambda obj: obj.bar, name='y')
        self.assertEqual(len(helper.properties), 2)
        foo = Dummy()
        helper.apply(foo)
        foo.bar = 1
        self.assertEqual(foo.x, 1)
        self.assertEqual(foo.y, 1)
        foo.bar = 2
        self.assertEqual(foo.x, 1)
        self.assertEqual(foo.y, 2)
    def test_apply_multiple_times(self):
        helper = self._makeOne()
        helper.add_property(lambda obj: 1, name='x')
        foo, bar = Dummy(), Dummy()
        helper.apply(foo)
        self.assertEqual(foo.x, 1)
        helper.add_property(lambda obj: 2, name='x')
        helper.apply(bar)
        self.assertEqual(foo.x, 1)
        self.assertEqual(bar.x, 2)
class Test_InstancePropertyMixin(unittest.TestCase):
    def _makeOne(self):
        cls = self._getTargetClass()
@@ -110,58 +292,6 @@
        self.assertEqual(1, foo.x)
        foo.set_property(lambda _: 2, name='x', reify=True)
        self.assertEqual(1, foo.x)
    def test__make_property(self):
        from pyramid.decorator import reify
        cls = self._getTargetClass()
        name, fn = cls._make_property(lambda x: 1, name='x', reify=True)
        self.assertEqual(name, 'x')
        self.assertTrue(isinstance(fn, reify))
    def test__set_properties_with_iterable(self):
        foo = self._makeOne()
        x = foo._make_property(lambda _: 1, name='x', reify=True)
        y = foo._make_property(lambda _: 2, name='y')
        foo._set_properties([x, y])
        self.assertEqual(1, foo.x)
        self.assertEqual(2, foo.y)
    def test__make_property_unicode(self):
        from pyramid.compat import text_
        from pyramid.exceptions import ConfigurationError
        cls = self._getTargetClass()
        if PY3:  # pragma: nocover
            name = b'La Pe\xc3\xb1a'
        else:  # pragma: nocover
            name = text_(b'La Pe\xc3\xb1a', 'utf-8')
        def make_bad_name():
            cls._make_property(lambda x: 1, name=name, reify=True)
        self.assertRaises(ConfigurationError, make_bad_name)
    def test__set_properties_with_dict(self):
        foo = self._makeOne()
        x_name, x_fn = foo._make_property(lambda _: 1, name='x', reify=True)
        y_name, y_fn = foo._make_property(lambda _: 2, name='y')
        foo._set_properties({x_name: x_fn, y_name: y_fn})
        self.assertEqual(1, foo.x)
        self.assertEqual(2, foo.y)
    def test__set_extensions(self):
        inst = self._makeOne()
        def foo(self, result):
            return result
        n, bar = inst._make_property(lambda _: 'bar', name='bar')
        class Extensions(object):
            def __init__(self):
                self.methods = {'foo':foo}
                self.descriptors = {'bar':bar}
        extensions = Extensions()
        inst._set_extensions(extensions)
        self.assertEqual(inst.bar, 'bar')
        self.assertEqual(inst.foo('abc'), 'abc')
class Test_WeakOrderedSet(unittest.TestCase):
    def _makeOne(self):
@@ -301,9 +431,9 @@
        self.assertEqual(self._callFUT(('a', 'b')), "('a', 'b')")
    def test_set(self):
        if PY3: # pragma: no cover
        if PY3:
            self.assertEqual(self._callFUT(set(['a'])), "{'a'}")
        else: # pragma: no cover
        else:
            self.assertEqual(self._callFUT(set(['a'])), "set(['a'])")
    def test_list(self):
@@ -646,7 +776,7 @@
        else:  # pragma: nocover
            name = text_(b'hello world', 'utf-8')
        self.assertEquals(get_callable_name(name), 'hello world')
        self.assertEqual(get_callable_name(name), 'hello world')
    def test_invalid_ascii(self):
        from pyramid.util import get_callable_name
pyramid/traversal.py
@@ -575,7 +575,7 @@
"""
if PY3: # pragma: no cover
if PY3:
    # special-case on Python 2 for speed?  unchecked
    def quote_path_segment(segment, safe=''):
        """ %s """ % quote_path_segment_doc
pyramid/urldispatch.py
@@ -210,7 +210,7 @@
    def generator(dict):
        newdict = {}
        for k, v in dict.items():
            if PY3: # pragma: no cover
            if PY3:
                if v.__class__ is binary_type:
                    # url_quote below needs a native string, not bytes on Py3
                    v = v.decode('utf-8')
pyramid/util.py
@@ -16,7 +16,6 @@
    )
from pyramid.compat import (
    iteritems_,
    is_nonstr_iter,
    integer_types,
    string_types,
@@ -28,20 +27,29 @@
from pyramid.interfaces import IActionInfo
from pyramid.path import DottedNameResolver as _DottedNameResolver
class DottedNameResolver(_DottedNameResolver):
    def __init__(self, package=None): # default to package = None for bw compat
        return _DottedNameResolver.__init__(self, package)
_marker = object()
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.
class InstancePropertyHelper(object):
    """A helper object for assigning properties and descriptors to instances.
    It is not normally possible to do this because descriptors must be
    defined on the class itself.
    This class is optimized for adding multiple properties at once to an
    instance. This is done by calling :meth:`.add_property` once
    per-property and then invoking :meth:`.apply` on target objects.
    """
    def __init__(self):
        self.properties = {}
    @classmethod
    def _make_property(cls, callable, name=None, reify=False):
    def make_property(cls, callable, name=None, reify=False):
        """ Convert a callable into one suitable for adding to the
        instance. This will return a 2-tuple containing the computed
        (name, property) pair.
@@ -69,25 +77,15 @@
        return name, fn
    def _set_properties(self, properties):
        """ Create several properties on the instance at once.
        This is a more efficient version of
        :meth:`pyramid.util.InstancePropertyMixin.set_property` which
        can accept multiple ``(name, property)`` pairs generated via
        :meth:`pyramid.util.InstancePropertyMixin._make_property`.
        ``properties`` is a sequence of two-tuples *or* a data structure
        with an ``.items()`` method which returns a sequence of two-tuples
        (presumably a dictionary). It will be used to add several
        properties to the instance in a manner that is more efficient
        than simply calling ``set_property`` repeatedly.
    @classmethod
    def apply_properties(cls, target, properties):
        """Accept a list or dict of ``properties`` generated from
        :meth:`.make_property` and apply them to a ``target`` object.
        """
        attrs = dict(properties)
        if attrs:
            parent = self.__class__
            cls = type(parent.__name__, (parent, object), attrs)
            parent = target.__class__
            newcls = type(parent.__name__, (parent, object), attrs)
            # We assign __provides__, __implemented__ and __providedBy__ below
            # to prevent a memory leak that results from from the usage of this
            # instance's eventual use in an adapter lookup.  Adapter lookup
@@ -106,15 +104,34 @@
                # attached to it
                val = getattr(parent, name, _marker)
                if val is not _marker:
                    setattr(cls, name, val)
            self.__class__ = cls
                    setattr(newcls, name, val)
            target.__class__ = newcls
    def _set_extensions(self, extensions):
        for name, fn in iteritems_(extensions.methods):
            method = fn.__get__(self, self.__class__)
            setattr(self, name, method)
    @classmethod
    def set_property(cls, target, callable, name=None, reify=False):
        """A helper method to apply a single property to an instance."""
        prop = cls.make_property(callable, name=name, reify=reify)
        cls.apply_properties(target, [prop])
        self._set_properties(extensions.descriptors)
    def add_property(self, callable, name=None, reify=False):
        """Add a new property configuration.
        This should be used in combination with :meth:`.apply` as a
        more efficient version of :meth:`.set_property`.
        """
        name, fn = self.make_property(callable, name=name, reify=reify)
        self.properties[name] = fn
    def apply(self, target):
        """ Apply all configured properties to the ``target`` instance."""
        if self.properties:
            self.apply_properties(target, self.properties)
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, callable, name=None, reify=False):
        """ Add a callable or a property descriptor to the instance.
@@ -168,8 +185,8 @@
           >>> foo.y # notice y keeps the original value
           1
        """
        prop = self._make_property(callable, name=name, reify=reify)
        self._set_properties([prop])
        InstancePropertyHelper.set_property(
            self, callable, name=name, reify=reify)
class WeakOrderedSet(object):
    """ Maintain a set of items.
@@ -293,7 +310,7 @@
    if isinstance(object, (bool, float, type(None))):
        return text_(str(object))
    if isinstance(object, set):
        if PY3: # pragma: no cover
        if PY3:
            return shortrepr(object, '}')
        else:
            return shortrepr(object, ')')
setup.cfg
@@ -5,9 +5,6 @@
match=^test
where=pyramid
nocapture=1
cover-package=pyramid
cover-erase=1
cover-min-percentage=100
[aliases]
dev = develop easy_install pyramid[testing]
tox.ini
@@ -1,22 +1,29 @@
[tox]
envlist =
    py26,py27,py32,py33,py34,pypy,pypy3,cover
envlist =
    py26,py27,py32,py33,py34,pypy,pypy3,pep8,
    {py2,py3}-docs,
    {py2,py3}-cover,coverage
[testenv]
commands =
    python setup.py -q dev
    python setup.py -q test -q
[testenv:cover]
# Most of these are defaults but if you specify any you can't fall back
# to defaults for others.
basepython =
    python2.6
commands =
    python setup.py -q dev
    nosetests --with-xunit --with-xcoverage
deps =
    nosexcover
    py26: python2.6
    py27: python2.7
    py32: python3.2
    py33: python3.3
    py34: python3.4
    pypy: pypy
    pypy3: pypy3
    py2: python2.7
    py3: python3.4
commands =
    pip install pyramid[testing]
    nosetests --with-xunit --xunit-file=nosetests-{envname}.xml {posargs:}
[testenv:pep8]
basepython = python3.4
commands =
    flake8 pyramid/
deps =
@@ -25,4 +32,42 @@
# we separate coverage into its own testenv because a) "last run wins" wrt
# cobertura jenkins reporting and b) pypy and jython can't handle any
# combination of versions of coverage and nosexcover that i can find.
[testenv:py2-cover]
commands =
    pip install pyramid[testing]
    coverage run --source=pyramid {envbindir}/nosetests
    coverage xml -o coverage-py2.xml
setenv =
    COVERAGE_FILE=.coverage.py2
[testenv:py3-cover]
commands =
    pip install pyramid[testing]
    coverage run --source=pyramid {envbindir}/nosetests
    coverage xml -o coverage-py3.xml
setenv =
    COVERAGE_FILE=.coverage.py3
[testenv:py2-docs]
whitelist_externals = make
commands =
    pip install pyramid[docs]
    make -C docs html
[testenv:py3-docs]
whitelist_externals = make
commands =
    pip install pyramid[docs]
    make -C docs html
[testenv:coverage]
basepython = python3.4
commands =
    coverage erase
    coverage combine
    coverage xml
    coverage report --show-missing --fail-under=100
deps =
    coverage
setenv =
    COVERAGE_FILE=.coverage