Chris McDonough
2015-04-02 45ddb5a5b744aebeac6004e9dba1c03d5bc8c50f
Merge branch 'master' of github.com:Pylons/pyramid
2 files deleted
10 files added
131 files modified
17651 ■■■■■ changed files
.gitignore 4 ●●●● patch | view | raw | blame | history
.travis.yml 24 ●●●●● patch | view | raw | blame | history
CHANGES.txt 184 ●●●●● patch | view | raw | blame | history
CONTRIBUTORS.txt 14 ●●●●● patch | view | raw | blame | history
HACKING.txt 5 ●●●●● patch | view | raw | blame | history
HISTORY.txt 2 ●●● patch | view | raw | blame | history
README.rst 11 ●●●●● patch | view | raw | blame | history
RELEASING.txt 3 ●●●●● patch | view | raw | blame | history
TODO.txt 5 ●●●● patch | view | raw | blame | history
docs/_static/pyramid_request_processing.graffle 9748 ●●●●● patch | view | raw | blame | history
docs/_static/pyramid_request_processing.png patch | view | raw | blame | history
docs/_static/pyramid_request_processing.svg 3 ●●●●● patch | view | raw | blame | history
docs/_static/pyramid_router.graffle 1621 ●●●●● patch | view | raw | blame | history
docs/_static/pyramid_router.png patch | view | raw | blame | history
docs/_static/pyramid_router.svg 3 ●●●●● patch | view | raw | blame | history
docs/_themes @ 382cba 2 ●●● patch | view | raw | blame | history
docs/api/config.rst 5 ●●●●● patch | view | raw | blame | history
docs/api/exceptions.rst 12 ●●●● patch | view | raw | blame | history
docs/api/httpexceptions.rst 94 ●●●● patch | view | raw | blame | history
docs/api/index.rst 12 ●●●●● patch | view | raw | blame | history
docs/api/interfaces.rst 5 ●●●●● patch | view | raw | blame | history
docs/api/registry.rst 12 ●●●●● patch | view | raw | blame | history
docs/api/request.rst 42 ●●●● patch | view | raw | blame | history
docs/api/security.rst 2 ●●● patch | view | raw | blame | history
docs/api/static.rst 14 ●●●●● patch | view | raw | blame | history
docs/conf.py 2 ●●● patch | view | raw | blame | history
docs/glossary.rst 37 ●●●● patch | view | raw | blame | history
docs/index.rst 3 ●●●● patch | view | raw | blame | history
docs/narr/MyProject/myproject/tests.py 37 ●●●●● patch | view | raw | blame | history
docs/narr/MyProject/setup.py 45 ●●●●● patch | view | raw | blame | history
docs/narr/assets.rst 244 ●●●●● patch | view | raw | blame | history
docs/narr/commandline.rst 56 ●●●● patch | view | raw | blame | history
docs/narr/configuration.rst 4 ●●●● patch | view | raw | blame | history
docs/narr/environment.rst 170 ●●●● patch | view | raw | blame | history
docs/narr/extconfig.rst 104 ●●●●● patch | view | raw | blame | history
docs/narr/hooks.rst 51 ●●●●● patch | view | raw | blame | history
docs/narr/hybrid.rst 2 ●●● patch | view | raw | blame | history
docs/narr/i18n.rst 18 ●●●●● patch | view | raw | blame | history
docs/narr/introspector.rst 16 ●●●● patch | view | raw | blame | history
docs/narr/logging.rst 68 ●●●●● patch | view | raw | blame | history
docs/narr/router.rst 6 ●●●● patch | view | raw | blame | history
docs/narr/security.rst 154 ●●●● patch | view | raw | blame | history
docs/narr/sessions.rst 4 ●●●● patch | view | raw | blame | history
docs/narr/startup.rst 8 ●●●●● patch | view | raw | blame | history
docs/narr/templates.rst 3 ●●●● patch | view | raw | blame | history
docs/narr/testing.rst 136 ●●●● patch | view | raw | blame | history
docs/narr/urldispatch.rst 62 ●●●● patch | view | raw | blame | history
docs/narr/viewconfig.rst 6 ●●●● patch | view | raw | blame | history
docs/narr/webob.rst 8 ●●●●● patch | view | raw | blame | history
docs/quick_tour.rst 2 ●●● patch | view | raw | blame | history
docs/quick_tutorial/debugtoolbar.rst 46 ●●●● patch | view | raw | blame | history
docs/quick_tutorial/forms.rst 2 ●●● patch | view | raw | blame | history
docs/quick_tutorial/functional_testing.rst 7 ●●●● patch | view | raw | blame | history
docs/quick_tutorial/hello_world.rst 2 ●●● patch | view | raw | blame | history
docs/quick_tutorial/ini.rst 6 ●●●● patch | view | raw | blame | history
docs/quick_tutorial/jinja2.rst 13 ●●●● patch | view | raw | blame | history
docs/quick_tutorial/jinja2/tutorial/tests.py 8 ●●●● patch | view | raw | blame | history
docs/quick_tutorial/logging.rst 2 ●●● patch | view | raw | blame | history
docs/quick_tutorial/more_view_classes/tutorial/views.py 6 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/authorization.rst 4 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/design.rst 4 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/design.rst 3 ●●●● patch | view | raw | blame | history
pyramid/authentication.py 29 ●●●● patch | view | raw | blame | history
pyramid/compat.py 113 ●●●●● patch | view | raw | blame | history
pyramid/config/__init__.py 135 ●●●●● patch | view | raw | blame | history
pyramid/config/adapters.py 2 ●●● patch | view | raw | blame | history
pyramid/config/assets.py 220 ●●●● patch | view | raw | blame | history
pyramid/config/factories.py 52 ●●●● patch | view | raw | blame | history
pyramid/config/routes.py 31 ●●●●● patch | view | raw | blame | history
pyramid/config/settings.py 9 ●●●● patch | view | raw | blame | history
pyramid/config/util.py 3 ●●●● patch | view | raw | blame | history
pyramid/config/views.py 163 ●●●● patch | view | raw | blame | history
pyramid/decorator.py 8 ●●●● patch | view | raw | blame | history
pyramid/httpexceptions.py 69 ●●●●● patch | view | raw | blame | history
pyramid/i18n.py 8 ●●●● patch | view | raw | blame | history
pyramid/interfaces.py 134 ●●●● patch | view | raw | blame | history
pyramid/path.py 10 ●●●● patch | view | raw | blame | history
pyramid/registry.py 5 ●●●●● patch | view | raw | blame | history
pyramid/renderers.py 113 ●●●●● patch | view | raw | blame | history
pyramid/request.py 52 ●●●● patch | view | raw | blame | history
pyramid/response.py 17 ●●●● patch | view | raw | blame | history
pyramid/router.py 3 ●●●● patch | view | raw | blame | history
pyramid/scaffolds/alchemy/development.ini_tmpl 2 ●●● patch | view | raw | blame | history
pyramid/scaffolds/alchemy/production.ini_tmpl 2 ●●● patch | view | raw | blame | history
pyramid/scaffolds/starter/development.ini_tmpl 2 ●●● patch | view | raw | blame | history
pyramid/scaffolds/starter/production.ini_tmpl 2 ●●● patch | view | raw | blame | history
pyramid/scaffolds/tests.py 4 ●●●● patch | view | raw | blame | history
pyramid/scaffolds/zodb/development.ini_tmpl 2 ●●● patch | view | raw | blame | history
pyramid/scaffolds/zodb/production.ini_tmpl 2 ●●● patch | view | raw | blame | history
pyramid/scripting.py 8 ●●●●● patch | view | raw | blame | history
pyramid/scripts/pcreate.py 15 ●●●● patch | view | raw | blame | history
pyramid/scripts/prequest.py 8 ●●●● patch | view | raw | blame | history
pyramid/scripts/proutes.py 380 ●●●●● patch | view | raw | blame | history
pyramid/scripts/pserve.py 37 ●●●●● patch | view | raw | blame | history
pyramid/scripts/pshell.py 9 ●●●●● patch | view | raw | blame | history
pyramid/security.py 39 ●●●● patch | view | raw | blame | history
pyramid/session.py 4 ●●●● patch | view | raw | blame | history
pyramid/static.py 134 ●●●●● patch | view | raw | blame | history
pyramid/testing.py 12 ●●●● patch | view | raw | blame | history
pyramid/tests/test_authentication.py 16 ●●●● patch | view | raw | blame | history
pyramid/tests/test_compat.py 26 ●●●●● 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/subpackage/templates/bar.pt 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 487 ●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_factories.py 34 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_init.py 91 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_settings.py 31 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_util.py 7 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_views.py 190 ●●●● patch | view | raw | blame | history
pyramid/tests/test_decorator.py 18 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_integration.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/test_path.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/test_registry.py 5 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_renderers.py 91 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_request.py 93 ●●●● patch | view | raw | blame | history
pyramid/tests/test_response.py 28 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_router.py 22 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_scripting.py 16 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/dummy.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/pystartup.txt 3 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/test_pcreate.py 28 ●●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/test_prequest.py 13 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/test_proutes.py 620 ●●●●● 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 23 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_security.py 21 ●●●● patch | view | raw | blame | history
pyramid/tests/test_session.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/test_static.py 163 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_testing.py 6 ●●●●● 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 301 ●●●● patch | view | raw | blame | history
pyramid/traversal.py 2 ●●● patch | view | raw | blame | history
pyramid/url.py 4 ●●●● patch | view | raw | blame | history
pyramid/urldispatch.py 13 ●●●● patch | view | raw | blame | history
pyramid/util.py 126 ●●●●● patch | view | raw | blame | history
pyramid/view.py 32 ●●●●● patch | view | raw | blame | history
rtd.txt 2 ●●● patch | view | raw | blame | history
setup.cfg 2 ●●●●● patch | view | raw | blame | history
setup.py 4 ●●●● patch | view | raw | blame | history
tox.ini 73 ●●●● 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
@@ -1,16 +1,24 @@
# Wire up travis
language: python
sudo: false
python:
  - 2.6
  - 2.7
  - pypy
  - 3.2
  - 3.3
env:
  - TOXENV=py26
  - TOXENV=py27
  - TOXENV=py32
  - TOXENV=py33
  - TOXENV=py34
  - TOXENV=pypy
  - TOXENV=pypy3
  - TOXENV=py2-docs
  - TOXENV=py3-docs
  - TOXENV=py2-cover,py3-cover,coverage
install: python setup.py dev
install:
  - travis_retry pip install tox
script: python setup.py test -q
script:
  - travis_retry tox
notifications:
  email:
CHANGES.txt
@@ -1,21 +1,152 @@
Next release
============
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
  https://github.com/Pylons/pyramid/issues/1297
- 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``.
  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
  resources relative to the package in which the ``Configurator`` was created.
  This is especially useful for addons that need to load asset specs from
  settings, in which case it is may be natural for a developer to define
  imports or assets relative to the top-level package.
  See https://github.com/Pylons/pyramid/pull/1337
- Added line numbers to the log formatters in the scaffolds to assist with
  debugging. See https://github.com/Pylons/pyramid/pull/1326
- Add new HTTP exception objects for status codes
  ``428 Precondition Required``, ``429 Too Many Requests`` and
  ``431 Request Header Fields Too Large`` in ``pyramid.httpexceptions``.
  See https://github.com/Pylons/pyramid/pull/1372/files
- The ``pshell`` script will now load a ``PYTHONSTARTUP`` file if one is
  defined in the environment prior to launching the interpreter.
  See https://github.com/Pylons/pyramid/pull/1448
- Make it simple to define notfound and forbidden views that wish to use
  the default exception-response view but with altered predicates and other
  configuration options. The ``view`` argument is now optional in
  ``config.add_notfound_view`` and ``config.add_forbidden_view``..
  See https://github.com/Pylons/pyramid/issues/494
- Greatly improve the readability of the ``pcreate`` shell script output.
  See https://github.com/Pylons/pyramid/pull/1453
- Improve robustness to timing attacks in the ``AuthTktCookieHelper`` and
  the ``SignedCookieSessionFactory`` classes by using the stdlib's
  ``hmac.compare_digest`` if it is available (such as Python 2.7.7+ and 3.3+).
  See https://github.com/Pylons/pyramid/pull/1457
- Assets can now be overidden by an absolute path on the filesystem when using
  the ``config.override_asset`` API. This makes it possible to fully support
  serving up static content from a mutable directory while still being able
  to use the ``request.static_url`` API and ``config.add_static_view``.
  Previously it was not possible to use ``config.add_static_view`` with an
  absolute path **and** generate urls to the content. This change replaces
  the call, ``config.add_static_view('/abs/path', 'static')``, with
  ``config.add_static_view('myapp:static', 'static')`` and
  ``config.override_asset(to_override='myapp:static/',
  override_with='/abs/path/')``. The ``myapp:static`` asset spec is completely
  made up and does not need to exist - it is used for generating urls
  via ``request.static_url('myapp:static/foo.png')``.
  See https://github.com/Pylons/pyramid/issues/1252
- Added ``pyramid.config.Configurator.set_response_factory`` and the
  ``response_factory`` keyword argument to the ``Configurator`` for defining
  a factory that will return a custom ``Response`` class.
  See https://github.com/Pylons/pyramid/pull/1499
- Allow an iterator to be returned from a renderer. Previously it was only
  possible to return bytes or unicode.
  See https://github.com/Pylons/pyramid/pull/1417
- ``pserve`` can now take a ``-b`` or ``--browser`` option to open the server
  URL in a web browser. See https://github.com/Pylons/pyramid/pull/1533
- Overall improvments for the ``proutes`` command. Added ``--format`` and
  ``--glob`` arguments to the command, introduced the ``method``
  column for displaying available request methods, and improved the ``view``
  output by showing the module instead of just ``__repr__``.
  See https://github.com/Pylons/pyramid/pull/1488
- 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
- The ``append_slash`` argument of ```Configurator().add_notfound_view()`` will
  now accept anything that implements the ``IResponse`` interface and will use
  that as the response class instead of the default ``HTTPFound``.  See
  https://github.com/Pylons/pyramid/pull/1610
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``.
  See https://github.com/Pylons/pyramid/pull/1320
- Fix an issue whereby predicates would be resolved as maybe_dotted in the
  introspectable but not when passed for registration. This would mean that
  add_route_predicate for example can not take a string and turn it into the
  actual callable function.
  ``add_route_predicate`` for example can not take a string and turn it into
  the actual callable function.
  See https://github.com/Pylons/pyramid/pull/1306
- Fix ``pyramid.testing.setUp`` to return a ``Configurator`` with a proper
  package. Previously it was not possible to do package-relative includes
  using the returned ``Configurator`` during testing. There is now a
  ``package`` argument that can override this behavior as well.
  See https://github.com/Pylons/pyramid/pull/1322
- Fix an issue where a ``pyramid.response.FileResponse`` may apply a charset
  where it does not belong. See https://github.com/Pylons/pyramid/pull/1251
@@ -25,8 +156,53 @@
  type, unlike any previous version of Python.  See
  https://github.com/Pylons/pyramid/issues/1360 for more information.
- ``pcreate`` now normalizes the package name by converting hyphens to
  underscores. See https://github.com/Pylons/pyramid/pull/1376
- Fix an issue with the final response/finished callback being unable to
  add another callback to the list. See
  https://github.com/Pylons/pyramid/pull/1373
- Fix a failing unittest caused by differing mimetypes across various OSs.
  See https://github.com/Pylons/pyramid/issues/1405
- Fix route generation for static view asset specifications having no path.
  See https://github.com/Pylons/pyramid/pull/1377
- 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.
  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
------------
- Renamed the ``principal`` argument to ``pyramid.security.remember()`` to
  ``userid`` in order to clarify its intended purpose.
  See https://github.com/Pylons/pyramid/pull/1399
Docs
----
- Moved the documentation for ``accept`` on ``Configurator.add_view`` to no
  longer be part of the predicate list. See
  https://github.com/Pylons/pyramid/issues/1391 for a bug report stating
  ``not_`` was failing on ``accept``. Discussion with @mcdonc led to the
  conclusion that it should not be documented as a predicate.
  See https://github.com/Pylons/pyramid/pull/1487 for this PR
- Removed logging configuration from Quick Tutorial ini files except for
  scaffolding- and logging-related chapters to avoid needing to explain it too
@@ -35,6 +211,10 @@
- Clarify a previously-implied detail of the ``ISession.invalidate`` API
  documentation.
- Improve and clarify the documentation on what Pyramid defines as a
  ``principal`` and a ``userid`` in its security APIs.
  See https://github.com/Pylons/pyramid/pull/1399
Scaffolds
---------
CONTRIBUTORS.txt
@@ -232,3 +232,17 @@
- Amit Mane, 2014/01/23
- Fenton Travers, 2014/05/06
- Randall Leeds, 2014/11/11
- Hugo Branquinho, 2014/11/25
- Adrian Teng, 2014/12/17
- Ilja Everila, 2015/02/05
- Geoffrey T. Dairiki, 2015/02/06
- David Glick, 2015/02/12
- Donald Stufft, 2015/03/15
HACKING.txt
@@ -31,7 +31,7 @@
   $ cd hack-on-pyramid
   # Configure remotes such that you can pull changes from the Pyramid
   # repository into your local repository.
   $ git remote add upstream https://github.com:Pylons/pyramid.git
   $ git remote add upstream https://github.com/Pylons/pyramid.git
   # fetch and merge changes from upstream into master
   $ git fetch upstream
   $ git merge upstream/master
@@ -113,6 +113,7 @@
``hacking`` that you can then fire up like so:
  cd env27/hacking
  ../bin/python setup.py develop
  ../bin/pserve development.ini
Adding Features
@@ -194,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``.
HISTORY.txt
@@ -1327,7 +1327,7 @@
- Make test suite pass on 32-bit systems; closes #286.  closes #306.
  See also https://github.com/Pylons/pyramid/issues/286
- The ``pryamid.view.view_config`` decorator did not accept a ``match_params``
- The ``pyramid.view.view_config`` decorator did not accept a ``match_params``
  predicate argument.  See https://github.com/Pylons/pyramid/pull/308
- The AuthTktCookieHelper could potentially generate Unicode headers
README.rst
@@ -1,6 +1,17 @@
Pyramid
=======
.. image:: https://travis-ci.org/Pylons/pyramid.png?branch=master
        :target: https://travis-ci.org/Pylons/pyramid
.. image:: https://readthedocs.org/projects/pyramid/badge/?version=master
        :target: http://docs.pylonsproject.org/projects/pyramid/en/master/
        :alt: Master Documentation Status
.. image:: https://readthedocs.org/projects/pyramid/badge/?version=latest
        :target: http://docs.pylonsproject.org/projects/pyramid/en/latest/
        :alt: Latest Documentation Status
Pyramid is a small, fast, down-to-earth, open source Python web framework.
It makes real-world web application development and
deployment more fun, more predictable, and more productive.
RELEASING.txt
@@ -26,6 +26,9 @@
- Copy relevant changes (delta bug fixes) from CHANGES.txt to
  docs/whatsnew-X.X (if it's a major release).
- update README.rst to use correct versions of badges and URLs according to
  each branch and context, i.e., RTD "latest" == GitHub/Travis "1.x-branch".
- Make sure docs render OK::
  $ cd docs
TODO.txt
@@ -125,7 +125,10 @@
- 1.7: Change ``pyramid.authentication.AuthTktAuthenticationPolicy`` default
  ``hashalg`` to ``sha512``.
- 1.8 Remove set_request_property.
- 1.8: Remove set_request_property.
- 1.9: Remove extra code enabling ``pyramid.security.remember(principal=...)``
  and force use of ``userid``.
Probably Bad Ideas
------------------
docs/_static/pyramid_request_processing.graffle
New file
Diff too large
docs/_static/pyramid_request_processing.png
docs/_static/pyramid_request_processing.svg
New file
@@ -0,0 +1,3 @@
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="91 11 424 533" width="424pt" height="533pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2014-11-23 07:19Z</dc:date><!-- Produced by OmniGraffle Professional 5.4.4 --></metadata><defs><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="SharpArrow_Marker" viewBox="-4 -4 10 8" markerWidth="10" markerHeight="8" color="#191919"><g><path d="M 5 0 L -3 -3 L 0 0 L 0 0 L -3 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="522.94922" cap-height="717.28516" ascent="770.01953" descent="-229.98047" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="12" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Request Processing</title><rect fill="white" width="576" height="733"/><g><title>no exceptions</title><path d="M 155 444.75674 C 155 450.64061 155 486.2592 155 502.71617" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 322.33334 C 154.99999 327.72413 155 337.74646 155 346.1775" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 245.22768 C 154.99999 250.5417 154.99999 257.93189 154.99999 265.10145" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99995 198.62203 C 154.99995 203.74682 154.99998 209.1909 154.99999 215.28222" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="102.16667" y="45.183037" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="45.183037" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 50.455358)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="4.7596016" y="10" textLength="88.92578">middleware ingress </tspan></text><rect x="102.16667" y="96.183037" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="96.183037" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 101.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.983723" y="10" textLength="61.69922">tween ingress</tspan></text><rect x="102.16667" y="222.18304" width="105.666664" height="22.544641" fill="#d2ffd0"/><rect x="102.16667" y="222.18304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 227.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="28.660969" y="10" textLength="38.344727">traversal</tspan></text><rect x="238.83336" y="247.18304" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="247.18304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 252.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.424641" y="10" textLength="62.817383">ContextFound</tspan></text><rect x="102.16667" y="422.2121" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="422.2121" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 427.48442)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="18.094563" y="10" textLength="59.47754">tween egress</tspan></text><rect x="239" y="445.2359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="445.2359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 450.50821)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.3113594" y="10" textLength="85.043945">response callbacks</tspan></text><rect x="239" y="497.2359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="497.2359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 502.5082)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="8.6463203" y="10" textLength="5">fi</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="13.64632" y="10" textLength="73.374023">nished callbacks</tspan></text><rect x="102.16667" y="509.61795" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="509.61795" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 514.89027)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.8704414" y="10" textLength="83.92578">middleware egress</tspan></text><path d="M 155 67.72768 C 155 73.048893 155 81.55558 155 89.2853" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 155 119.22768 C 155 124.62026 154.99997 133.48763 154.99996 141.38632" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="375.5" y="391" width="105.666664" height="22.544642" fill="#dfbeff"/><rect x="375.5" y="391" width="105.666664" height="22.544642" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(380.5 396.27232)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.702961" y="10" textLength="62.260742">BeforeRender</tspan></text><text transform="translate(233.5 20)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="bold" x=".31445312" y="11" textLength="115.371094">Request Processing</tspan></text><path d="M 375.99995 42.910746 L 498.66662 42.910746 C 501.42805 42.910746 503.66662 45.149323 503.66662 47.910746 L 503.66662 222 C 503.66662 224.76142 501.42805 227 498.66662 227 L 375.99995 227 C 373.23853 227 370.99995 224.76142 370.99995 222 L 370.99995 47.910746 C 370.99995 45.149323 373.23853 42.910746 375.99995 42.910746 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(375.99995 42.910746)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="0" y="10" textLength="35.55664">Legend</tspan></text><rect x="383.66662" y="63.908513" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="383.66662" y="63.908513" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 69.180834)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="35.601887" y="10" textLength="24.46289">event</tspan></text><rect x="383.66662" y="186.58226" width="105.666664" height="22.544641" fill="#fed153"/><rect x="383.66662" y="186.58226" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 191.85458)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="29.769367" y="10" textLength="36.12793">callback</tspan></text><rect x="383.66662" y="158.54998" width="105.666664" height="22.544641" fill="#ffff6c"/><rect x="383.66662" y="158.54998" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 163.8223)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.83089" y="10" textLength="20.004883">view</tspan></text><rect x="383.66662" y="91.94079" width="105.666664" height="33.089283" fill="#a4cfff"/><rect x="383.66662" y="91.94079" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 96.48543)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.148762" y="10" textLength="76.14746">external process </tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="2.8162422" y="22" textLength="90.03418">(middleware, tween)</tspan></text><rect x="383.66662" y="130.51771" width="105.666664" height="22.544641" fill="#d2ffd0"/><rect x="383.66662" y="130.51771" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 135.79003)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="12.537922" y="10" textLength="70.59082">internal process</tspan></text><line x1="154.99999" y1="258.44082" x2="238.83336" y2="258.45536" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="102.16667" y="353.07515" width="105.666664" height="33.089283" fill="#ffff6c"/><rect x="102.16667" y="353.07515" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 363.61979)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.205402" y="10" textLength="57.25586">view pipeline</tspan></text><path d="M 155 386.66443 C 155 392.17252 155 405.5052 155 415.30935" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="239.33336" y1="285.57838" x2="207.66667" y2="353.07515" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><line x1="238.75001" y1="430.80676" x2="207.66667" y2="385.656" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><rect x="102.16666" y="305.0893" width="105.666664" height="17.244049" fill="#d2ffd0"/><rect x="102.16666" y="305.0893" width="105.666664" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 307.71132)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="24.764484" y="10" textLength="46.137695">predicates</tspan></text><rect x="102.16666" y="272" width="105.666664" height="33.089294" fill="#d2ffd0"/><rect x="102.16666" y="272" width="105.666664" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 282.54465)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="21.707844" y="10" textLength="52.250977">view lookup</tspan></text><rect x="102.166606" y="181.37798" width="105.666695" height="17.244049" fill="#d2ffd0"/><rect x="102.166606" y="181.37798" width="105.666695" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 184)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.978855" y="10" textLength="71.708984">route predicates</tspan></text><rect x="102.166606" y="148.28869" width="105.666695" height="33.089294" fill="#d2ffd0"/><rect x="102.166606" y="148.28869" width="105.666695" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 158.83333)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="18.001804" y="10" textLength="20.004883">URL</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.640476" y="10" textLength="40.024414"> dispatch</tspan></text><rect x="239.8334" y="117.3192" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="239.8334" y="117.3192" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244.8334 122.59152)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.207844" y="10" textLength="57.250977">NewRequest</tspan></text><line x1="154.99999" y1="128.68025" x2="239.8334" y2="128.59152" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.83336" y="471.2262" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="471.2262" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 476.49852)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="15.316242" y="10" textLength="65.03418">NewResponse</tspan></text><line x1="155" y1="470.25295" x2="238.33861" y2="482.42625" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.75001" y="322.26348" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="322.26348" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 325.5915)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="1.9812813" y="10" textLength="91.7041">view mapper ingress</tspan></text><rect x="238.75001" y="341.36561" width="105.666664" height="33.089283" fill="#ffff6c"/><rect x="238.75001" y="341.36561" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 351.91025)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.83089" y="10" textLength="20.004883">view</tspan></text><rect x="238.75001" y="374.901" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="374.901" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 378.22901)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="3.0921211" y="10" textLength="89.48242">view mapper egress</tspan></text><rect x="238.75001" y="393.55704" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="393.55704" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 396.88507)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="8.9173164" y="10" textLength="77.83203">response adapter</tspan></text><rect x="238.75001" y="303.65604" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="303.65604" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 306.98407)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="6.702961" y="10" textLength="82.26074">decorators ingress</tspan></text><rect x="238.75001" y="412.1507" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="412.1507" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 415.47873)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="7.813801" y="10" textLength="80.039062">decorators egress</tspan></text><rect x="238.83336" y="285" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.83336" y="285" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 288.32802)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.202972" y="10" textLength="57.260742">authorization</tspan></text><line x1="155" y1="482.12575" x2="238.52297" y2="508.3584" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="155" y1="459.27668" x2="238.50027" y2="456.52468" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="344.41667" y1="402.88507" x2="375.5" y2="402.27232" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/></g></g></svg>
docs/_static/pyramid_router.graffle
New file
@@ -0,0 +1,1621 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>ActiveLayerIndex</key>
    <integer>0</integer>
    <key>ApplicationVersion</key>
    <array>
        <string>com.omnigroup.OmniGrafflePro</string>
        <string>139.18.0.187838</string>
    </array>
    <key>AutoAdjust</key>
    <true/>
    <key>BackgroundGraphic</key>
    <dict>
        <key>Bounds</key>
        <string>{{0, 0}, {576, 733}}</string>
        <key>Class</key>
        <string>SolidGraphic</string>
        <key>ID</key>
        <integer>2</integer>
        <key>Style</key>
        <dict>
            <key>shadow</key>
            <dict>
                <key>Draws</key>
                <string>NO</string>
            </dict>
            <key>stroke</key>
            <dict>
                <key>Draws</key>
                <string>NO</string>
            </dict>
        </dict>
    </dict>
    <key>BaseZoom</key>
    <integer>0</integer>
    <key>CanvasOrigin</key>
    <string>{0, 0}</string>
    <key>ColumnAlign</key>
    <integer>1</integer>
    <key>ColumnSpacing</key>
    <real>36</real>
    <key>CreationDate</key>
    <string>2014-12-01 08:25:13 +0000</string>
    <key>Creator</key>
    <string>Steve Piercy</string>
    <key>DisplayScale</key>
    <string>1 0/72 in = 1 0/72 in</string>
    <key>GraphDocumentVersion</key>
    <integer>8</integer>
    <key>GraphicsList</key>
    <array>
        <dict>
            <key>Class</key>
            <string>LineGraphic</string>
            <key>ControlPoints</key>
            <array>
                <string>{0, 6.9840087890625}</string>
                <string>{0, -9}</string>
            </array>
            <key>FontInfo</key>
            <dict>
                <key>Color</key>
                <dict>
                    <key>w</key>
                    <string>0</string>
                </dict>
                <key>Font</key>
                <string>Helvetica</string>
                <key>Size</key>
                <real>12</real>
            </dict>
            <key>Head</key>
            <dict>
                <key>ID</key>
                <integer>169413</integer>
            </dict>
            <key>ID</key>
            <integer>169414</integer>
            <key>Points</key>
            <array>
                <string>{202.04165903727232, 501.05557886759294}</string>
                <string>{202.04165903727232, 528.77776209513161}</string>
            </array>
            <key>Style</key>
            <dict>
                <key>stroke</key>
                <dict>
                    <key>Bezier</key>
                    <true/>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.0980392</string>
                        <key>g</key>
                        <string>0.0980392</string>
                        <key>r</key>
                        <string>0.0980392</string>
                    </dict>
                    <key>HeadArrow</key>
                    <string>SharpArrow</string>
                    <key>Legacy</key>
                    <true/>
                    <key>LineType</key>
                    <integer>1</integer>
                    <key>TailArrow</key>
                    <string>0</string>
                </dict>
            </dict>
            <key>Tail</key>
            <dict>
                <key>ID</key>
                <integer>169412</integer>
            </dict>
        </dict>
        <dict>
            <key>Bounds</key>
            <string>{{104.41666666666686, 528.77776209513161}, {195.24998474121094, 29}}</string>
            <key>Class</key>
            <string>ShapedGraphic</string>
            <key>ID</key>
            <integer>169413</integer>
            <key>Magnets</key>
            <array>
                <string>{0, 1}</string>
                <string>{0, -1}</string>
                <string>{1, 0}</string>
                <string>{-1, 0}</string>
            </array>
            <key>Shape</key>
            <string>Rectangle</string>
            <key>Style</key>
            <dict>
                <key>fill</key>
                <dict>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.999449</string>
                        <key>g</key>
                        <string>0.743511</string>
                        <key>r</key>
                        <string>0.872276</string>
                    </dict>
                </dict>
                <key>shadow</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
            </dict>
            <key>Text</key>
            <dict>
                <key>Text</key>
                <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
\f0\fs20 \cf0 Return the
\b response}</string>
                <key>VerticalPad</key>
                <integer>0</integer>
            </dict>
        </dict>
        <dict>
            <key>Bounds</key>
            <string>{{104.41666666666657, 471.55557886759294}, {195.24998474121094, 29}}</string>
            <key>Class</key>
            <string>ShapedGraphic</string>
            <key>ID</key>
            <integer>169412</integer>
            <key>Magnets</key>
            <array>
                <string>{0, 1}</string>
                <string>{0, -1}</string>
                <string>{1, 0}</string>
                <string>{-1, 0}</string>
            </array>
            <key>Shape</key>
            <string>Rectangle</string>
            <key>Style</key>
            <dict>
                <key>fill</key>
                <dict>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.422927</string>
                        <key>g</key>
                        <string>1</string>
                        <key>r</key>
                        <string>1</string>
                    </dict>
                </dict>
                <key>shadow</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
            </dict>
            <key>Text</key>
            <dict>
                <key>Text</key>
                <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
\f0\fs20 \cf0 Invoke the
\b view callable
\b0 ,\
which returns a
\b response}</string>
                <key>VerticalPad</key>
                <integer>0</integer>
            </dict>
        </dict>
        <dict>
            <key>Bounds</key>
            <string>{{291.21562524160186, 379.55555343627816}, {26, 24}}</string>
            <key>Class</key>
            <string>ShapedGraphic</string>
            <key>FitText</key>
            <string>YES</string>
            <key>Flow</key>
            <string>Resize</string>
            <key>FontInfo</key>
            <dict>
                <key>Color</key>
                <dict>
                    <key>w</key>
                    <string>0</string>
                </dict>
                <key>Font</key>
                <string>Helvetica</string>
                <key>Size</key>
                <real>12</real>
            </dict>
            <key>ID</key>
            <integer>169411</integer>
            <key>Line</key>
            <dict>
                <key>ID</key>
                <integer>169410</integer>
                <key>Offset</key>
                <real>7.3333320617675781</real>
                <key>Position</key>
                <real>0.4865129292011261</real>
                <key>RotationType</key>
                <integer>0</integer>
            </dict>
            <key>Shape</key>
            <string>Rectangle</string>
            <key>Style</key>
            <dict>
                <key>fill</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
                <key>shadow</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
                <key>stroke</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
            </dict>
            <key>Text</key>
            <dict>
                <key>Text</key>
                <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc
\f0\fs24 \cf0 No}</string>
            </dict>
            <key>Wrap</key>
            <string>NO</string>
        </dict>
        <dict>
            <key>Class</key>
            <string>LineGraphic</string>
            <key>ControlPoints</key>
            <array>
                <string>{34.791667904111534, 0}</string>
                <string>{-33.999994913736998, 0}</string>
            </array>
            <key>FontInfo</key>
            <dict>
                <key>Color</key>
                <dict>
                    <key>w</key>
                    <string>0</string>
                </dict>
                <key>Font</key>
                <string>Helvetica</string>
                <key>Size</key>
                <real>12</real>
            </dict>
            <key>Head</key>
            <dict>
                <key>ID</key>
                <integer>169409</integer>
            </dict>
            <key>ID</key>
            <integer>169410</integer>
            <key>Points</key>
            <array>
                <string>{280.85416589389337, 398.88888549804574}</string>
                <string>{327.47912214508739, 398.88888549804574}</string>
            </array>
            <key>Style</key>
            <dict>
                <key>stroke</key>
                <dict>
                    <key>Bezier</key>
                    <true/>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.0980392</string>
                        <key>g</key>
                        <string>0.0980392</string>
                        <key>r</key>
                        <string>0.0980392</string>
                    </dict>
                    <key>HeadArrow</key>
                    <string>SharpArrow</string>
                    <key>Legacy</key>
                    <true/>
                    <key>LineType</key>
                    <integer>1</integer>
                    <key>TailArrow</key>
                    <string>0</string>
                </dict>
            </dict>
            <key>Tail</key>
            <dict>
                <key>ID</key>
                <integer>169404</integer>
            </dict>
        </dict>
        <dict>
            <key>Bounds</key>
            <string>{{327.47912214508739, 384.38888549804574}, {156.62496948242188, 29}}</string>
            <key>Class</key>
            <string>ShapedGraphic</string>
            <key>ID</key>
            <integer>169409</integer>
            <key>Magnets</key>
            <array>
                <string>{0, 1}</string>
                <string>{0, -1}</string>
                <string>{1, 0}</string>
                <string>{-1, 0}</string>
            </array>
            <key>Shape</key>
            <string>Rectangle</string>
            <key>Style</key>
            <dict>
                <key>fill</key>
                <dict>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.756045</string>
                        <key>g</key>
                        <string>0.75004</string>
                        <key>r</key>
                        <string>0.994455</string>
                    </dict>
                </dict>
                <key>shadow</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
            </dict>
            <key>Text</key>
            <dict>
                <key>Text</key>
                <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
\f0\fs20 \cf0 Return the
\b Forbidden View}</string>
                <key>VerticalPad</key>
                <integer>0</integer>
            </dict>
        </dict>
        <dict>
            <key>Bounds</key>
            <string>{{175.11595161998204, 438.9999954213917}, {30, 24}}</string>
            <key>Class</key>
            <string>ShapedGraphic</string>
            <key>FitText</key>
            <string>YES</string>
            <key>Flow</key>
            <string>Resize</string>
            <key>FontInfo</key>
            <dict>
                <key>Color</key>
                <dict>
                    <key>w</key>
                    <string>0</string>
                </dict>
                <key>Font</key>
                <string>Helvetica</string>
                <key>Size</key>
                <real>12</real>
            </dict>
            <key>ID</key>
            <integer>169408</integer>
            <key>Shape</key>
            <string>Rectangle</string>
            <key>Style</key>
            <dict>
                <key>fill</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
                <key>shadow</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
                <key>stroke</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
            </dict>
            <key>Text</key>
            <dict>
                <key>Text</key>
                <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc
\f0\fs24 \cf0 Yes}</string>
            </dict>
            <key>Wrap</key>
            <string>NO</string>
        </dict>
        <dict>
            <key>Class</key>
            <string>LineGraphic</string>
            <key>ControlPoints</key>
            <array>
                <string>{0, 6.9840087890625}</string>
                <string>{0, -9}</string>
            </array>
            <key>FontInfo</key>
            <dict>
                <key>Color</key>
                <dict>
                    <key>w</key>
                    <string>0</string>
                </dict>
                <key>Font</key>
                <string>Helvetica</string>
                <key>Size</key>
                <real>12</real>
            </dict>
            <key>Head</key>
            <dict>
                <key>ID</key>
                <integer>169412</integer>
                <key>Info</key>
                <integer>2</integer>
            </dict>
            <key>ID</key>
            <integer>169407</integer>
            <key>Points</key>
            <array>
                <string>{202.04165267944353, 437.33333079020139}</string>
                <string>{202.04165903727204, 471.55557886759294}</string>
            </array>
            <key>Style</key>
            <dict>
                <key>stroke</key>
                <dict>
                    <key>Bezier</key>
                    <true/>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.0980392</string>
                        <key>g</key>
                        <string>0.0980392</string>
                        <key>r</key>
                        <string>0.0980392</string>
                    </dict>
                    <key>HeadArrow</key>
                    <string>SharpArrow</string>
                    <key>Legacy</key>
                    <true/>
                    <key>LineType</key>
                    <integer>1</integer>
                    <key>TailArrow</key>
                    <string>0</string>
                </dict>
            </dict>
            <key>Tail</key>
            <dict>
                <key>ID</key>
                <integer>169404</integer>
                <key>Info</key>
                <integer>1</integer>
            </dict>
        </dict>
        <dict>
            <key>Bounds</key>
            <string>{{171.708317756653, 329.24978243601743}, {30, 24}}</string>
            <key>Class</key>
            <string>ShapedGraphic</string>
            <key>FitText</key>
            <string>YES</string>
            <key>Flow</key>
            <string>Resize</string>
            <key>FontInfo</key>
            <dict>
                <key>Color</key>
                <dict>
                    <key>w</key>
                    <string>0</string>
                </dict>
                <key>Font</key>
                <string>Helvetica</string>
                <key>Size</key>
                <real>12</real>
            </dict>
            <key>ID</key>
            <integer>169406</integer>
            <key>Line</key>
            <dict>
                <key>ID</key>
                <integer>169405</integer>
                <key>Offset</key>
                <real>-15.333334922790527</real>
                <key>Position</key>
                <real>0.45895844697952271</real>
                <key>RotationType</key>
                <integer>0</integer>
            </dict>
            <key>Shape</key>
            <string>Rectangle</string>
            <key>Style</key>
            <dict>
                <key>fill</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
                <key>shadow</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
                <key>stroke</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
            </dict>
            <key>Text</key>
            <dict>
                <key>Text</key>
                <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc
\f0\fs24 \cf0 Yes}</string>
            </dict>
            <key>Wrap</key>
            <string>NO</string>
        </dict>
        <dict>
            <key>Class</key>
            <string>LineGraphic</string>
            <key>ControlPoints</key>
            <array>
                <string>{0, 6.9840087890625}</string>
                <string>{0, -9}</string>
            </array>
            <key>FontInfo</key>
            <dict>
                <key>Color</key>
                <dict>
                    <key>w</key>
                    <string>0</string>
                </dict>
                <key>Font</key>
                <string>Helvetica</string>
                <key>Size</key>
                <real>12</real>
            </dict>
            <key>Head</key>
            <dict>
                <key>ID</key>
                <integer>169404</integer>
                <key>Info</key>
                <integer>2</integer>
            </dict>
            <key>ID</key>
            <integer>169405</integer>
            <key>Points</key>
            <array>
                <string>{202.04165267944353, 326.72223360222029}</string>
                <string>{202.04165267944353, 360.44446818033811}</string>
            </array>
            <key>Style</key>
            <dict>
                <key>stroke</key>
                <dict>
                    <key>Bezier</key>
                    <true/>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.0980392</string>
                        <key>g</key>
                        <string>0.0980392</string>
                        <key>r</key>
                        <string>0.0980392</string>
                    </dict>
                    <key>HeadArrow</key>
                    <string>SharpArrow</string>
                    <key>Legacy</key>
                    <true/>
                    <key>LineType</key>
                    <integer>1</integer>
                    <key>TailArrow</key>
                    <string>0</string>
                </dict>
            </dict>
            <key>Tail</key>
            <dict>
                <key>ID</key>
                <integer>3</integer>
            </dict>
        </dict>
        <dict>
            <key>Bounds</key>
            <string>{{123.72916793823259, 360.44446818033811}, {156.62496948242188, 76.888862609863281}}</string>
            <key>Class</key>
            <string>ShapedGraphic</string>
            <key>ID</key>
            <integer>169404</integer>
            <key>Magnets</key>
            <array>
                <string>{0, 1}</string>
                <string>{0, -1}</string>
                <string>{1, 0}</string>
                <string>{-1, 0}</string>
            </array>
            <key>Shape</key>
            <string>Diamond</string>
            <key>Style</key>
            <dict>
                <key>fill</key>
                <dict>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.422927</string>
                        <key>g</key>
                        <string>1</string>
                        <key>r</key>
                        <string>1</string>
                    </dict>
                </dict>
                <key>shadow</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
            </dict>
            <key>Text</key>
            <dict>
                <key>Text</key>
                <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
\f0\fs20 \cf0 Current user has
\b authorization
\b0  to invoke the view callable?}</string>
                <key>VerticalPad</key>
                <integer>0</integer>
            </dict>
        </dict>
        <dict>
            <key>Bounds</key>
            <string>{{283.07625736262997, 281.88889694213805}, {26, 24}}</string>
            <key>Class</key>
            <string>ShapedGraphic</string>
            <key>FitText</key>
            <string>YES</string>
            <key>Flow</key>
            <string>Resize</string>
            <key>FontInfo</key>
            <dict>
                <key>Color</key>
                <dict>
                    <key>w</key>
                    <string>0</string>
                </dict>
                <key>Font</key>
                <string>Helvetica</string>
                <key>Size</key>
                <real>12</real>
            </dict>
            <key>ID</key>
            <integer>169403</integer>
            <key>Line</key>
            <dict>
                <key>ID</key>
                <integer>169402</integer>
                <key>Offset</key>
                <real>7.3333320617675781</real>
                <key>Position</key>
                <real>0.4865129292011261</real>
                <key>RotationType</key>
                <integer>0</integer>
            </dict>
            <key>Shape</key>
            <string>Rectangle</string>
            <key>Style</key>
            <dict>
                <key>fill</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
                <key>shadow</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
                <key>stroke</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
            </dict>
            <key>Text</key>
            <dict>
                <key>Text</key>
                <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc
\f0\fs24 \cf0 No}</string>
            </dict>
            <key>Wrap</key>
            <string>NO</string>
        </dict>
        <dict>
            <key>Class</key>
            <string>LineGraphic</string>
            <key>ControlPoints</key>
            <array>
                <string>{34.791667904111534, 0}</string>
                <string>{-33.999994913736998, 0}</string>
            </array>
            <key>FontInfo</key>
            <dict>
                <key>Color</key>
                <dict>
                    <key>w</key>
                    <string>0</string>
                </dict>
                <key>Font</key>
                <string>Helvetica</string>
                <key>Size</key>
                <real>12</real>
            </dict>
            <key>Head</key>
            <dict>
                <key>ID</key>
                <integer>169401</integer>
            </dict>
            <key>ID</key>
            <integer>169402</integer>
            <key>Points</key>
            <array>
                <string>{265.20833208871704, 301.22222900390562}</string>
                <string>{327.47911580403627, 301.22222900390562}</string>
            </array>
            <key>Style</key>
            <dict>
                <key>stroke</key>
                <dict>
                    <key>Bezier</key>
                    <true/>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.0980392</string>
                        <key>g</key>
                        <string>0.0980392</string>
                        <key>r</key>
                        <string>0.0980392</string>
                    </dict>
                    <key>HeadArrow</key>
                    <string>SharpArrow</string>
                    <key>Legacy</key>
                    <true/>
                    <key>LineType</key>
                    <integer>1</integer>
                    <key>TailArrow</key>
                    <string>0</string>
                </dict>
            </dict>
            <key>Tail</key>
            <dict>
                <key>ID</key>
                <integer>3</integer>
            </dict>
        </dict>
        <dict>
            <key>Bounds</key>
            <string>{{327.47911580403627, 286.72222900390562}, {156.62496948242188, 29}}</string>
            <key>Class</key>
            <string>ShapedGraphic</string>
            <key>ID</key>
            <integer>169401</integer>
            <key>Magnets</key>
            <array>
                <string>{0, 1}</string>
                <string>{0, -1}</string>
                <string>{1, 0}</string>
                <string>{-1, 0}</string>
            </array>
            <key>Shape</key>
            <string>Rectangle</string>
            <key>Style</key>
            <dict>
                <key>fill</key>
                <dict>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.756045</string>
                        <key>g</key>
                        <string>0.75004</string>
                        <key>r</key>
                        <string>0.994455</string>
                    </dict>
                </dict>
                <key>shadow</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
            </dict>
            <key>Text</key>
            <dict>
                <key>Text</key>
                <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
\f0\fs20 \cf0 Return the
\b Not Found View}</string>
                <key>VerticalPad</key>
                <integer>0</integer>
            </dict>
        </dict>
        <dict>
            <key>Class</key>
            <string>LineGraphic</string>
            <key>ControlPoints</key>
            <array>
                <string>{0, 6.9840087890625}</string>
                <string>{0, -9}</string>
            </array>
            <key>FontInfo</key>
            <dict>
                <key>Color</key>
                <dict>
                    <key>w</key>
                    <string>0</string>
                </dict>
                <key>Font</key>
                <string>Helvetica</string>
                <key>Size</key>
                <real>12</real>
            </dict>
            <key>Head</key>
            <dict>
                <key>ID</key>
                <integer>3</integer>
                <key>Info</key>
                <integer>2</integer>
            </dict>
            <key>ID</key>
            <integer>169400</integer>
            <key>Points</key>
            <array>
                <string>{202.04165903727255, 251}</string>
                <string>{202.04165776570633, 276.22223154703772}</string>
            </array>
            <key>Style</key>
            <dict>
                <key>stroke</key>
                <dict>
                    <key>Bezier</key>
                    <true/>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.0980392</string>
                        <key>g</key>
                        <string>0.0980392</string>
                        <key>r</key>
                        <string>0.0980392</string>
                    </dict>
                    <key>HeadArrow</key>
                    <string>SharpArrow</string>
                    <key>Legacy</key>
                    <true/>
                    <key>LineType</key>
                    <integer>1</integer>
                    <key>TailArrow</key>
                    <string>0</string>
                </dict>
            </dict>
            <key>Tail</key>
            <dict>
                <key>ID</key>
                <integer>169393</integer>
            </dict>
        </dict>
        <dict>
            <key>Bounds</key>
            <string>{{139.37498982747391, 276.22223154703778}, {125.33333587646484, 50}}</string>
            <key>Class</key>
            <string>ShapedGraphic</string>
            <key>ID</key>
            <integer>3</integer>
            <key>Magnets</key>
            <array>
                <string>{0, 1}</string>
                <string>{0, -1}</string>
                <string>{1, 0}</string>
                <string>{-1, 0}</string>
            </array>
            <key>Shape</key>
            <string>Diamond</string>
            <key>Style</key>
            <dict>
                <key>fill</key>
                <dict>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.422927</string>
                        <key>g</key>
                        <string>1</string>
                        <key>r</key>
                        <string>1</string>
                    </dict>
                </dict>
                <key>shadow</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
            </dict>
            <key>Text</key>
            <dict>
                <key>Text</key>
                <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
\f0\fs20 \cf0 View callable found?}</string>
                <key>VerticalPad</key>
                <integer>0</integer>
            </dict>
        </dict>
        <dict>
            <key>Class</key>
            <string>LineGraphic</string>
            <key>ControlPoints</key>
            <array>
                <string>{0, 6.9840087890625}</string>
                <string>{0, -9}</string>
            </array>
            <key>FontInfo</key>
            <dict>
                <key>Color</key>
                <dict>
                    <key>w</key>
                    <string>0</string>
                </dict>
                <key>Font</key>
                <string>Helvetica</string>
                <key>Size</key>
                <real>12</real>
            </dict>
            <key>Head</key>
            <dict>
                <key>ID</key>
                <integer>169393</integer>
                <key>Info</key>
                <integer>2</integer>
            </dict>
            <key>ID</key>
            <integer>169396</integer>
            <key>Points</key>
            <array>
                <string>{202.04165903727255, 196.77777862548834}</string>
                <string>{202.04165903727255, 222}</string>
            </array>
            <key>Style</key>
            <dict>
                <key>stroke</key>
                <dict>
                    <key>Bezier</key>
                    <true/>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.0980392</string>
                        <key>g</key>
                        <string>0.0980392</string>
                        <key>r</key>
                        <string>0.0980392</string>
                    </dict>
                    <key>HeadArrow</key>
                    <string>SharpArrow</string>
                    <key>Legacy</key>
                    <true/>
                    <key>LineType</key>
                    <integer>1</integer>
                    <key>TailArrow</key>
                    <string>0</string>
                </dict>
            </dict>
            <key>Tail</key>
            <dict>
                <key>ID</key>
                <integer>169392</integer>
                <key>Info</key>
                <integer>1</integer>
            </dict>
        </dict>
        <dict>
            <key>Class</key>
            <string>LineGraphic</string>
            <key>ControlPoints</key>
            <array>
                <string>{0, 6.9840087890625}</string>
                <string>{0, -9}</string>
            </array>
            <key>FontInfo</key>
            <dict>
                <key>Color</key>
                <dict>
                    <key>w</key>
                    <string>0</string>
                </dict>
                <key>Font</key>
                <string>Helvetica</string>
                <key>Size</key>
                <real>12</real>
            </dict>
            <key>Head</key>
            <dict>
                <key>ID</key>
                <integer>169392</integer>
            </dict>
            <key>ID</key>
            <integer>169395</integer>
            <key>Points</key>
            <array>
                <string>{202.04165903727255, 142.55555725097662}</string>
                <string>{202.04165903727255, 167.77777862548834}</string>
            </array>
            <key>Style</key>
            <dict>
                <key>stroke</key>
                <dict>
                    <key>Bezier</key>
                    <true/>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.0980392</string>
                        <key>g</key>
                        <string>0.0980392</string>
                        <key>r</key>
                        <string>0.0980392</string>
                    </dict>
                    <key>HeadArrow</key>
                    <string>SharpArrow</string>
                    <key>Legacy</key>
                    <true/>
                    <key>LineType</key>
                    <integer>1</integer>
                    <key>TailArrow</key>
                    <string>0</string>
                </dict>
            </dict>
            <key>Tail</key>
            <dict>
                <key>ID</key>
                <integer>169391</integer>
                <key>Info</key>
                <integer>1</integer>
            </dict>
        </dict>
        <dict>
            <key>Class</key>
            <string>LineGraphic</string>
            <key>ControlPoints</key>
            <array>
                <string>{0, 6.9840087890625}</string>
                <string>{0, -9}</string>
            </array>
            <key>FontInfo</key>
            <dict>
                <key>Color</key>
                <dict>
                    <key>w</key>
                    <string>0</string>
                </dict>
                <key>Font</key>
                <string>Helvetica</string>
                <key>Size</key>
                <real>12</real>
            </dict>
            <key>Head</key>
            <dict>
                <key>ID</key>
                <integer>169391</integer>
            </dict>
            <key>ID</key>
            <integer>169385</integer>
            <key>Points</key>
            <array>
                <string>{202.04165903727255, 82.666667938232479}</string>
                <string>{202.04165903727255, 107.88888931274418}</string>
            </array>
            <key>Style</key>
            <dict>
                <key>stroke</key>
                <dict>
                    <key>Bezier</key>
                    <true/>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.0980392</string>
                        <key>g</key>
                        <string>0.0980392</string>
                        <key>r</key>
                        <string>0.0980392</string>
                    </dict>
                    <key>HeadArrow</key>
                    <string>SharpArrow</string>
                    <key>Legacy</key>
                    <true/>
                    <key>LineType</key>
                    <integer>1</integer>
                    <key>TailArrow</key>
                    <string>0</string>
                </dict>
            </dict>
            <key>Tail</key>
            <dict>
                <key>ID</key>
                <integer>19</integer>
                <key>Info</key>
                <integer>1</integer>
            </dict>
        </dict>
        <dict>
            <key>Bounds</key>
            <string>{{104.41666666666708, 222}, {195.24998474121094, 29}}</string>
            <key>Class</key>
            <string>ShapedGraphic</string>
            <key>ID</key>
            <integer>169393</integer>
            <key>Magnets</key>
            <array>
                <string>{0, 1}</string>
                <string>{0, -1}</string>
                <string>{1, 0}</string>
                <string>{-1, 0}</string>
            </array>
            <key>Shape</key>
            <string>Rectangle</string>
            <key>Style</key>
            <dict>
                <key>fill</key>
                <dict>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.815377</string>
                        <key>g</key>
                        <string>1</string>
                        <key>r</key>
                        <string>0.820561</string>
                    </dict>
                </dict>
                <key>shadow</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
            </dict>
            <key>Text</key>
            <dict>
                <key>Text</key>
                <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
\f0\fs20 \cf0 Look up a
\b view callable
\b0  in the
\b registry
\b0  using the
\b context
\b0  and
\b view name}</string>
                <key>VerticalPad</key>
                <integer>0</integer>
            </dict>
        </dict>
        <dict>
            <key>Bounds</key>
            <string>{{104.41666666666708, 167.77777862548834}, {195.24998474121094, 29}}</string>
            <key>Class</key>
            <string>ShapedGraphic</string>
            <key>ID</key>
            <integer>169392</integer>
            <key>Magnets</key>
            <array>
                <string>{0, 1}</string>
                <string>{0, -1}</string>
                <string>{1, 0}</string>
                <string>{-1, 0}</string>
            </array>
            <key>Shape</key>
            <string>Rectangle</string>
            <key>Style</key>
            <dict>
                <key>fill</key>
                <dict>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.815377</string>
                        <key>g</key>
                        <string>1</string>
                        <key>r</key>
                        <string>0.820561</string>
                    </dict>
                </dict>
                <key>shadow</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
            </dict>
            <key>Text</key>
            <dict>
                <key>Text</key>
                <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
\f0\b\fs20 \cf0 Traversal
\b0  locates\
the
\b context
\b0  and
\b view name}</string>
                <key>VerticalPad</key>
                <integer>0</integer>
            </dict>
        </dict>
        <dict>
            <key>Bounds</key>
            <string>{{104.41666666666708, 107.88888931274418}, {195.24998474121094, 34.666667938232422}}</string>
            <key>Class</key>
            <string>ShapedGraphic</string>
            <key>ID</key>
            <integer>169391</integer>
            <key>Magnets</key>
            <array>
                <string>{0, 1}</string>
                <string>{0, -1}</string>
                <string>{1, 0}</string>
                <string>{-1, 0}</string>
            </array>
            <key>Shape</key>
            <string>Rectangle</string>
            <key>Style</key>
            <dict>
                <key>fill</key>
                <dict>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.815377</string>
                        <key>g</key>
                        <string>1</string>
                        <key>r</key>
                        <string>0.820561</string>
                    </dict>
                </dict>
                <key>shadow</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
            </dict>
            <key>Text</key>
            <dict>
                <key>Text</key>
                <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
\f0\fs20 \cf0 Traverse the model graph\
from the
\b root
\b0  using the
\b path}</string>
                <key>VerticalPad</key>
                <integer>0</integer>
            </dict>
        </dict>
        <dict>
            <key>Bounds</key>
            <string>{{104.41666666666708, 48.000000000000043}, {195.24998474121094, 34.666667938232422}}</string>
            <key>Class</key>
            <string>ShapedGraphic</string>
            <key>ID</key>
            <integer>19</integer>
            <key>Magnets</key>
            <array>
                <string>{0, 1}</string>
                <string>{0, -1}</string>
                <string>{1, 0}</string>
                <string>{-1, 0}</string>
            </array>
            <key>Shape</key>
            <string>Rectangle</string>
            <key>Style</key>
            <dict>
                <key>fill</key>
                <dict>
                    <key>Color</key>
                    <dict>
                        <key>b</key>
                        <string>0.815377</string>
                        <key>g</key>
                        <string>1</string>
                        <key>r</key>
                        <string>0.820561</string>
                    </dict>
                </dict>
                <key>shadow</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
            </dict>
            <key>Text</key>
            <dict>
                <key>Text</key>
                <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
\f0\fs20 \cf0 Obtain a root object from the
\b root factory}</string>
                <key>VerticalPad</key>
                <integer>0</integer>
            </dict>
        </dict>
        <dict>
            <key>Bounds</key>
            <string>{{229.04165903727255, 20.000000000000934}, {90, 14}}</string>
            <key>Class</key>
            <string>ShapedGraphic</string>
            <key>FitText</key>
            <string>YES</string>
            <key>Flow</key>
            <string>Resize</string>
            <key>FontInfo</key>
            <dict>
                <key>Font</key>
                <string>Helvetica</string>
                <key>Size</key>
                <real>12</real>
            </dict>
            <key>ID</key>
            <integer>169390</integer>
            <key>Shape</key>
            <string>Rectangle</string>
            <key>Style</key>
            <dict>
                <key>fill</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
                <key>shadow</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
                <key>stroke</key>
                <dict>
                    <key>Draws</key>
                    <string>NO</string>
                </dict>
            </dict>
            <key>Text</key>
            <dict>
                <key>Pad</key>
                <integer>0</integer>
                <key>Text</key>
                <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc
\f0\b\fs24 \cf0 &lt;%Canvas%&gt;}</string>
                <key>VerticalPad</key>
                <integer>0</integer>
            </dict>
            <key>Wrap</key>
            <string>NO</string>
        </dict>
    </array>
    <key>GridInfo</key>
    <dict/>
    <key>GuidesLocked</key>
    <string>NO</string>
    <key>GuidesVisible</key>
    <string>YES</string>
    <key>HPages</key>
    <integer>1</integer>
    <key>ImageCounter</key>
    <integer>1</integer>
    <key>KeepToScale</key>
    <false/>
    <key>Layers</key>
    <array>
        <dict>
            <key>Lock</key>
            <string>NO</string>
            <key>Name</key>
            <string>Layer 1</string>
            <key>Print</key>
            <string>YES</string>
            <key>View</key>
            <string>YES</string>
        </dict>
    </array>
    <key>LayoutInfo</key>
    <dict>
        <key>Animate</key>
        <string>NO</string>
        <key>circoMinDist</key>
        <real>18</real>
        <key>circoSeparation</key>
        <real>0.0</real>
        <key>layoutEngine</key>
        <string>dot</string>
        <key>neatoSeparation</key>
        <real>0.0</real>
        <key>twopiSeparation</key>
        <real>0.0</real>
    </dict>
    <key>LinksVisible</key>
    <string>NO</string>
    <key>MagnetsVisible</key>
    <string>NO</string>
    <key>MasterSheets</key>
    <array/>
    <key>ModificationDate</key>
    <string>2014-12-01 09:19:51 +0000</string>
    <key>Modifier</key>
    <string>Steve Piercy</string>
    <key>NotesVisible</key>
    <string>NO</string>
    <key>Orientation</key>
    <integer>2</integer>
    <key>OriginVisible</key>
    <string>NO</string>
    <key>PageBreaks</key>
    <string>YES</string>
    <key>PrintInfo</key>
    <dict>
        <key>NSBottomMargin</key>
        <array>
            <string>float</string>
            <string>41</string>
        </array>
        <key>NSHorizonalPagination</key>
        <array>
            <string>coded</string>
            <string>BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG</string>
        </array>
        <key>NSLeftMargin</key>
        <array>
            <string>float</string>
            <string>18</string>
        </array>
        <key>NSPaperSize</key>
        <array>
            <string>size</string>
            <string>{612, 792}</string>
        </array>
        <key>NSPrintReverseOrientation</key>
        <array>
            <string>int</string>
            <string>0</string>
        </array>
        <key>NSRightMargin</key>
        <array>
            <string>float</string>
            <string>18</string>
        </array>
        <key>NSTopMargin</key>
        <array>
            <string>float</string>
            <string>18</string>
        </array>
    </dict>
    <key>PrintOnePage</key>
    <false/>
    <key>ReadOnly</key>
    <string>NO</string>
    <key>RowAlign</key>
    <integer>1</integer>
    <key>RowSpacing</key>
    <real>36</real>
    <key>SheetTitle</key>
    <string>Pyramid Router</string>
    <key>SmartAlignmentGuidesActive</key>
    <string>YES</string>
    <key>SmartDistanceGuidesActive</key>
    <string>YES</string>
    <key>UniqueID</key>
    <integer>1</integer>
    <key>UseEntirePage</key>
    <false/>
    <key>VPages</key>
    <integer>1</integer>
    <key>WindowInfo</key>
    <dict>
        <key>CurrentSheet</key>
        <integer>0</integer>
        <key>ExpandedCanvases</key>
        <array>
            <dict>
                <key>name</key>
                <string>Pyramid Router</string>
            </dict>
        </array>
        <key>Frame</key>
        <string>{{96, 20}, {1076, 1286}}</string>
        <key>ListView</key>
        <false/>
        <key>OutlineWidth</key>
        <integer>142</integer>
        <key>RightSidebar</key>
        <true/>
        <key>ShowRuler</key>
        <true/>
        <key>Sidebar</key>
        <true/>
        <key>SidebarWidth</key>
        <integer>120</integer>
        <key>VisibleRegion</key>
        <string>{{8, -10}, {532, 754.66666666666663}}</string>
        <key>Zoom</key>
        <real>1.5</real>
        <key>ZoomValues</key>
        <array>
            <array>
                <string>Pyramid Router</string>
                <real>1.5</real>
                <real>1</real>
            </array>
        </array>
    </dict>
</dict>
</plist>
docs/_static/pyramid_router.png
docs/_static/pyramid_router.svg
New file
@@ -0,0 +1,3 @@
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="93 11 403 558" width="403pt" height="558pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2014-12-01 09:19Z</dc:date><!-- Produced by OmniGraffle Professional 5.4.4 --></metadata><defs><font-face font-family="Helvetica" font-size="12" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="522.94922" cap-height="717.28516" ascent="770.01953" descent="-229.98047" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="SharpArrow_Marker" viewBox="-4 -4 10 8" markerWidth="10" markerHeight="8" color="#191919"><g><path d="M 5 0 L -3 -3 L 0 0 L 0 0 L -3 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Helvetica" font-size="12" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="522.94922" cap-height="717.28516" ascent="770.01953" descent="-229.98047" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Pyramid Router</title><rect fill="white" width="576" height="733"/><g><title>Layer 1</title><text transform="translate(229.04166 20)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="bold" x=".32226562" y="11" textLength="89.35547">Pyramid Router</tspan></text><rect x="104.416667" y="48" width="195.24998" height="34.666668" fill="#d2ffd0"/><rect x="104.416667" y="48" width="195.24998" height="34.666668" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(109.416667 59.333334)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x=".088371277" y="10" textLength="129.51172">Obtain a root object from the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="129.60009" y="10" textLength="55.561523">root factory</tspan></text><rect x="104.416667" y="107.88889" width="195.24998" height="34.666668" fill="#d2ffd0"/><rect x="104.416667" y="107.88889" width="195.24998" height="34.666668" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(109.416667 113.22222)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="35.557121" y="10" textLength="6.1083984">T</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="41.29931" y="10" textLength="108.393555">raverse the model graph</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="29.551262" y="22" textLength="39.458008">from the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="69.00927" y="22" textLength="19.438477">root</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="88.447746" y="22" textLength="46.142578"> using the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="134.590324" y="22" textLength="21.108398">path</tspan></text><rect x="104.416667" y="167.77778" width="195.24998" height="29" fill="#d2ffd0"/><rect x="104.416667" y="167.77778" width="195.24998" height="29" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(109.416667 170.27778)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="53.428215" y="10" textLength="6.1083984">T</tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="58.98974" y="10" textLength="38.36914">raversal</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="97.35888" y="10" textLength="34.46289"> locates</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="30.093254" y="22" textLength="16.6796875">the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="46.772942" y="22" textLength="35.561523">context</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="82.334465" y="22" textLength="22.241211"> and </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="104.575676" y="22" textLength="50.581055">view name</tspan></text><rect x="104.416667" y="222" width="195.24998" height="29" fill="#d2ffd0"/><rect x="104.416667" y="222" width="195.24998" height="29" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(109.416667 224.5)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.3471603" y="10" textLength="46.704102">Look up a </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="52.051262" y="10" textLength="61.14746">view callable</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="113.19872" y="10" textLength="30.019531"> in the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="143.21825" y="10" textLength="36.68457">registry</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="179.90282" y="10" textLength="2.7783203"> </tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.750969" y="22" textLength="43.364258">using the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="60.115227" y="22" textLength="35.561523">context</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="95.67675" y="22" textLength="22.241211"> and </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="117.91796" y="22" textLength="50.581055">view name</tspan></text><path d="M 202.04166 82.66667 C 202.04166 87.86648 202.04166 94.31586 202.04166 100.98615" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 202.04166 142.55556 C 202.04166 147.75537 202.04166 154.20475 202.04166 160.87504" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 202.04166 196.77778 C 202.04166 201.97759 202.04166 208.42697 202.04166 215.09726" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 202.04166 276.22223 L 264.70833 301.22223 L 202.04166 326.22223 L 139.37499 301.22223 Z" fill="#ffff6c"/><path d="M 202.04166 276.22223 L 264.70833 301.22223 L 202.04166 326.22223 L 139.37499 301.22223 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(161.29499 288.72223)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="12.905763" y="10" textLength="6.669922">V</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.399903" y="10" textLength="54.472656">iew callable </tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="26.707032" y="22" textLength="30.585938">found?</tspan></text><path d="M 202.04166 251 C 202.04166 256.19981 202.04166 262.6492 202.04166 269.31949" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="327.47912" y="286.72223" width="156.62497" height="29" fill="#fec0c1"/><rect x="327.47912" y="286.72223" width="156.62497" height="29" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(332.47912 295.22223)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="10.89061" y="10" textLength="49.472656">Return the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="60.363266" y="10" textLength="59.42871">Not Found V</tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="119.616196" y="10" textLength="16.118164">iew</tspan></text><path d="M 265.20833 301.22223 C 297.4314 301.22223 294.2168 301.22223 320.5783 301.22223" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(288.07626 286.8889)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" fill="black" x=".33007812" y="11" textLength="15.339844">No</tspan></text><path d="M 202.04165 360.44447 L 280.35414 398.8889 L 202.04165 437.33333 L 123.72917 398.8889 Z" fill="#ffff6c"/><path d="M 202.04165 360.44447 L 280.35414 398.8889 L 202.04165 437.33333 L 123.72917 398.8889 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(149.87354 380.12)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.495594" y="10" textLength="77.25586">Current user has </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x=".9462776" y="22" textLength="62.773438">authorization</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="63.719715" y="22" textLength="45.581055"> to invoke </tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="14.26659" y="34" textLength="78.935547">the view callable?</tspan></text><path d="M 202.04165 326.72223 C 202.04165 332.1894 202.04165 344.2467 202.04165 353.54354" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(176.70832 334.24978)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" fill="black" x=".21191406" y="11" textLength="8.0039062">Y</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" fill="black" x="7.114258" y="11" textLength="12.673828">es</tspan></text><path d="M 202.04165 437.33333 C 202.04165 442.81278 202.04166 455.21977 202.04166 464.65762" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(180.11595 444)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".21191406" y="11" textLength="8.0039062">Y</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="7.114258" y="11" textLength="12.673828">es</tspan></text><rect x="327.47912" y="384.38889" width="156.62497" height="29" fill="#fec0c1"/><rect x="327.47912" y="384.38889" width="156.62497" height="29" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(332.47912 392.88889)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.439926" y="10" textLength="49.472656">Return the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="60.912582" y="10" textLength="58.330078">Forbidden V</tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="119.06688" y="10" textLength="16.118164">iew</tspan></text><path d="M 280.85417 398.88889 C 312.9685 398.88889 296.55343 398.88889 320.57617 398.88889" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(296.21563 384.55555)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" x=".33007812" y="11" textLength="15.339844">No</tspan></text><rect x="104.416667" y="471.55558" width="195.24998" height="29" fill="#ffff6c"/><rect x="104.416667" y="471.55558" width="195.24998" height="29" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(109.416667 474.05558)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="36.201653" y="10" textLength="48.920898">Invoke the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="85.12255" y="10" textLength="61.14746">view callable</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="146.27001" y="10" textLength="2.7783203">,</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="35.100578" y="22" textLength="70.585938">which returns a </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="105.686516" y="22" textLength="44.46289">response</tspan></text><rect x="104.416667" y="528.77776" width="195.24998" height="29" fill="#dfbeff"/><rect x="104.416667" y="528.77776" width="195.24998" height="29" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(109.416667 537.27776)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="45.65722" y="10" textLength="49.472656">Return the </tspan><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="95.129875" y="10" textLength="44.46289">response</tspan></text><path d="M 202.04166 501.05558 C 202.04166 506.35088 202.04166 514.3792 202.04166 521.8749" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g></g></svg>
docs/_themes
@@ -1 +1 @@
Subproject commit 3bec9280a6cedb15e97e5899021aa8d723c25388
Subproject commit 382cba80fbd6a7424818d17ec63ca520e485f108
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/exceptions.rst
@@ -5,14 +5,14 @@
.. automodule:: pyramid.exceptions
  .. autoclass:: BadCSRFToken
  .. autoexception:: BadCSRFToken
  .. autoclass:: PredicateMismatch
  .. autoexception:: PredicateMismatch
  .. autoclass:: Forbidden
  .. autoexception:: Forbidden
  .. autoclass:: NotFound
  .. autoexception:: NotFound
  .. autoclass:: ConfigurationError
  .. autoexception:: ConfigurationError
  .. autoclass:: URLDecodeError
  .. autoexception:: URLDecodeError
docs/api/httpexceptions.rst
@@ -13,96 +13,96 @@
  .. autofunction:: exception_response
  .. autoclass:: HTTPException
  .. autoexception:: HTTPException
  .. autoclass:: HTTPOk
  .. autoexception:: HTTPOk
  .. autoclass:: HTTPRedirection
  .. autoexception:: HTTPRedirection
  .. autoclass:: HTTPError
  .. autoexception:: HTTPError
  .. autoclass:: HTTPClientError
  .. autoexception:: HTTPClientError
  .. autoclass:: HTTPServerError
  .. autoexception:: HTTPServerError
  .. autoclass:: HTTPCreated
  .. autoexception:: HTTPCreated
  .. autoclass:: HTTPAccepted
  .. autoexception:: HTTPAccepted
  .. autoclass:: HTTPNonAuthoritativeInformation
  .. autoexception:: HTTPNonAuthoritativeInformation
  .. autoclass:: HTTPNoContent
  .. autoexception:: HTTPNoContent
  .. autoclass:: HTTPResetContent
  .. autoexception:: HTTPResetContent
  .. autoclass:: HTTPPartialContent
  .. autoexception:: HTTPPartialContent
  .. autoclass:: HTTPMultipleChoices
  .. autoexception:: HTTPMultipleChoices
  .. autoclass:: HTTPMovedPermanently
  .. autoexception:: HTTPMovedPermanently
  .. autoclass:: HTTPFound
  .. autoexception:: HTTPFound
  .. autoclass:: HTTPSeeOther
  .. autoexception:: HTTPSeeOther
  .. autoclass:: HTTPNotModified
  .. autoexception:: HTTPNotModified
  .. autoclass:: HTTPUseProxy
  .. autoexception:: HTTPUseProxy
  .. autoclass:: HTTPTemporaryRedirect
  .. autoexception:: HTTPTemporaryRedirect
  .. autoclass:: HTTPBadRequest
  .. autoexception:: HTTPBadRequest
  .. autoclass:: HTTPUnauthorized
  .. autoexception:: HTTPUnauthorized
  .. autoclass:: HTTPPaymentRequired
  .. autoexception:: HTTPPaymentRequired
  .. autoclass:: HTTPForbidden
  .. autoexception:: HTTPForbidden
  .. autoclass:: HTTPNotFound
  .. autoexception:: HTTPNotFound
  .. autoclass:: HTTPMethodNotAllowed
  .. autoexception:: HTTPMethodNotAllowed
  .. autoclass:: HTTPNotAcceptable
  .. autoexception:: HTTPNotAcceptable
  .. autoclass:: HTTPProxyAuthenticationRequired
  .. autoexception:: HTTPProxyAuthenticationRequired
  .. autoclass:: HTTPRequestTimeout
  .. autoexception:: HTTPRequestTimeout
  .. autoclass:: HTTPConflict
  .. autoexception:: HTTPConflict
  .. autoclass:: HTTPGone
  .. autoexception:: HTTPGone
  .. autoclass:: HTTPLengthRequired
  .. autoexception:: HTTPLengthRequired
  .. autoclass:: HTTPPreconditionFailed
  .. autoexception:: HTTPPreconditionFailed
  .. autoclass:: HTTPRequestEntityTooLarge
  .. autoexception:: HTTPRequestEntityTooLarge
  .. autoclass:: HTTPRequestURITooLong
  .. autoexception:: HTTPRequestURITooLong
  .. autoclass:: HTTPUnsupportedMediaType
  .. autoexception:: HTTPUnsupportedMediaType
  .. autoclass:: HTTPRequestRangeNotSatisfiable
  .. autoexception:: HTTPRequestRangeNotSatisfiable
  .. autoclass:: HTTPExpectationFailed
  .. autoexception:: HTTPExpectationFailed
  .. autoclass:: HTTPUnprocessableEntity
  .. autoexception:: HTTPUnprocessableEntity
  .. autoclass:: HTTPLocked
  .. autoexception:: HTTPLocked
  .. autoclass:: HTTPFailedDependency
  .. autoexception:: HTTPFailedDependency
  .. autoclass:: HTTPInternalServerError
  .. autoexception:: HTTPInternalServerError
  .. autoclass:: HTTPNotImplemented
  .. autoexception:: HTTPNotImplemented
  .. autoclass:: HTTPBadGateway
  .. autoexception:: HTTPBadGateway
  .. autoclass:: HTTPServiceUnavailable
  .. autoexception:: HTTPServiceUnavailable
  .. autoclass:: HTTPGatewayTimeout
  .. autoexception:: HTTPGatewayTimeout
  .. autoclass:: HTTPVersionNotSupported
  .. autoexception:: HTTPVersionNotSupported
  .. autoclass:: HTTPInsufficientStorage
  .. autoexception:: HTTPInsufficientStorage
docs/api/index.rst
New file
@@ -0,0 +1,12 @@
.. _html_api_documentation:
API Documentation
=================
Comprehensive reference material for every public API exposed by :app:`Pyramid`:
.. toctree::
   :maxdepth: 1
   :glob:
   *
docs/api/interfaces.rst
@@ -56,6 +56,9 @@
  .. autointerface:: IRenderer
     :members:
  .. autointerface:: IResponseFactory
     :members:
  .. autointerface:: IViewMapperFactory
     :members:
@@ -86,3 +89,5 @@
  .. autointerface:: IResourceURL
     :members:
  .. autointerface:: ICacheBuster
     :members:
docs/api/registry.rst
@@ -14,6 +14,18 @@
     accessed as ``request.registry.settings`` or
     ``config.registry.settings`` in a typical Pyramid application.
   .. attribute:: package_name
     .. versionadded:: 1.6
     When a registry is set up (or created) by a :term:`Configurator`, this
     attribute will be the shortcut for
     :attr:`pyramid.config.Configurator.package_name`.
     This attribute is often accessed as ``request.registry.package_name`` or
     ``config.registry.package_name`` or ``config.package_name``
     in a typical Pyramid application.
   .. attribute:: introspector
     .. versionadded:: 1.3
docs/api/request.rst
@@ -167,37 +167,40 @@
      .. versionadded:: 1.5
      A property which returns the userid of the currently authenticated user
      or ``None`` if there is no :term:`authentication policy` in effect or
      there is no currently authenticated user.  This differs from
      :attr:`~pyramid.request.Request.unauthenticated_userid`, because the
      effective authentication policy will have ensured that a record
      associated with the userid exists in persistent storage; if it has
      not, this value will be ``None``.
      A property which returns the :term:`userid` of the currently
      authenticated user or ``None`` if there is no :term:`authentication
      policy` in effect or there is no currently authenticated user.  This
      differs from :attr:`~pyramid.request.Request.unauthenticated_userid`,
      because the effective authentication policy will have ensured that a
      record associated with the :term:`userid` exists in persistent storage;
      if it has not, this value will be ``None``.
   .. attribute:: unauthenticated_userid
      .. versionadded:: 1.5
      A property which returns a value which represents the *claimed* (not
      verified) user id of the credentials present in the request. ``None`` if
      there is no :term:`authentication policy` in effect or there is no user
      data associated with the current request.  This differs from
      :attr:`~pyramid.request.Request.authenticated_userid`, because the
      effective authentication policy will not ensure that a record associated
      with the userid exists in persistent storage.  Even if the userid
      does not exist in persistent storage, this value will be the value
      of the userid *claimed* by the request data.
      verified) :term:`userid` of the credentials present in the
      request. ``None`` if there is no :term:`authentication policy` in effect
      or there is no user data associated with the current request.  This
      differs from :attr:`~pyramid.request.Request.authenticated_userid`,
      because the effective authentication policy will not ensure that a
      record associated with the :term:`userid` exists in persistent storage.
      Even if the :term:`userid` does not exist in persistent storage, this
      value will be the value of the :term:`userid` *claimed* by the request
      data.
   .. attribute:: effective_principals
      .. versionadded:: 1.5
      A property which returns the list of 'effective' :term:`principal`
      identifiers for this request.  This will include the userid of the
      currently authenticated user if a user is currently authenticated. If no
      :term:`authentication policy` is in effect, this will return a sequence
      containing only the :attr:`pyramid.security.Everyone` principal.
      identifiers for this request.  This list typically includes the
      :term:`userid` of the currently authenticated user if a user is
      currently authenticated, but this depends on the
      :term:`authentication policy` in effect.  If no :term:`authentication
      policy` is in effect, this will return a sequence containing only the
      :attr:`pyramid.security.Everyone` principal.
   .. method:: invoke_subrequest(request, use_tweens=False)
@@ -366,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/security.rst
@@ -16,7 +16,7 @@
.. autofunction:: forget
.. autofunction:: remember
.. autofunction:: remember(request, userid, **kwargs)
Authorization API Functions
---------------------------
docs/api/static.rst
@@ -9,3 +9,17 @@
     :members:
     :inherited-members:
  .. autoclass:: PathSegmentCacheBuster
     :members:
  .. autoclass:: QueryStringCacheBuster
     :members:
  .. autoclass:: PathSegmentMd5CacheBuster
     :members:
  .. autoclass:: QueryStringMd5CacheBuster
     :members:
  .. autoclass:: QueryStringConstantCacheBuster
     :members:
docs/conf.py
@@ -74,7 +74,7 @@
        'http://docs.pylonsproject.org/projects/deform/en/latest',
    None),
    'sqla': ('http://docs.sqlalchemy.org/en/latest', None),
    'who': ('http://docs.repoze.org/who/latest', None),
    'who': ('http://repozewho.readthedocs.org/en/latest', None),
    'python': ('http://docs.python.org', None),
    'python3': ('http://docs.python.org/3', None),
    'tstring':
docs/glossary.rst
@@ -16,6 +16,11 @@
     An object which, provided a :term:`WSGI` environment as a single
     positional argument, returns a Pyramid-compatible request.
   response factory
     An object which, provided a :term:`request` as a single positional
     argument, returns a Pyramid-compatible response. See
     :class:`pyramid.interfaces.IResponseFactory`.
   response
     An object returned by a :term:`view callable` that represents response
     data returned to the requesting user agent.  It must implement the
@@ -286,13 +291,22 @@
     :term:`authorization policy`.
   principal
     A *principal* is a string or unicode object representing a userid
     or a group id.  It is provided by an :term:`authentication
     policy`.  For example, if a user had the user id "bob", and Bob
     was part of two groups named "group foo" and "group bar", the
     request might have information attached to it that would
     indicate that Bob was represented by three principals: "bob",
     "group foo" and "group bar".
     A *principal* is a string or unicode object representing an
     entity, typically a user or group.  Principals are provided by an
     :term:`authentication policy`.  For example, if a user had the
     :term:`userid` `"bob"`, and was part of two groups named `"group foo"`
     and "group bar", the request might have information attached to
     it that would indicate that Bob was represented by three
     principals: `"bob"`, `"group foo"` and `"group bar"`.
   userid
     A *userid* is a string or unicode object used to identify and
     authenticate a real-world user (or client).  A userid is
     supplied to an :term:`authentication policy` in order to discover
     the user's :term:`principals <principal>`.  The default behavior
     of the authentication policies :app:`Pyramid` provides is to
     return the user's userid as a principal, but this is not strictly
     necessary in custom policies that define their principals differently.
   authorization policy
     An authorization policy in :app:`Pyramid` terms is a bit of
@@ -749,9 +763,16 @@
     made.  For example the word "java" might be translated
     differently if the translation domain is "programming-languages"
     than would be if the translation domain was "coffee".  A
     translation domain is represnted by a collection of ``.mo`` files
     translation domain is represented by a collection of ``.mo`` files
     within one or more :term:`translation directory` directories.
   Translation Context
     A string representing the "context" in which a translation was
     made within a given :term:`translation domain`. See the gettext
     documentation, `11.2.5 Using contexts for solving ambiguities
     <https://www.gnu.org/software/gettext/manual/gettext.html#Contexts>`_
     for more information.
   Translator
     A callable which receives a :term:`translation string` and returns a
     translated Unicode object for the purposes of internationalization.  A
docs/index.rst
@@ -135,8 +135,6 @@
   tutorials/wiki/index.rst
   tutorials/modwsgi/index.rst
.. _html_api_documentation:
API Documentation
=================
@@ -146,6 +144,7 @@
   :maxdepth: 1
   :glob:
   api/index
   api/*
Change History
docs/narr/MyProject/myproject/tests.py
@@ -15,3 +15,40 @@
        request = testing.DummyRequest()
        info = my_view(request)
        self.assertEqual(info['project'], 'MyProject')
class ViewIntegrationTests(unittest.TestCase):
    def setUp(self):
        """ This sets up the application registry with the
        registrations your application declares in its ``includeme``
        function.
        """
        self.config = testing.setUp()
        self.config.include('myproject')
    def tearDown(self):
        """ Clear out the application registry """
        testing.tearDown()
    def test_my_view(self):
        from myproject.views import my_view
        request = testing.DummyRequest()
        result = my_view(request)
        self.assertEqual(result.status, '200 OK')
        body = result.app_iter[0]
        self.assertTrue('Welcome to' in body)
        self.assertEqual(len(result.headerlist), 2)
        self.assertEqual(result.headerlist[0],
                         ('Content-Type', 'text/html; charset=UTF-8'))
        self.assertEqual(result.headerlist[1], ('Content-Length',
                                                str(len(body))))
class FunctionalTests(unittest.TestCase):
    def setUp(self):
        from myproject import main
        app = main({})
        from webtest import TestApp
        self.testapp = TestApp(app)
    def test_root(self):
        res = self.testapp.get('/', status=200)
        self.assertTrue('Pyramid' in res.body)
docs/narr/MyProject/setup.py
@@ -1,18 +1,30 @@
import os
"""Setup for the MyProject package.
"""
import os
from setuptools import setup, find_packages
here = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(here, 'README.txt')) as f:
    README = f.read()
with open(os.path.join(here, 'CHANGES.txt')) as f:
    CHANGES = f.read()
requires = [
HERE = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(HERE, 'README.txt')) as fp:
    README = fp.read()
with open(os.path.join(HERE, 'CHANGES.txt')) as fp:
    CHANGES = fp.read()
REQUIRES = [
    'pyramid',
    'pyramid_chameleon',
    'pyramid_debugtoolbar',
    'waitress',
    ]
TESTS_REQUIRE = [
    'webtest'
    ]
setup(name='MyProject',
@@ -20,11 +32,11 @@
      description='MyProject',
      long_description=README + '\n\n' + CHANGES,
      classifiers=[
        "Programming Language :: Python",
        "Framework :: Pyramid",
        "Topic :: Internet :: WWW/HTTP",
        "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
        ],
          'Programming Language :: Python',
          'Framework :: Pyramid',
          'Topic :: Internet :: WWW/HTTP',
          'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
      ],
      author='',
      author_email='',
      url='',
@@ -32,11 +44,10 @@
      packages=find_packages(),
      include_package_data=True,
      zip_safe=False,
      install_requires=requires,
      tests_require=requires,
      test_suite="myproject",
      install_requires=REQUIRES,
      tests_require=TESTS_REQUIRE,
      test_suite='myproject',
      entry_points="""\
      [paste.app_factory]
      main = myproject:main
      """,
      )
      """)
docs/narr/assets.rst
@@ -276,15 +276,238 @@
``name`` argument to :meth:`~pyramid.config.Configurator.add_static_view` is
a URL), while keeping static media package-internal and served by the
development webserver during development (if the ``name`` argument to
:meth:`~pyramid.config.Configurator.add_static_view` is a URL prefix).  To
create such a circumstance, we suggest using the
:attr:`pyramid.registry.Registry.settings` API in conjunction with a setting
in the application ``.ini`` file named ``media_location``.  Then set the
value of ``media_location`` to either a prefix or a URL depending on whether
the application is being run in development or in production (use a different
``.ini`` file for production than you do for development).  This is just a
suggestion for a pattern; any setting name other than ``media_location``
could be used.
:meth:`~pyramid.config.Configurator.add_static_view` is a URL prefix).
For example, we may define a :ref:`custom setting <adding_a_custom_setting>`
named ``media_location`` which we can set to an external URL in production
when our assets are hosted on a CDN.
.. code-block:: python
   :linenos:
   media_location = settings.get('media_location', 'static')
   config = Configurator(settings=settings)
   config.add_static_view(path='myapp:static', name=media_location)
Now we can optionally define the setting in our ini file:
.. code-block:: ini
   :linenos:
   # production.ini
   [app:main]
   use = egg:myapp#main
   media_location = http://static.example.com/
It is also possible to serve assets that live outside of the source by
referring to an absolute path on the filesystem. There are two ways to
accomplish this.
First, :meth:`~pyramid.config.Configurator.add_static_view`
supports taking an absolute path directly instead of an asset spec. This works
as expected, looking in the file or folder of files and serving them up at
some URL within your application or externally. Unfortunately, this technique
has a drawback that it is not possible to use the
:meth:`~pyramid.request.Request.static_url` method to generate URLs, since it
works based on an asset spec.
The second approach, available in Pyramid 1.6+, uses the asset overriding
APIs described in the :ref:`overriding_assets_section` section. It is then
possible to configure a "dummy" package which then serves its file or folder
from an absolute path.
.. code-block:: python
   config.add_static_view(path='myapp:static_images', name='static')
   config.override_asset(to_override='myapp:static_images/',
                         override_with='/abs/path/to/images/')
From this configuration it is now possible to use
:meth:`~pyramid.request.Request.static_url` to generate URLs to the data
in the folder by doing something like
``request.static_url('myapp:static_images/foo.png')``. While it is not
necessary that the ``static_images`` file or folder actually exist in the
``myapp`` package, it is important that the ``myapp`` portion points to a
valid package. If the folder does exist then the overriden folder is given
priority if the file's name exists in both locations.
.. index::
   single: Cache Busting
.. _cache_busting:
Cache Busting
-------------
.. versionadded:: 1.6
In order to maximize performance of a web application, you generally want to
limit the number of times a particular client requests the same static asset.
Ideally a client would cache a particular static asset "forever", requiring
it to be sent to the client a single time.  The HTTP protocol allows you to
send headers with an HTTP response that can instruct a client to cache a
particular asset for an amount of time.  As long as the client has a copy of
the asset in its cache and that cache hasn't expired, the client will use the
cached copy rather than request a new copy from the server.  The drawback to
sending cache headers to the client for a static asset is that at some point
the static asset may change, and then you'll want the client to load a new copy
of the asset.  Under normal circumstances you'd just need to wait for the
client's cached copy to expire before they get the new version of the static
resource.
A commonly used workaround to this problem is a technique known as "cache
busting".  Cache busting schemes generally involve generating a URL for a
static asset that changes when the static asset changes.  This way headers can
be sent along with the static asset instructing the client to cache the asset
for a very long time.  When a static asset is changed, the URL used to refer to
it in a web page also changes, so the client sees it as a new resource and
requests a copy, regardless of any caching policy set for the resource's old
URL.
:app:`Pyramid` can be configured to produce cache busting URLs for static
assets by passing the optional argument, ``cachebust`` to
:meth:`~pyramid.config.Configurator.add_static_view`:
.. code-block:: python
   :linenos:
   # config is an instance of pyramid.config.Configurator
   config.add_static_view(name='static', path='mypackage:folder/static',
                          cachebust=True)
Setting the ``cachebust`` argument instructs :app:`Pyramid` to use a cache
busting scheme which adds the md5 checksum for a static asset as a path segment
in the asset's URL:
.. code-block:: python
   :linenos:
   js_url = request.static_url('mypackage:folder/static/js/myapp.js')
   # Returns: 'http://www.example.com/static/c9658b3c0a314a1ca21e5988e662a09e/js/myapp.js`
When the asset changes, so will its md5 checksum, and therefore so will its
URL.  Supplying the ``cachebust`` argument also causes the static view to set
headers instructing clients to cache the asset for ten years, unless the
``max_cache_age`` argument is also passed, in which case that value is used.
.. note::
   md5 checksums are cached in RAM so if you change a static resource without
   restarting your application, you may still generate URLs with a stale md5
   checksum.
Disabling the Cache Buster
~~~~~~~~~~~~~~~~~~~~~~~~~~
It can be useful in some situations (e.g. development) to globally disable all
configured cache busters without changing calls to
:meth:`~pyramid.config.Configurator.add_static_view`.  To do this set the
``PYRAMID_PREVENT_CACHEBUST`` environment variable or the
``pyramid.prevent_cachebust`` configuration value to a true value.
Customizing the Cache Buster
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Revisiting from the previous section:
.. code-block:: python
   :linenos:
   # config is an instance of pyramid.config.Configurator
   config.add_static_view(name='static', path='mypackage:folder/static',
                          cachebust=True)
Setting ``cachebust`` to ``True`` instructs :app:`Pyramid` to use a default
cache busting implementation that should work for many situations.  The
``cachebust`` may be set to any object that implements the interface,
:class:`~pyramid.interfaces.ICacheBuster`.  The above configuration is exactly
equivalent to:
.. code-block:: python
   :linenos:
   from pyramid.static import PathSegmentMd5CacheBuster
   # config is an instance of pyramid.config.Configurator
   config.add_static_view(name='static', path='mypackage:folder/static',
                          cachebust=PathSegmentMd5CacheBuster())
:app:`Pyramid` includes a handful of ready to use cache buster implementations:
:class:`~pyramid.static.PathSegmentMd5CacheBuster`, which inserts an md5
checksum token in the path portion of the asset's URL,
:class:`~pyramid.static.QueryStringMd5CacheBuster`, which adds an md5 checksum
token to the query string of the asset's URL, and
:class:`~pyramid.static.QueryStringConstantCacheBuster`, which adds an
arbitrary token you provide to the query string of the asset's URL.
In order to implement your own cache buster, you can write your own class from
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 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 PathSegmentCacheBuster
   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
       get the current commit's SHA1 to use as the cache bust token.
       """
       def __init__(self):
           here = os.path.dirname(os.path.abspath(__file__))
           self.sha1 = subprocess.check_output(
               ['git', 'rev-parse', 'HEAD'],
               cwd=here).strip()
       def tokenize(self, pathspec):
           return self.sha1
Choosing a Cache Buster
~~~~~~~~~~~~~~~~~~~~~~~
The default cache buster implementation,
:class:`~pyramid.static.PathSegmentMd5CacheBuster`, works very well assuming
that you're using :app:`Pyramid` to serve your static assets.  The md5 checksum
is fine grained enough that browsers should only request new versions of
specific assets that have changed.  Many caching HTTP proxies will fail to
cache a resource if the URL contains a query string.  In general, therefore,
you should prefer a cache busting strategy which modifies the path segment to
a strategy which adds a query string.
It is possible, however, that your static assets are being served by another
web server or externally on a CDN.  In these cases modifying the path segment
for a static asset URL would cause the external service to fail to find the
asset, causing your customer to get a 404.  In these cases you would need to
fall back to a cache buster which adds a query string.  It is even possible
that there isn't a copy of your static assets available to the :app:`Pyramid`
application, so a cache busting implementation that generates md5 checksums
would fail since it can't access the assets.  In such a case,
:class:`~pyramid.static.QueryStringConstantCacheBuster` is a reasonable
fallback.  The following code would set up a cachebuster that just uses the
time at start up as a cachebust token:
.. code-block:: python
   :linenos:
   import time
   from pyramid.static import QueryStringConstantCacheBuster
   config.add_static_view(
       name='http://mycdn.example.com/',
       path='mypackage:static',
       cachebust=QueryStringConstantCacheBuster(str(time.time())))
.. index::
   single: static assets view
@@ -526,3 +749,6 @@
:func:`pkg_resources.get_resource_string` APIs will obtain an overridden file
when an override is used.
As of Pyramid 1.6, it is also possible to override an asset by supplying an
absolute path to a file or directory. This may be useful if the assets are
not distributed as part of a Python package.
docs/narr/commandline.rst
@@ -146,7 +146,7 @@
.. code-block:: text
    $ $VENV/bin starter/development.ini#main
    $ $VENV/bin/pshell starter/development.ini#main
    Python 2.6.5 (r265:79063, Apr 29 2010, 00:31:32) 
    [GCC 4.4.3] on linux2
    Type "help" for more information.
@@ -312,24 +312,60 @@
   :linenos:
   $ $VENV/bin/proutes development.ini
   Name            Pattern                        View
   ----            -------                        ----
   home            /                              <function my_view>
   home2           /                              <function my_view>
   another         /another                       None
   static/         static/*subpath                <static_view object>
   catchall        /*subpath                      <function static_view>
   Name                       Pattern                     View
   ----                       -------                     ----
   debugtoolbar               /_debug_toolbar/*subpath    <wsgiapp>                                     *
   __static/                  /static/*subpath            dummy_starter:static/                         *
   __static2/                 /static2/*subpath           /var/www/static/                              *
   __pdt_images/              /pdt_images/*subpath        pyramid_debugtoolbar:static/img/              *
   a                          /                           <unknown>                                     *
   no_view_attached           /                           <unknown>                                     *
   route_and_view_attached    /                           app1.standard_views.route_and_view_attached   *
   method_conflicts           /conflicts                  app1.standard_conflicts                       <route mismatch>
   multiview                  /multiview                  app1.standard_views.multiview                 GET,PATCH
   not_post                   /not_post                   app1.standard_views.multview                  !POST,*
``proutes`` generates a table with three columns: *Name*, *Pattern*,
``proutes`` generates a table with four columns: *Name*, *Pattern*, *Method*,
and *View*.  The items listed in the
Name column are route names, the items listed in the Pattern column are route
patterns, and the items listed in the View column are representations of the
view callable that will be invoked when a request matches the associated
route pattern.  The view column may show ``None`` if no associated view
route pattern.  The view column may show ``<unknown>`` if no associated view
callable could be found.  If no routes are configured within your
application, nothing will be printed to the console when ``proutes``
is executed.
It is convenient when using the ``proutes`` often to configure which columns
and the order you would like to view them. To facilitate this, ``proutes`` will
look for a special ``[proutes]`` section in your INI file and use those as
defaults.
For example you may remove request method and place the view first:
.. code-block:: text
  :linenos:
    [proutes]
    format = view
             name
             pattern
You can also separate the formats with commas or spaces:
.. code-block:: text
  :linenos:
    [proutes]
    format = view name pattern
    [proutes]
    format = view, name, pattern
If you want to temporarily configure the columns and order there is the
``--format`` which is a comma separated list of columns you want to include. The
current available formats are ``name``, ``pattern``, ``view``, and ``method``.
.. index::
   pair: tweens; printing
   single: ptweens
docs/narr/configuration.rst
@@ -17,6 +17,10 @@
referred to within this documentation as "configuration"; you are configuring
:app:`Pyramid` to call the code that makes up your application.
.. seealso::
   For information on ``.ini`` files for Pyramid applications see the
   :ref:`startup_chapter` chapter.
There are two ways to configure a :app:`Pyramid` application:
:term:`imperative configuration` and :term:`declarative configuration`.  Both
are described below.
docs/narr/environment.rst
@@ -13,7 +13,6 @@
   single: reload settings
   single: default_locale_name
   single: environment variables
   single: Mako environment settings
   single: ini file settings
   single: PasteDeploy settings
@@ -154,6 +153,28 @@
+=================================+==================================+
| ``PYRAMID_PREVENT_HTTP_CACHE``  |  ``pyramid.prevent_http_cache``  |
|                                 |  or ``prevent_http_cache``       |
|                                 |                                  |
|                                 |                                  |
+---------------------------------+----------------------------------+
Preventing Cache Busting
------------------------
Prevent the ``cachebust`` static view configuration argument from having any
effect globally in this process when this value is true.  No cache buster will
be configured or used when this is true.
.. versionadded:: 1.6
.. seealso::
    See also :ref:`cache_busting`.
+---------------------------------+----------------------------------+
| Environment Variable Name       | Config File Setting Name         |
+=================================+==================================+
| ``PYRAMID_PREVENT_CACHEBUST``   |  ``pyramid.prevent_cachebust``   |
|                                 |  or ``prevent_cachebust``        |
|                                 |                                  |
|                                 |                                  |
+---------------------------------+----------------------------------+
@@ -395,153 +416,6 @@
       config = Configurator(settings=settings)
It is fine to use both or either form.
.. _mako_template_renderer_settings:
Mako Template Render Settings
-----------------------------
Mako derives additional settings to configure its template renderer that
should be set when using it. Many of these settings are optional and only need
to be set if they should be different from the default. The Mako Template
Renderer uses a subclass of Mako's `template lookup
<http://www.makotemplates.org/docs/usage.html#usage_lookup>`_ and accepts
several arguments to configure it.
Mako Directories
~~~~~~~~~~~~~~~~
The value(s) supplied here are passed in as the template directories. They
should be in :term:`asset specification` format, for example:
``my.package:templates``.
+-----------------------------+
| Config File Setting Name    |
+=============================+
|  ``mako.directories``       |
|                             |
|                             |
|                             |
+-----------------------------+
Mako Module Directory
~~~~~~~~~~~~~~~~~~~~~
The value supplied here tells Mako where to store compiled Mako templates. If
omitted, compiled templates will be stored in memory. This value should be an
absolute path, for example: ``%(here)s/data/templates`` would use a directory
called ``data/templates`` in the same parent directory as the INI file.
+-----------------------------+
| Config File Setting Name    |
+=============================+
|  ``mako.module_directory``  |
|                             |
|                             |
|                             |
+-----------------------------+
Mako Input Encoding
~~~~~~~~~~~~~~~~~~~
The encoding that Mako templates are assumed to have. By default this is set
to ``utf-8``. If you wish to use a different template encoding, this value
should be changed accordingly.
+-----------------------------+
| Config File Setting Name    |
+=============================+
|  ``mako.input_encoding``    |
|                             |
|                             |
|                             |
+-----------------------------+
Mako Error Handler
~~~~~~~~~~~~~~~~~~
A callable (or a :term:`dotted Python name` which names a callable) which is
called whenever Mako compile or runtime exceptions occur. The callable is
passed the current context as well as the exception. If the callable returns
True, the exception is considered to be handled, else it is re-raised after
the function completes. Is used to provide custom error-rendering functions.
+-----------------------------+
| Config File Setting Name    |
+=============================+
|  ``mako.error_handler``     |
|                             |
|                             |
|                             |
+-----------------------------+
Mako Default Filters
~~~~~~~~~~~~~~~~~~~~
List of string filter names that will be applied to all Mako expressions.
+-----------------------------+
| Config File Setting Name    |
+=============================+
|  ``mako.default_filters``   |
|                             |
|                             |
|                             |
+-----------------------------+
Mako Import
~~~~~~~~~~~
String list of Python statements, typically individual "import" lines, which
will be placed into the module level preamble of all generated Python modules.
+-----------------------------+
| Config File Setting Name    |
+=============================+
|  ``mako.imports``           |
|                             |
|                             |
|                             |
+-----------------------------+
Mako Strict Undefined
~~~~~~~~~~~~~~~~~~~~~
``true`` or ``false``, representing the "strict undefined" behavior of Mako
(see `Mako Context Variables
<http://www.makotemplates.org/docs/runtime.html#context-variables>`_).  By
default, this is ``false``.
+-----------------------------+
| Config File Setting Name    |
+=============================+
|  ``mako.strict_undefined``  |
|                             |
|                             |
|                             |
+-----------------------------+
Mako Preprocessor
~~~~~~~~~~~~~~~~~
.. versionadded:: 1.1
A callable (or a :term:`dotted Python name` which names a callable) which is
called to preprocess the source before the template is called.  The callable
will be passed the full template source before it is parsed. The return
result of the callable will be used as the template source code.
+-----------------------------+
| Config File Setting Name    |
+=============================+
|  ``mako.preprocessor``      |
|                             |
|                             |
|                             |
+-----------------------------+
Examples
--------
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/hooks.rst
@@ -349,6 +349,55 @@
   the property
.. index::
   single: response factory
.. _changing_the_response_factory:
Changing the Response Factory
-------------------------------
.. versionadded:: 1.6
Whenever :app:`Pyramid` returns a response from a view it creates a
:term:`response` object.  By default, an instance of the
:class:`pyramid.response.Response` class is created to represent the response
object.
The factory that :app:`Pyramid` uses to create a response object instance can be
changed by passing a :class:`pyramid.interfaces.IResponseFactory` argument to
the constructor of the :term:`configurator`.  This argument can be either a
callable or a :term:`dotted Python name` representing a callable.
The factory takes a single positional argument, which is a :term:`Request`
object. The argument may be ``None``.
.. code-block:: python
   :linenos:
   from pyramid.response import Response
   class MyResponse(Response):
       pass
   config = Configurator(response_factory=lambda r: MyResponse())
If you're doing imperative configuration, and you'd rather do it after you've
already constructed a :term:`configurator` it can also be registered via the
:meth:`pyramid.config.Configurator.set_response_factory` method:
.. code-block:: python
   :linenos:
   from pyramid.config import Configurator
   from pyramid.response import Response
   class MyResponse(Response):
       pass
   config = Configurator()
   config.set_response_factory(lambda r: MyResponse())
.. index::
   single: before render event
   single: adding renderer globals
@@ -730,7 +779,7 @@
:class:`pyramid.response.Response` object in any capacity at all, you'll have
to make sure the object implements every attribute and method outlined in
:class:`pyramid.interfaces.IResponse` and you'll have to ensure that it uses
``zope.interface.implementer(IResponse)`` as a class decoratoror.
``zope.interface.implementer(IResponse)`` as a class decorator.
.. code-block:: python
   :linenos:
docs/narr/hybrid.rst
@@ -453,7 +453,7 @@
.. code-block:: python
   :linenos:
   from pryamid.static import static_view
   from pyramid.static import static_view
   www = static_view('mypackage:static', use_subpath=True)
docs/narr/i18n.rst
@@ -326,7 +326,7 @@
   $ cd /place/where/myapplication/setup.py/lives
   $ mkdir -p myapplication/locale
   $ $VENV/bin/pot-create src > myapplication/locale/myapplication.pot
   $ $VENV/bin/pot-create -o myapplication/locale/myapplication.pot src
The message catalog ``.pot`` template will end up in:
@@ -352,9 +352,9 @@
   $ cd /place/where/myapplication/setup.py/lives
   $ cd myapplication/locale
   $ mkdir -p es/LC_MESSAGES
   $ msginit -l es es/LC_MESSAGES/myapplication.po
   $ msginit -l es -o es/LC_MESSAGES/myapplication.po
This will create a new the message catalog ``.po`` file will in:
This will create a new message catalog ``.po`` file in:
``myapplication/locale/es/LC_MESSAGES/myapplication.po``.
@@ -402,11 +402,11 @@
.. code-block:: text
   $ cd /place/where/myapplication/setup.py/lives
   $ msgfmt myapplication/locale/*/LC_MESSAGES/*.po
   $ msgfmt -o myapplication/locale/es/LC_MESSAGES/myapplication.mo myapplication/locale/es/LC_MESSAGES/myapplication.po
This will create a ``.mo`` file for each ``.po`` file in your
application.  As long as the :term:`translation directory` in which
the ``.mo`` file ends up in is configured into your application, these
the ``.mo`` file ends up in is configured into your application (see :ref:`adding_a_translation_directory`), these
translations will be available to :app:`Pyramid`.
.. index::
@@ -792,9 +792,11 @@
.. code-block:: python
   :linenos:
   from pyramid.threadlocal import get_current_registry
   settings = get_current_registry().settings
   languages = settings['available_languages'].split()
   from pyramid.settings import aslist
   def my_locale_negotiator(request):
       languages = aslist(request.registry.settings['available_languages'])
       # ...
This is only a suggestion.  You can create your own "available
languages" configuration scheme as necessary.
docs/narr/introspector.rst
@@ -121,7 +121,7 @@
  ``subscriber``
    The subscriber callable object (the resolution of the ``subscriber``
    argument passed to ``add_susbcriber``).
    argument passed to ``add_subscriber``).
  ``interfaces``
@@ -137,12 +137,12 @@
  ``predicates``
    The predicate objects created as the result of passing predicate arguments
    to ``add_susbcriber``
    to ``add_subscriber``
  ``derived_predicates``
    Wrappers around the predicate objects created as the result of passing
    predicate arguments to ``add_susbcriber`` (to be used when predicates take
    predicate arguments to ``add_subscriber`` (to be used when predicates take
    only one value but must be passed more than one).
``response adapters``
@@ -450,9 +450,9 @@
    The :class:`pyramid.interfaces.IRendererInfo` object which represents
    this template's renderer.
``view mapper``
``view mappers``
  Each introspectable in the ``permissions`` category represents a call to
  Each introspectable in the ``view mappers`` category represents a call to
  :meth:`pyramid.config.Configurator.add_view` that has an explicit
  ``mapper`` argument to *or* a call to
  :meth:`pyramid.config.Configurator.set_view_mapper`; each will have
@@ -481,8 +481,8 @@
``translation directories``
  Each introspectable in the ``asset overrides`` category represents an
  individual element in a ``specs`` argument passed to
  Each introspectable in the ``translation directories`` category represents
  an individual element in a ``specs`` argument passed to
  :meth:`pyramid.config.Configurator.add_translation_dirs`; each will have
  the following data.
@@ -511,7 +511,7 @@
  ``type``
    ``implict`` or ``explicit`` as a string.
    ``implicit`` or ``explicit`` as a string.
  ``under``
docs/narr/logging.rst
@@ -16,6 +16,11 @@
   a third-party scaffold which does not create these files, the
   configuration information in this chapter may not be applicable.
.. index:
   pair: settings; logging
   pair: .ini; logging
   pair: logging; configuration
.. _logging_config:
Logging Configuration
@@ -242,23 +247,22 @@
    [logger_myapp] 
    level = DEBUG 
    handlers = 
    qualname = helloworld
    qualname = myapp
All of the child loggers of the ``myapp`` logger will inherit the ``DEBUG``
level unless they're explicitly set differently. Meaning the ``myapp.views``,
``myapp.models`` (and all your app's modules') loggers by default have an
effective level of ``DEBUG`` too.
For more advanced filtering, the logging module provides a `Filter
<http://docs.python.org/lib/node423.html>`_ object; however it cannot be used
directly from the configuration file.
For more advanced filtering, the logging module provides a
:class:`logging.Filter` object; however it cannot be used directly from the
configuration file.
Advanced Configuration
Advanced Configuration
----------------------
To capture log output to a separate file, use a `FileHandler
<http://docs.python.org/lib/node412.html>`_ (or a `RotatingFileHandler
<http://docs.python.org/lib/node413.html>`_):
To capture log output to a separate file, use :class:`logging.FileHandler` (or
:class:`logging.handlers.RotatingFileHandler`):
.. code-block:: ini 
@@ -294,15 +298,27 @@
in its `documentation
<http://docs.pylonsproject.org/projects/pyramid_exclog/dev/>`_.
.. index::
   single: TransLogger
   single: middleware; TransLogger
   pair: configuration; middleware
   single: settings; middleware
   pair: .ini; middleware
.. _request_logging_with_pastes_translogger:
Request Logging with Paste's TransLogger 
----------------------------------------
Paste provides the `TransLogger
<http://pythonpaste.org/modules/translogger.html>`_ :term:`middleware` for
logging requests using the `Apache Combined Log Format
<http://httpd.apache.org/docs/2.2/logs.html#combined>`_. TransLogger combined
with a FileHandler can be used to create an ``access.log`` file similar to
Apache's.
The term:`WSGI` design is modular.  Waitress logs error conditions, debugging
output, etc., but not web traffic.  For web traffic logging Paste provides the
`TransLogger <http://pythonpaste.org/modules/translogger.html>`_
:term:`middleware`.  TransLogger produces logs in the `Apache Combined Log
Format <http://httpd.apache.org/docs/2.2/logs.html#combined>`_.  But
TransLogger does not write to files, the Python logging system must be
configured to do this.  The Python :class:`logging.FileHandler` logging
handler can be used alongside TransLogger to create an ``access.log`` file
similar to Apache's.
Like any standard :term:`middleware` with a Paste entry point, TransLogger can
be configured to wrap your application using ``.ini`` file syntax.  First,
@@ -343,10 +359,12 @@
    app = TransLogger(app, setup_console_handler=False) 
    return app 
TransLogger will automatically setup a logging handler to the console when
called with no arguments, so it 'just works' in environments that don't
configure logging. Since we've configured our own logging handlers, we need
to disable that option via ``setup_console_handler = False``.
.. note::
    TransLogger will automatically setup a logging handler to the console when
    called with no arguments, so it 'just works' in environments that don't
    configure logging. Since our logging handlers are configured we disable
    the automation via ``setup_console_handler = False``.
With the filter in place, TransLogger's logger (named the ``wsgi`` logger) will
propagate its log messages to the parent logger (the root logger), sending
@@ -361,9 +379,9 @@
    "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.6) Gecko/20070725
    Firefox/2.0.0.6" 
To direct TransLogger to an ``access.log`` FileHandler, we need to add that
FileHandler to the list of handlers (named ``accesslog``), and ensure that the
``wsgi`` logger is configured and uses this handler accordingly:
To direct TransLogger to an ``access.log`` FileHandler, we need the following
to add a FileHandler (named ``accesslog``) to the list of handlers, and ensure
that the ``wsgi`` logger is configured and uses this handler accordingly:
.. code-block:: ini 
@@ -377,7 +395,7 @@
    [logger_wsgi] 
    level = INFO 
    handlers = handler_accesslog
    handlers = accesslog
    qualname = wsgi 
    propagate = 0 
@@ -395,7 +413,7 @@
Finally, there's no need to use the ``generic`` formatter with TransLogger as
TransLogger itself provides all the information we need. We'll use a
formatter that passes-through the log messages as is. Add a new formatter
called ``accesslog`` by include the following in your configuration file:
called ``accesslog`` by including the following in your configuration file:
.. code-block:: ini 
@@ -405,7 +423,9 @@
    [formatter_accesslog] 
    format = %(message)s 
Then wire this new ``accesslog`` formatter into the FileHandler:
Finally alter the existing configuration to wire this new
``accesslog`` formatter into the FileHandler:
.. code-block:: ini 
docs/narr/router.rst
@@ -9,6 +9,9 @@
Request Processing
==================
.. image:: ../_static/pyramid_request_processing.*
   :alt: Request Processing
Once a :app:`Pyramid` application is up and running, it is ready to accept
requests and return responses.  What happens from the time a :term:`WSGI`
request enters a :app:`Pyramid` application through to the point that
@@ -116,7 +119,8 @@
#. The :term:`thread local` stack is popped.
.. image:: router.png
.. image:: ../_static/pyramid_router.*
   :alt: Pyramid Router
This is a very high-level overview that leaves out various details.  For more
detail about subsystems invoked by the :app:`Pyramid` router such as
docs/narr/security.rst
@@ -6,12 +6,27 @@
Security
========
:app:`Pyramid` provides an optional declarative authorization system
that can prevent a :term:`view` from being invoked based on an
:app:`Pyramid` provides an optional, declarative, security system.
Security in :app:`Pyramid` is separated into authentication and
authorization. The two systems communicate via :term:`principal`
identifiers. Authentication is merely the mechanism by which credentials
provided in the :term:`request` are resolved to one or more
:term:`principal` identifiers. These identifiers represent the users and
groups that are in effect during the request. Authorization then determines
access based on the :term:`principal` identifiers, the requested
:term:`permission`, and a :term:`context`.
The :app:`Pyramid` authorization system
can prevent a :term:`view` from being invoked based on an
:term:`authorization policy`. Before a view is invoked, the
authorization system can use the credentials in the :term:`request`
along with the :term:`context` resource to determine if access will be
allowed.  Here's how it works at a high level:
- A user may or may not have previously visited the application and
  supplied authentication credentials, including a :term:`userid`.  If
  so, the application may have called
  :func:`pyramid.security.remember` to remember these.
- A :term:`request` is generated when a user visits the application.
@@ -25,7 +40,9 @@
  context as well as other attributes of the request.
- If an :term:`authentication policy` is in effect, it is passed the
  request; it returns some number of :term:`principal` identifiers.
  request. It will return some number of :term:`principal` identifiers.
  To do this, the policy would need to determine the authenticated
  :term:`userid` present in the request.
- If an :term:`authorization policy` is in effect and the :term:`view
  configuration` associated with the view callable that was found has
@@ -40,15 +57,6 @@
- If the authorization policy denies access, the view callable is not
  invoked; instead the :term:`forbidden view` is invoked.
Security in :app:`Pyramid`, unlike many systems, cleanly and explicitly
separates authentication and authorization. Authentication is merely the
mechanism by which credentials provided in the :term:`request` are
resolved to one or more :term:`principal` identifiers. These identifiers
represent the users and groups in effect during the request.
Authorization then determines access based on the :term:`principal`
identifiers, the :term:`view callable` being invoked, and the
:term:`context` resource.
Authorization is enabled by modifying your application to include an
:term:`authentication policy` and :term:`authorization policy`.
@@ -104,7 +112,8 @@
The above configuration enables a policy which compares the value of an "auth
ticket" cookie passed in the request's environment which contains a reference
to a single :term:`principal` against the principals present in any
to a single :term:`userid` and matches that userid's
:term:`principals <principal>` against the principals present in any
:term:`ACL` found in the resource tree when attempting to call some
:term:`view`.
@@ -332,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:
@@ -574,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:
@@ -595,39 +656,56 @@
       """ An object representing a Pyramid authentication policy. """
       def authenticated_userid(self, request):
           """ Return the authenticated userid or ``None`` if no
           authenticated userid can be found. This method of the policy
           should ensure that a record exists in whatever persistent store is
           used related to the user (the user should not have been deleted);
           if a record associated with the current id does not exist in a
           persistent store, it should return ``None``."""
           """ Return the authenticated :term:`userid` or ``None`` if
           no authenticated userid can be found. This method of the
           policy should ensure that a record exists in whatever
           persistent store is used related to the user (the user
           should not have been deleted); if a record associated with
           the current id does not exist in a persistent store, it
           should return ``None``.
           """
       def unauthenticated_userid(self, request):
           """ Return the *unauthenticated* userid.  This method performs the
           same duty as ``authenticated_userid`` but is permitted to return the
           userid based only on data present in the request; it needn't (and
           shouldn't) check any persistent store to ensure that the user record
           related to the request userid exists."""
           """ Return the *unauthenticated* userid.  This method
           performs the same duty as ``authenticated_userid`` but is
           permitted to return the userid based only on data present
           in the request; it needn't (and shouldn't) check any
           persistent store to ensure that the user record related to
           the request userid exists.
           This method is intended primarily a helper to assist the
           ``authenticated_userid`` method in pulling credentials out
           of the request data, abstracting away the specific headers,
           query strings, etc that are used to authenticate the request.
           """
       def effective_principals(self, request):
           """ Return a sequence representing the effective principals
           including the userid and any groups belonged to by the current
           user, including 'system' groups such as
           ``pyramid.security.Everyone`` and
           ``pyramid.security.Authenticated``. """
           typically including the :term:`userid` and any groups belonged
           to by the current user, always including 'system' groups such
           as ``pyramid.security.Everyone`` and
           ``pyramid.security.Authenticated``.
       def remember(self, request, principal, **kw):
           """
       def remember(self, request, userid, **kw):
           """ Return a set of headers suitable for 'remembering' the
           principal named ``principal`` when set in a response.  An
           individual authentication policy and its consumers can decide
           on the composition and meaning of **kw. """
           :term:`userid` named ``userid`` when set in a response.  An
           individual authentication policy and its consumers can
           decide on the composition and meaning of **kw.
           """
       def forget(self, request):
           """ Return a set of headers suitable for 'forgetting' the
           current user on subsequent requests. """
           current user on subsequent requests.
           """
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/sessions.rst
@@ -44,7 +44,7 @@
tampered with.
You can configure this session factory in your :app:`Pyramid` application
by using the :meth:`pyramid.config.Configurator.set_session_factory`` method.
by using the :meth:`pyramid.config.Configurator.set_session_factory` method.
.. code-block:: python
   :linenos:
@@ -380,7 +380,7 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In request handling code, you can check the presence and validity of a CSRF
token with :func:`pyramid.session.check_csrf_token(request)``. If the token is
token with :func:`pyramid.session.check_csrf_token`. If the token is
valid, it will return ``True``, otherwise it will raise ``HTTPBadRequest``.
Optionally, you can specify ``raises=False`` to have the check return ``False``
instead of raising an exception.
docs/narr/startup.rst
@@ -19,6 +19,7 @@
.. index::
   single: startup process
   pair: settings; .ini
The Startup Process
-------------------
@@ -139,6 +140,13 @@
   The server serves the application, and the application is running, waiting
   to receive requests.
.. seealso::
   Logging configuration is described in the :ref:`logging_chapter`
   chapter.  There, in :ref:`request_logging_with_pastes_translogger`,
   you will also find an example of how to configure
   :term:`middleware` to add pre-packaged functionality to your
   application.
.. index::
   pair: settings; deployment
   single: custom settings
docs/narr/templates.rst
@@ -316,8 +316,7 @@
   we're using a Chameleon renderer, it means "relative to the directory in
   which the file which defines the view configuration lives".  In this case,
   this is the directory containing the file that defines the ``my_view``
   function.  View-configuration-relative asset specifications work only
   in Chameleon, not in Mako templates.
   function.
Similar renderer configuration can be done imperatively.  See
:ref:`views_which_use_a_renderer`.
docs/narr/testing.rst
@@ -128,8 +128,9 @@
test.  See the :ref:`testing_module` API for information about the extra
arguments supported by these functions.
If you also want to make :func:`~pyramid.threadlocal.get_current_request` return something
other than ``None`` during the course of a single test, you can pass a
If you also want to make :func:`~pyramid.threadlocal.get_current_request`
return something other than ``None`` during the course of a single test, you
can pass a
:term:`request` object into the :func:`pyramid.testing.setUp` within the
``setUp`` method of your test:
@@ -333,66 +334,49 @@
--------------------------
In :app:`Pyramid`, a *unit test* typically relies on "mock" or "dummy"
implementations to give the code under test only enough context to run.
implementations to give the code under test enough context to run.
"Integration testing" implies another sort of testing.  In the context of a
:app:`Pyramid` integration test, the test logic tests the functionality of
some code *and* its integration with the rest of the :app:`Pyramid`
:app:`Pyramid` integration test, the test logic exercises the functionality of
the code under test *and* its integration with the rest of the :app:`Pyramid`
framework.
In :app:`Pyramid` applications that are plugins to Pyramid, you can create an
integration test by including its ``includeme`` function via
:meth:`pyramid.config.Configurator.include` in the test's setup code.  This
causes the entire :app:`Pyramid` environment to be set up and torn down as if
your application was running "for real".  This is a heavy-hammer way of
making sure that your tests have enough context to run properly, and it tests
your code's integration with the rest of :app:`Pyramid`.
Creating an integration test for a :app:`Pyramid` application usually means
invoking the application's ``includeme`` function via
:meth:`pyramid.config.Configurator.include` within the test's setup code.  This
causes the entire :app:`Pyramid` environment to be set up, simulating what
happens when your application is run "for real".  This is a heavy-hammer way of
making sure that your tests have enough context to run properly, and tests your
code's integration with the rest of :app:`Pyramid`.
Let's demonstrate this by showing an integration test for a view.  The below
test assumes that your application's package name is ``myapp``, and that
there is a ``views`` module in the app with a function with the name
``my_view`` in it that returns the response 'Welcome to this application'
after accessing some values that require a fully set up environment.
.. seealso::
.. code-block:: python
   :linenos:
   See also :ref:`including_configuration`
   import unittest
Let's demonstrate this by showing an integration test for a view.
   from pyramid import testing
Given the following view definition, which assumes that your application's
:term:`package` name is ``myproject``, and within that :term:`package` there
exists a module ``views``, which in turn contains a :term:`view` function named
``my_view``:
   class ViewIntegrationTests(unittest.TestCase):
       def setUp(self):
           """ This sets up the application registry with the
           registrations your application declares in its ``includeme``
           function.
           """
           import myapp
           self.config = testing.setUp()
           self.config.include('myapp')
   .. literalinclude:: MyProject/myproject/views.py
      :linenos:
      :lines: 1-6
      :language: python
       def tearDown(self):
           """ Clear out the application registry """
           testing.tearDown()
You'd then create a ``tests`` module within your ``myproject`` package,
containing the following test code:
       def test_my_view(self):
           from myapp.views import my_view
           request = testing.DummyRequest()
           result = my_view(request)
           self.assertEqual(result.status, '200 OK')
           body = result.app_iter[0]
           self.assertTrue('Welcome to' in body)
           self.assertEqual(len(result.headerlist), 2)
           self.assertEqual(result.headerlist[0],
                            ('Content-Type', 'text/html; charset=UTF-8'))
           self.assertEqual(result.headerlist[1], ('Content-Length',
                                                   str(len(body))))
   .. literalinclude:: MyProject/myproject/tests.py
      :linenos:
      :pyobject: ViewIntegrationTests
      :language: python
Unless you cannot avoid it, you should prefer writing unit tests that use the
:class:`~pyramid.config.Configurator` API to set up the right "mock"
registrations rather than creating an integration test.  Unit tests will run
faster (because they do less for each test) and the result of a unit test is
usually easier to make assertions about.
Writing unit tests that use the :class:`~pyramid.config.Configurator` API to
set up the right "mock" registrations is often preferred to creating
integration tests.  Unit tests will run faster (because they do less for each
test) and are usually easier to reason about.
.. index::
   single: functional tests
@@ -404,34 +388,40 @@
Functional tests test your literal application.
The below test assumes that your application's package name is ``myapp``, and
that there is a view that returns an HTML body when the root URL is invoked.
It further assumes that you've added a ``tests_require`` dependency on the
``WebTest`` package within your ``setup.py`` file.  :term:`WebTest` is a
functional testing package written by Ian Bicking.
In Pyramid, functional tests are typically written using the :term:`WebTest`
package, which provides APIs for invoking HTTP(S) requests to your application.
.. code-block:: python
   :linenos:
Regardless of which testing :term:`package` you use, ensure to add a
``tests_require`` dependency on that package to to your application's
``setup.py`` file:
   import unittest
   .. literalinclude:: MyProject/setup.py
      :linenos:
      :emphasize-lines: 26-28,48
      :language: python
   class FunctionalTests(unittest.TestCase):
       def setUp(self):
           from myapp import main
           app = main({})
           from webtest import TestApp
           self.testapp = TestApp(app)
Assuming your :term:`package` is named ``myproject``, which contains a
``views`` module, which in turn contains a :term:`view` function ``my_view``
that returns a HTML body when the root URL is invoked:
       def test_root(self):
           res = self.testapp.get('/', status=200)
           self.assertTrue('Pyramid' in res.body)
   .. literalinclude:: MyProject/myproject/views.py
      :linenos:
      :language: python
When this test is run, each test creates a "real" WSGI application using the
``main`` function in your ``myapp.__init__`` module and uses :term:`WebTest`
to wrap that WSGI application.  It assigns the result to ``self.testapp``.
In the test named ``test_root``, we use the testapp's ``get`` method to
invoke the root URL.  We then assert that the returned HTML has the string
``Pyramid`` in it.
Then the following example functional test (shown below) demonstrates invoking
the :term:`view` shown above:
   .. literalinclude:: MyProject/myproject/tests.py
      :linenos:
      :pyobject: FunctionalTests
      :language: python
When this test is run, each test method creates a "real" :term:`WSGI`
application using the ``main`` function in your ``myproject.__init__`` module,
using :term:`WebTest` to wrap that WSGI application.  It assigns the result to
``self.testapp``.  In the test named ``test_root``. The ``TestApp``'s ``get``
method is used to invoke the root URL.  Finally, an assertion is made that the
returned HTML contains the text ``MyProject``.
See the :term:`WebTest` documentation for further information about the
methods available to a :class:`webtest.app.TestApp` instance.
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.
@@ -804,7 +842,9 @@
application, this view will be invoked if the value of ``PATH_INFO`` does not
already end in a slash, and if the value of ``PATH_INFO`` *plus* a slash
matches any route's pattern.  In this case it does an HTTP redirect to the
slash-appended ``PATH_INFO``.
slash-appended ``PATH_INFO``. In addition you may pass anything that implements
:class:`pyramid.interfaces.IResponse` which will then be used in place of the
default class (:class:`pyramid.httpexceptions.HTTPFound`).
Let's use an example.  If the following routes are configured in your
application:
docs/narr/viewconfig.rst
@@ -325,7 +325,7 @@
``match_param``
  This param may be either a single string of the format "key=value" or a
  dict of key/value pairs.
  tuple containing one or more of these strings.
  This argument ensures that the view will only be called when the
  :term:`request` has key/value pairs in its :term:`matchdict` that equal
@@ -334,8 +334,8 @@
  hand side of the expression (``edit``) for the view to "match" the current
  request.
  If the ``match_param`` is a dict, every key/value pair must match for the
  predicate to pass.
  If the ``match_param`` is a tuple, every key/value pair must match
  for the predicate to pass.
  If ``match_param`` is not supplied, the view will be invoked without
  consideration of the keys and values in ``request.matchdict``.
docs/narr/webob.rst
@@ -310,6 +310,14 @@
    req = urllib2.Request('http://localhost:6543/', json_payload, headers)
    resp = urllib2.urlopen(req)
If you are doing Cross-origin resource sharing (CORS), then the standard
requires the browser to do a pre-flight HTTP OPTIONS request. The easiest way
to handling this is adding an extra ``view_config`` for the same route, with
``request_method`` set to ``OPTIONS``, and setting the desired response header
before returning. You can find examples of response headers here_.
.. _here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests
.. index::
   single: cleaning up after request
docs/quick_tour.rst
@@ -700,7 +700,7 @@
need *logging*.
Fortunately Pyramid uses the normal Python approach to logging. The
scaffold generated in your ``development.ini`` a number of lines that
scaffold generated in your ``development.ini`` has a number of lines that
configure the logging for you to some reasonable defaults. You then see
messages sent by Pyramid (for example, when a new request comes in).
docs/quick_tutorial/debugtoolbar.rst
@@ -58,33 +58,31 @@
Analysis
========
``pyramid_debugtoolbar`` is a full-fledged Python package,
available on PyPI just like thousands of other Python packages. Thus we
start by installing the ``pyramid_debugtoolbar`` package into our
virtual environment using normal Python package installation commands.
``pyramid_debugtoolbar`` is a full-fledged Python package, available on PyPI
just like thousands of other Python packages. Thus we start by installing the
``pyramid_debugtoolbar`` package into our virtual environment using normal
Python package installation commands.
The ``pyramid_debugtoolbar`` Python package is also a Pyramid add-on,
which means we need to include its add-on configuration into our web
application. We could do this with imperative configuration in
``tutorial/__init__.py`` by using ``config.include``. Pyramid also
supports wiring in add-on configuration via our ``development.ini``
using ``pyramid.includes``. We use this to load the configuration for
the debugtoolbar.
The ``pyramid_debugtoolbar`` Python package is also a Pyramid add-on, which
means we need to include its add-on configuration into our web application. We
could do this with imperative configuration in ``tutorial/__init__.py`` by
using ``config.include``. Pyramid also supports wiring in add-on configuration
via our ``development.ini`` using ``pyramid.includes``. We use this to load
the configuration for the debugtoolbar.
You'll now see an attractive button on the right side of
your browser, which you may click to provide introspective access to debugging
information in a new browser tab. Even better, if your web application
generates an error,
you will see a nice traceback on the screen. When you want to disable
this toolbar, no need to change code: you can remove it from
``pyramid.includes`` in the relevant ``.ini`` configuration file (thus
showing why configuration files are handy.)
You'll now see an attractive button on the right side of your browser, which
you may click to provide introspective access to debugging information in a
new browser tab. Even better, if your web application generates an error, you
will see a nice traceback on the screen. When you want to disable this
toolbar, no need to change code: you can remove it from ``pyramid.includes``
in the relevant ``.ini`` configuration file (thus showing why configuration
files are handy.)
Note injects a small amount of html/css into your app just before the closing
``</body>`` tag in order to display itself. If you
start to experience otherwise inexplicable client-side weirdness, you can shut
it off by commenting out the ``pyramid_debugtoolbar`` line in
``pyramid.includes`` temporarily.
Note that the toolbar injects a small amount of html/css into your app just
before the closing ``</body>`` tag in order to display itself. If you start to
experience otherwise inexplicable client-side weirdness, you can shut it off
by commenting out the ``pyramid_debugtoolbar`` line in ``pyramid.includes``
temporarily.
.. seealso:: See also :ref:`pyramid_debugtoolbar <toolbar:overview>`.
docs/quick_tutorial/forms.rst
@@ -104,7 +104,7 @@
it is located. We point at the package, then the path inside the package.
We just need to include a call to ``add_static_view`` to make that
directory available at a URL. For Pyramid-specific pages,
directory available at a URL. For Pyramid-specific packages,
Pyramid provides a facility (``config.include()``) which even makes
that unnecessary for consumers of a package. (Deform is not specific to
Pyramid.)
docs/quick_tutorial/functional_testing.rst
@@ -37,12 +37,15 @@
    $ $VENV/bin/python setup.py develop
    $ $VENV/bin/easy_install webtest
#. Let's extend ``unit_testing/tutorial/tests.py`` to include a
#. Let's extend ``functional_testing/tutorial/tests.py`` to include a
   functional test:
   .. literalinclude:: functional_testing/tutorial/tests.py
    :linenos:
   Be sure this file is not executable, or ``nosetests`` may not
   include your tests.
#. Now run the tests:
   .. code-block:: bash
@@ -67,4 +70,4 @@
Extra Credit
============
#. Why do our functional tests use ``b''``?
#. Why do our functional tests use ``b''``?
docs/quick_tutorial/hello_world.rst
@@ -77,7 +77,7 @@
#. *Lines 12-14*. Use Pyramid's :term:`configurator` to connect
   :term:`view` code to a particular URL :term:`route`.
#. *Lines 6-7*. Implement the view code that generates the
#. *Lines 6-8*. Implement the view code that generates the
   :term:`response`.
#. *Lines 15-17*. Publish a :term:`WSGI` app using an HTTP
docs/quick_tutorial/ini.rst
@@ -14,9 +14,9 @@
:ref:`configuration <configuration_narr>` distinct from code.
This approach is optional, but its presence makes it distinct from
other Python web frameworks. It taps into Python's ``setuptools``
library, which establishes conventions for how Python projects can be
installed and provide "entry points". Pyramid uses an entry point to
let a Pyramid application it where to find the WSGI app.
library, which establishes conventions for installing and providing
"entry points" for Python projects. Pyramid uses an entry point to
let a Pyramid application know where to find the WSGI app.
Objectives
==========
docs/quick_tutorial/jinja2.rst
@@ -20,8 +20,8 @@
Steps
=====
#. In this step let's start by installing the ``pyramid_jinja2``
   add-on, the copying the ``view_class`` step's directory:
#. In this step let's start by copying the ``view_class`` step's
   directory, and then installing the ``pyramid_jinja2`` add-on.
   .. code-block:: bash
@@ -44,12 +44,6 @@
   .. literalinclude:: jinja2/tutorial/home.jinja2
    :language: html
#. Get the ``pyramid.includes`` into the functional test setup in
   ``jinja2/tutorial/tests.py``:
   .. literalinclude:: jinja2/tutorial/tests.py
    :linenos:
#. Now run the tests:
@@ -77,9 +71,6 @@
Our view code stayed largely the same. We simply changed the file
extension on the renderer. For the template, the syntax for Chameleon
and Jinja2's basic variable insertion is very similar.
Our functional tests don't have ``development.ini`` so they needed the
``pyramid.includes`` to be setup in the test setup.
Extra Credit
============
docs/quick_tutorial/jinja2/tutorial/tests.py
@@ -30,13 +30,7 @@
class TutorialFunctionalTests(unittest.TestCase):
    def setUp(self):
        from tutorial import main
        settings = {
            'pyramid.includes': [
                'pyramid_jinja2'
            ]
        }
        app = main({}, **settings)
        app = main({})
        from webtest import TestApp
        self.testapp = TestApp(app)
docs/quick_tutorial/logging.rst
@@ -16,7 +16,7 @@
need *logging*.
Fortunately Pyramid uses the normal Python approach to logging. The
scaffold generated, in your ``development.ini``, a number of lines that
scaffold generated, in your ``development.ini``, has a number of lines that
configure the logging for you to some reasonable defaults. You then see
messages sent by Pyramid (for example, when a new request comes in.)
docs/quick_tutorial/more_view_classes/tutorial/views.py
@@ -5,7 +5,7 @@
@view_defaults(route_name='hello')
class TutorialViews:
class TutorialViews(object):
    def __init__(self, request):
        self.request = request
        self.view_name = 'TutorialViews'
@@ -25,13 +25,13 @@
    def hello(self):
        return {'page_title': 'Hello View'}
    # Posting to /home via the "Edit" submit button
    # Posting to /howdy/first/last via the "Edit" submit button
    @view_config(request_method='POST', renderer='edit.pt')
    def edit(self):
        new_name = self.request.params['new_name']
        return {'page_title': 'Edit View', 'new_name': new_name}
    # Posting to /home via the "Delete" submit button
    # Posting to /howdy/first/last via the "Delete" submit button
    @view_config(request_method='POST', request_param='form.delete',
                 renderer='delete.pt')
    def delete(self):
docs/tutorials/wiki/authorization.rst
@@ -197,9 +197,9 @@
head of ``tutorial/tutorial/views.py``:
.. literalinclude:: src/authorization/tutorial/views.py
   :lines: 6-13,15-17
   :lines: 6-17
   :linenos:
   :emphasize-lines: 3,6-9,11
   :emphasize-lines: 3,6-11
   :language: python
(Only the highlighted lines, with other necessary modifications,
docs/tutorials/wiki/design.rst
@@ -53,10 +53,10 @@
We'll eventually be adding security to our application.  The components we'll
use to do this are below.
- USERS, a dictionary mapping usernames to their
- USERS, a dictionary mapping :term:`userids <userid>` to their
  corresponding passwords.
- GROUPS, a dictionary mapping usernames to a
- GROUPS, a dictionary mapping :term:`userids <userid>` to a
  list of groups to which they belong to.
- ``groupfinder``, an *authorization callback* that looks up
docs/tutorials/wiki2/design.rst
@@ -53,7 +53,8 @@
We'll eventually be adding security to our application.  The components we'll
use to do this are below.
- USERS, a dictionary mapping users names to their corresponding passwords.
- USERS, a dictionary mapping users names (the user's :term:`userids
  <userid>`) to their corresponding passwords.
- GROUPS, a dictionary mapping user names to a list of groups they belong to.
pyramid/authentication.py
@@ -3,7 +3,6 @@
from codecs import utf_8_encode
import hashlib
import base64
import datetime
import re
import time as time_mod
import warnings
@@ -335,11 +334,11 @@
        effective_principals.extend(groups)
        return effective_principals
    def remember(self, request, principal, **kw):
        """ Store the ``principal`` as ``repoze.who.userid``.
    def remember(self, request, userid, **kw):
        """ Store the ``userid`` as ``repoze.who.userid``.
        
        The identity to authenticated to :mod:`repoze.who`
        will contain the given principal as ``userid``, and
        will contain the given userid as ``userid``, and
        provide all keyword arguments as additional identity
        keys. Useful keys could be ``max_age`` or ``userdata``.
        """
@@ -348,7 +347,7 @@
            return []
        environ = request.environ
        identity = kw
        identity['repoze.who.userid'] = principal
        identity['repoze.who.userid'] = userid
        return identifier.remember(environ, identity)
    def forget(self, request):
@@ -404,7 +403,7 @@
        """ The ``REMOTE_USER`` value found within the ``environ``."""
        return request.environ.get(self.environ_key)
    def remember(self, request, principal, **kw):
    def remember(self, request, userid, **kw):
        """ A no-op. The ``REMOTE_USER`` does not provide a protocol for
        remembering the user. This will be application-specific and can
        be done somewhere else or in a subclass."""
@@ -652,7 +651,7 @@
        if result:
            return result['userid']
    def remember(self, request, principal, **kw):
    def remember(self, request, userid, **kw):
        """ Accepts the following kw args: ``max_age=<int-seconds>,
        ``tokens=<sequence-of-ascii-strings>``.
@@ -660,7 +659,7 @@
        the response.
        """
        return self.cookie.remember(request, principal, **kw)
        return self.cookie.remember(request, userid, **kw)
    def forget(self, request):
        """ A list of headers which will delete appropriate cookies."""
@@ -741,7 +740,7 @@
    If the ticket cannot be parsed, a ``BadTicket`` exception will be raised
    with an explanation.
    """
    ticket = ticket.strip('"')
    ticket = native_(ticket).strip('"')
    digest_size = hashlib.new(hashalg).digest_size * 2
    digest = ticket[:digest_size]
    try:
@@ -929,7 +928,7 @@
        if reissue and not hasattr(request, '_authtkt_reissued'):
            if ( (now - timestamp) > self.reissue_time ):
                # work around https://github.com/Pylons/pyramid/issues#issue/108
                # See https://github.com/Pylons/pyramid/issues#issue/108
                tokens = list(filter(None, tokens))
                headers = self.remember(request, userid, max_age=self.max_age,
                                        tokens=tokens)
@@ -1061,13 +1060,13 @@
        self.userid_key = prefix + 'userid'
        self.debug = debug
    def remember(self, request, principal, **kw):
        """ Store a principal in the session."""
        request.session[self.userid_key] = principal
    def remember(self, request, userid, **kw):
        """ Store a userid in the session."""
        request.session[self.userid_key] = userid
        return []
    def forget(self, request):
        """ Remove the stored principal from the session."""
        """ Remove the stored userid from the session."""
        if self.userid_key in request.session:
            del request.session[self.userid_key]
        return []
@@ -1132,7 +1131,7 @@
        if credentials:
            return credentials[0]
    def remember(self, request, principal, **kw):
    def remember(self, request, userid, **kw):
        """ A no-op. Basic authentication does not provide a protocol for
        remembering the user. Credentials are sent on every request.
pyramid/compat.py
@@ -3,27 +3,27 @@
import sys
import types
if platform.system() == 'Windows': # pragma: no cover
if platform.system() == 'Windows':  # pragma: no cover
    WIN = True
else: # pragma: no cover
else:  # pragma: no cover
    WIN = False
try: # pragma: no cover
try:  # pragma: no cover
    import __pypy__
    PYPY = True
except: # pragma: no cover
except:  # pragma: no cover
    __pypy__ = None
    PYPY = False
try:
    import cPickle as pickle
except ImportError: # pragma: no cover
except ImportError:  # pragma: no cover
    import pickle
# 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,
@@ -43,16 +43,16 @@
    ``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')
@@ -72,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)``"""
@@ -95,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
@@ -112,17 +112,18 @@
    from urllib import unquote as url_unquote
    from urllib import urlencode as url_encode
    from urllib2 import urlopen as url_open
    def url_unquote_text(v, encoding='utf-8', errors='replace'): # pragma: no cover
        v = url_unquote(v)
        return v.decode(encoding, errors)
    def url_unquote_native(v, encoding='utf-8', errors='replace'): # pragma: no cover
        return native_(url_unquote_text(v, encoding, errors))
if PY3: # pragma: no cover
if PY3:  # pragma: no cover
    import builtins
    exec_ = getattr(builtins, "exec")
    def reraise(tp, value, tb=None):
        if value is None:
@@ -131,10 +132,9 @@
            raise value.with_traceback(tb)
        raise value
    del builtins
else: # pragma: no cover
else:  # pragma: no cover
    def exec_(code, globs=None, locs=None):
        """Execute code in a namespace."""
        if globs is None:
@@ -147,35 +147,38 @@
            locs = globs
        exec("""exec code in globs, locs""")
    exec_("""def reraise(tp, value, tb=None):
    raise tp, value, tb
""")
if PY3: # pragma: no cover
if PY3:  # pragma: no cover
    def iteritems_(d):
        return d.items()
    def itervalues_(d):
        return d.values()
    def iterkeys_(d):
        return d.keys()
else: # pragma: no cover
else:  # pragma: no cover
    def iteritems_(d):
        return d.iteritems()
    def itervalues_(d):
        return d.itervalues()
    def iterkeys_(d):
        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
@@ -183,46 +186,49 @@
else:
    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
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):
@@ -231,16 +237,47 @@
    def decode_path_info(path):
        return path.decode('utf-8')
if PY3: # pragma: no cover
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):
        return unquote_to_bytes(bytestring).decode('latin-1')
else:
    from urlparse import unquote as unquote_to_bytes
    def unquote_bytes_to_wsgi(bytestring):
        return unquote_to_bytes(bytestring)
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):
    """
    This consistently verifies that the callable is bound to a
    class.
    """
    is_bound = is_bound_method(fn)
    if not is_bound and inspect.isroutine(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
            return True
        elif inspect.ismethod(fn):
            return True
    return False
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,
@@ -124,6 +129,14 @@
    which accept a ``renderer`` argument, into absolute paths.  If ``None``
    is passed (the default), the package is assumed to be the Python package
    in which the *caller* of the ``Configurator`` constructor lives.
    If the ``root_package`` is passed, it will propagate through the
    configuration hierarchy as a way for included packages to locate
    resources relative to the package in which the main ``Configurator`` was
    created. If ``None`` is passed (the default), the ``root_package`` will
    be derived from the ``package`` argument. The ``package`` attribute is
    always pointing at the package being included when using :meth:`.include`,
    whereas the ``root_package`` does not change.
    If the ``settings`` argument is passed, it should be a Python dictionary
    representing the :term:`deployment settings` for this application.  These
@@ -171,6 +184,11 @@
    See :ref:`changing_the_request_factory`.  By default it is ``None``,
    which means use the default request factory.
    If ``response_factory`` is passed, it should be a :term:`response
    factory` implementation or a :term:`dotted Python name` to the same.
    See :ref:`changing_the_response_factory`.  By default it is ``None``,
    which means use the default response factory.
    If ``default_permission`` is passed, it should be a
    :term:`permission` string to be used as the default permission for
    all view configuration registrations performed against this
@@ -182,7 +200,7 @@
    configurations which do not explicitly declare a permission will
    always be executable by entirely anonymous users (any
    authorization policy in effect is ignored).
    .. seealso::
        See also :ref:`setting_a_default_permission`.
@@ -243,6 +261,10 @@
    .. versionadded:: 1.3
       The ``introspection`` argument.
    .. versionadded:: 1.6
       The ``root_package`` argument.
       The ``response_factory`` argument.
    """
    manager = manager # for testing injection
    venusian = venusian # for testing injection
@@ -265,6 +287,7 @@
                 debug_logger=None,
                 locale_negotiator=None,
                 request_factory=None,
                 response_factory=None,
                 default_permission=None,
                 session_factory=None,
                 default_view_mapper=None,
@@ -272,13 +295,17 @@
                 exceptionresponse_view=default_exceptionresponse_view,
                 route_prefix=None,
                 introspection=True,
                 root_package=None,
                 ):
        if package is None:
            package = caller_package()
        if root_package is None:
            root_package = package
        name_resolver = DottedNameResolver(package)
        self.name_resolver = name_resolver
        self.package_name = name_resolver.get_package_name()
        self.package = name_resolver.get_package()
        self.root_package = root_package
        self.registry = registry
        self.autocommit = autocommit
        self.route_prefix = route_prefix
@@ -295,6 +322,7 @@
                debug_logger=debug_logger,
                locale_negotiator=locale_negotiator,
                request_factory=request_factory,
                response_factory=response_factory,
                default_permission=default_permission,
                session_factory=session_factory,
                default_view_mapper=default_view_mapper,
@@ -310,6 +338,7 @@
                       debug_logger=None,
                       locale_negotiator=None,
                       request_factory=None,
                       response_factory=None,
                       default_permission=None,
                       session_factory=None,
                       default_view_mapper=None,
@@ -397,6 +426,9 @@
        if request_factory:
            self.set_request_factory(request_factory)
        if response_factory:
            self.set_response_factory(response_factory)
        if default_permission:
            self.set_default_permission(default_permission)
@@ -454,7 +486,7 @@
            _registry.registerSelfAdapter = registerSelfAdapter
    # API
    def _get_introspector(self):
        introspector = getattr(self.registry, 'introspector', _marker)
        if introspector is _marker:
@@ -747,6 +779,7 @@
            configurator = self.__class__(
                registry=self.registry,
                package=package_of(module),
                root_package=self.root_package,
                autocommit=self.autocommit,
                route_prefix=route_prefix,
                )
@@ -806,6 +839,7 @@
        configurator = self.__class__(
            registry=self.registry,
            package=package,
            root_package=self.root_package,
            autocommit=self.autocommit,
            route_prefix=self.route_prefix,
            introspection=self.introspection,
@@ -958,7 +992,7 @@
class ActionState(object):
    def __init__(self):
        # NB "actions" is an API, dep'd upon by pyramid_zcml's load_zcml func
        self.actions = []
        self.actions = []
        self._seen_files = set()
    def processSpec(self, spec):
@@ -1042,10 +1076,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']
@@ -1066,15 +1172,19 @@
                                ConfigurationExecutionError(t, v, info),
                                tb)
                    finally:
                       del t, v, tb
                        del t, v, tb
                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):
@@ -1195,4 +1305,3 @@
        )
global_registries = WeakOrderedSet()
pyramid/config/adapters.py
@@ -143,7 +143,7 @@
        Adds a subscriber predicate factory.  The associated subscriber
        predicate can later be named as a keyword argument to
        :meth:`pyramid.config.Configurator.add_subscriber` in the
        ``**predicates`` anonyous keyword argument dictionary.
        ``**predicates`` anonymous keyword argument dictionary.
        ``name`` should be the name of the predicate.  It must be a valid
        Python identifier (it will be used as a ``**predicates`` keyword
pyramid/config/assets.py
@@ -1,3 +1,4 @@
import os
import pkg_resources
import sys
@@ -79,7 +80,8 @@
                return result
        return pkg_resources.DefaultProvider.resource_listdir(
            self, resource_name)
@implementer(IPackageOverrides)
class PackageOverrides(object):
    # pkg_resources arg in kw args below for testing
@@ -97,57 +99,61 @@
        # optional)...
        # A __loader__ attribute is basically metadata, and setuptools
        # uses it as such.
        package.__loader__ = self
        package.__loader__ = self
        # we call register_loader_type for every instantiation of this
        # class; that's OK, it's idempotent to do it more than once.
        pkg_resources.register_loader_type(self.__class__, OverrideProvider)
        self.overrides = []
        self.overridden_package_name = package.__name__
    def insert(self, path, package, prefix):
    def insert(self, path, source):
        if not path or path.endswith('/'):
            override = DirectoryOverride(path, package, prefix)
            override = DirectoryOverride(path, source)
        else:
            override = FileOverride(path, package, prefix)
            override = FileOverride(path, source)
        self.overrides.insert(0, override)
        return override
    def search_path(self, resource_name):
    def filtered_sources(self, resource_name):
        for override in self.overrides:
            o = override(resource_name)
            if o is not None:
                package, name = o
                yield package, name
                yield o
    def get_filename(self, resource_name):
        for package, rname in self.search_path(resource_name):
            if pkg_resources.resource_exists(package, rname):
                return pkg_resources.resource_filename(package, rname)
        for source, path in self.filtered_sources(resource_name):
            result = source.get_filename(path)
            if result is not None:
                return result
    def get_stream(self, resource_name):
        for package, rname in self.search_path(resource_name):
            if pkg_resources.resource_exists(package, rname):
                return pkg_resources.resource_stream(package, rname)
        for source, path in self.filtered_sources(resource_name):
            result = source.get_stream(path)
            if result is not None:
                return result
    def get_string(self, resource_name):
        for package, rname in self.search_path(resource_name):
            if pkg_resources.resource_exists(package, rname):
                return pkg_resources.resource_string(package, rname)
        for source, path in self.filtered_sources(resource_name):
            result = source.get_string(path)
            if result is not None:
                return result
    def has_resource(self, resource_name):
        for package, rname in self.search_path(resource_name):
            if pkg_resources.resource_exists(package, rname):
        for source, path in self.filtered_sources(resource_name):
            if source.exists(path):
                return True
    def isdir(self, resource_name):
        for package, rname in self.search_path(resource_name):
            if pkg_resources.resource_exists(package, rname):
                return pkg_resources.resource_isdir(package, rname)
        for source, path in self.filtered_sources(resource_name):
            result = source.isdir(path)
            if result is not None:
                return result
    def listdir(self, resource_name):
        for package, rname in self.search_path(resource_name):
            if pkg_resources.resource_exists(package, rname):
                return pkg_resources.resource_listdir(package, rname)
        for source, path in self.filtered_sources(resource_name):
            result = source.listdir(path)
            if result is not None:
                return result
    @property
    def real_loader(self):
@@ -174,72 +180,184 @@
        """ See IPEP302Loader.
        """
        return self.real_loader.get_source(fullname)
class DirectoryOverride:
    def __init__(self, path, package, prefix):
    def __init__(self, path, source):
        self.path = path
        self.package = package
        self.prefix = prefix
        self.pathlen = len(self.path)
        self.source = source
    def __call__(self, resource_name):
        if resource_name.startswith(self.path):
            name = '%s%s' % (self.prefix, resource_name[self.pathlen:])
            return self.package, name
            new_path = resource_name[self.pathlen:]
            return self.source, new_path
class FileOverride:
    def __init__(self, path, package, prefix):
    def __init__(self, path, source):
        self.path = path
        self.package = package
        self.prefix = prefix
        self.source = source
    def __call__(self, resource_name):
        if resource_name == self.path:
            return self.package, self.prefix
            return self.source, ''
class PackageAssetSource(object):
    """
    An asset source relative to a package.
    If this asset source is a file, then we expect the ``prefix`` to point
    to the new name of the file, and the incoming ``resource_name`` will be
    the empty string, as returned by the ``FileOverride``.
    """
    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):
        return '%s%s' % (self.prefix, resource_name)
    def get_filename(self, resource_name):
        path = self.get_path(resource_name)
        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.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.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.pkg_name, path):
            return True
    def isdir(self, resource_name):
        path = self.get_path(resource_name)
        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.pkg_name, path):
            return pkg_resources.resource_listdir(self.pkg_name, path)
class FSAssetSource(object):
    """
    An asset source relative to a path in the filesystem.
    """
    def __init__(self, prefix):
        self.prefix = prefix
    def get_filename(self, resource_name):
        if resource_name:
            path = os.path.join(self.prefix, resource_name)
        else:
            path = self.prefix
        if os.path.exists(path):
            return path
    def get_stream(self, resource_name):
        path = self.get_filename(resource_name)
        if path is not None:
            return open(path, 'rb')
    def get_string(self, resource_name):
        stream = self.get_stream(resource_name)
        if stream is not None:
            with stream:
                return stream.read()
    def exists(self, resource_name):
        path = self.get_filename(resource_name)
        if path is not None:
            return True
    def isdir(self, resource_name):
        path = self.get_filename(resource_name)
        if path is not None:
            return os.path.isdir(path)
    def listdir(self, resource_name):
        path = self.get_filename(resource_name)
        if path is not None:
            return os.listdir(path)
class AssetsConfiguratorMixin(object):
    def _override(self, package, path, override_package, override_prefix,
    def _override(self, package, path, override_source,
                  PackageOverrides=PackageOverrides):
        pkg_name = package.__name__
        override_pkg_name = override_package.__name__
        override = self.registry.queryUtility(IPackageOverrides, name=pkg_name)
        if override is None:
            override = PackageOverrides(package)
            self.registry.registerUtility(override, IPackageOverrides,
                                          name=pkg_name)
        override.insert(path, override_pkg_name, override_prefix)
        override.insert(path, override_source)
    @action_method
    def override_asset(self, to_override, override_with, _override=None):
        """ Add a :app:`Pyramid` asset override to the current
        configuration state.
        ``to_override`` is a :term:`asset specification` to the
        ``to_override`` is an :term:`asset specification` to the
        asset being overridden.
        ``override_with`` is a :term:`asset specification` to the
        asset that is performing the override.
        ``override_with`` is an :term:`asset specification` to the
        asset that is performing the override. This may also be an absolute
        path.
        See :ref:`assets_chapter` for more
        information about asset overrides."""
        if to_override == override_with:
            raise ConfigurationError('You cannot override an asset with itself')
            raise ConfigurationError(
                'You cannot override an asset with itself')
        package = to_override
        path = ''
        if ':' in to_override:
            package, path = to_override.split(':', 1)
        override_package = override_with
        override_prefix = ''
        if ':' in override_with:
            override_package, override_prefix = override_with.split(':', 1)
        # *_isdir = override is package or directory
        overridden_isdir = path=='' or path.endswith('/')
        override_isdir = override_prefix=='' or override_prefix.endswith('/')
        overridden_isdir = path == '' or path.endswith('/')
        if os.path.isabs(override_with):
            override_source = FSAssetSource(override_with)
            if not os.path.exists(override_with):
                raise ConfigurationError(
                    'Cannot override asset with an absolute path that does '
                    'not exist')
            override_isdir = os.path.isdir(override_with)
            override_package = None
            override_prefix = override_with
        else:
            override_package = override_with
            override_prefix = ''
            if ':' in override_with:
                override_package, override_prefix = override_with.split(':', 1)
            __import__(override_package)
            to_package = sys.modules[override_package]
            override_source = PackageAssetSource(to_package, override_prefix)
            override_isdir = (
                override_prefix == '' or
                override_with.endswith('/')
            )
        if overridden_isdir and (not override_isdir):
            raise ConfigurationError(
@@ -255,10 +373,8 @@
        def register():
            __import__(package)
            __import__(override_package)
            from_package = sys.modules[package]
            to_package = sys.modules[override_package]
            override(from_package, path, to_package, override_prefix)
            override(from_package, path, override_source)
        intr = self.introspectable(
            'asset overrides',
pyramid/config/factories.py
@@ -1,9 +1,10 @@
from zope.deprecation import deprecate
from zope.deprecation import deprecated
from zope.interface import implementer
from pyramid.interfaces import (
    IDefaultRootFactory,
    IRequestFactory,
    IResponseFactory,
    IRequestExtensions,
    IRootFactory,
    ISessionFactory,
@@ -13,8 +14,10 @@
from pyramid.util import (
    action_method,
    InstancePropertyMixin,
    get_callable_name,
    InstancePropertyHelper,
    )
class FactoriesConfiguratorMixin(object):
    @action_method
@@ -32,9 +35,10 @@
        factory = self.maybe_dotted(factory)
        if factory is None:
            factory = DefaultRootFactory
        def register():
            self.registry.registerUtility(factory, IRootFactory)
            self.registry.registerUtility(factory, IDefaultRootFactory) # b/c
            self.registry.registerUtility(factory, IDefaultRootFactory)  # b/c
        intr = self.introspectable('root factories',
                                   None,
@@ -43,7 +47,7 @@
        intr['factory'] = factory
        self.action(IRootFactory, register, introspectables=(intr,))
    _set_root_factory = set_root_factory # bw compat
    _set_root_factory = set_root_factory  # bw compat
    @action_method
    def set_session_factory(self, factory):
@@ -59,6 +63,7 @@
           achieve the same purpose.
        """
        factory = self.maybe_dotted(factory)
        def register():
            self.registry.registerUtility(factory, ISessionFactory)
        intr = self.introspectable('session factory', None,
@@ -88,6 +93,7 @@
           can be used to achieve the same purpose.
        """
        factory = self.maybe_dotted(factory)
        def register():
            self.registry.registerUtility(factory, IRequestFactory)
        intr = self.introspectable('request factory', None,
@@ -95,6 +101,31 @@
                                   'request factory')
        intr['factory'] = factory
        self.action(IRequestFactory, register, introspectables=(intr,))
    @action_method
    def set_response_factory(self, factory):
        """ The object passed as ``factory`` should be an object (or a
        :term:`dotted Python name` which refers to an object) which
        will be used by the :app:`Pyramid` as the default response
        objects. The factory should conform to the
        :class:`pyramid.interfaces.IResponseFactory` interface.
        .. note::
           Using the ``response_factory`` argument to the
           :class:`pyramid.config.Configurator` constructor
           can be used to achieve the same purpose.
        """
        factory = self.maybe_dotted(factory)
        def register():
            self.registry.registerUtility(factory, IResponseFactory)
        intr = self.introspectable('response factory', None,
                                   self.object_description(factory),
                                   'response factory')
        intr['factory'] = factory
        self.action(IResponseFactory, register, introspectables=(intr,))
    @action_method
    def add_request_method(self,
@@ -143,10 +174,12 @@
        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__
        else:
            name = get_callable_name(name)
        def register():
            exts = self.registry.queryUtility(IRequestExtensions)
@@ -180,8 +213,6 @@
                        introspectables=(intr,))
    @action_method
    @deprecate('set_request_propery() is deprecated as of Pyramid 1.5; use '
               'add_request_method() with the property=True argument instead')
    def set_request_property(self, callable, name=None, reify=False):
        """ Add a property to the request object.
@@ -195,9 +226,14 @@
        self.add_request_method(
            callable, name=name, property=not reify, reify=reify)
    deprecated(
        set_request_property,
        'set_request_propery() is deprecated as of Pyramid 1.5; use '
        'add_request_method() with the property=True argument instead')
@implementer(IRequestExtensions)
class _RequestExtensions(object):
    def __init__(self):
        self.descriptors = {}
        self.methods = {}
pyramid/config/routes.py
@@ -138,6 +138,18 @@
          .. versionadded:: 1.1
        accept
          This value represents a match query for one or more mimetypes in the
          ``Accept`` HTTP request header.  If this value is specified, it must
          be in one of the following forms: a mimetype match token in the form
          ``text/plain``, a wildcard mimetype match token in the form
          ``text/*`` or a match-all wildcard mimetype match token in the form
          ``*/*``.  If any of the forms matches the ``Accept`` header of the
          request, or if the ``Accept`` header isn't set at all in the request,
          this will match the current route. If this does not match the
          ``Accept`` header of the request, route matching continues.
        Predicate Arguments
        pattern
@@ -220,19 +232,6 @@
          case of the header name is not significant.  If this
          predicate returns ``False``, route matching continues.
        accept
          This value represents a match query for one or more
          mimetypes in the ``Accept`` HTTP request header.  If this
          value is specified, it must be in one of the following
          forms: a mimetype match token in the form ``text/plain``, a
          wildcard mimetype match token in the form ``text/*`` or a
          match-all wildcard mimetype match token in the form ``*/*``.
          If any of the forms matches the ``Accept`` header of the
          request, or if the ``Accept`` header isn't set at all in the
          request, this predicate will be true. If this predicate
          returns ``False``, route matching continues.
        effective_principals
          If specified, this value should be a :term:`principal` identifier or
@@ -303,6 +302,8 @@
        # check for an external route; an external route is one which is
        # is a full url (e.g. 'http://example.com/{id}')
        parsed = urlparse.urlparse(pattern)
        external_url = pattern
        if parsed.hostname:
            pattern = parsed.path
@@ -357,6 +358,10 @@
        intr['pregenerator'] = pregenerator
        intr['static'] = static
        intr['use_global_views'] = use_global_views
        if static is True:
            intr['external_url'] = external_url
        introspectables.append(intr)
        if factory:
pyramid/config/settings.py
@@ -17,7 +17,7 @@
    def add_settings(self, settings=None, **kw):
        """Augment the :term:`deployment settings` with one or more
        key/value pairs.
        key/value pairs.
        You may pass a dictionary::
@@ -117,6 +117,11 @@
                                             config_prevent_http_cache)
        eff_prevent_http_cache = asbool(eget('PYRAMID_PREVENT_HTTP_CACHE',
                                             config_prevent_http_cache))
        config_prevent_cachebust = self.get('prevent_cachebust', '')
        config_prevent_cachebust = self.get('pyramid.prevent_cachebust',
                                             config_prevent_cachebust)
        eff_prevent_cachebust = asbool(eget('PYRAMID_PREVENT_CACHEBUST',
                                             config_prevent_cachebust))
        update = {
            'debug_authorization': eff_debug_all or eff_debug_auth,
@@ -128,6 +133,7 @@
            'reload_assets':eff_reload_all or eff_reload_assets,
            'default_locale_name':eff_locale_name,
            'prevent_http_cache':eff_prevent_http_cache,
            'prevent_cachebust':eff_prevent_cachebust,
            'pyramid.debug_authorization': eff_debug_all or eff_debug_auth,
            'pyramid.debug_notfound': eff_debug_all or eff_debug_notfound,
@@ -138,6 +144,7 @@
            'pyramid.reload_assets':eff_reload_all or eff_reload_assets,
            'pyramid.default_locale_name':eff_locale_name,
            'pyramid.prevent_http_cache':eff_prevent_http_cache,
            'pyramid.prevent_cachebust':eff_prevent_cachebust,
            }
        self.update(update)
pyramid/config/util.py
@@ -3,6 +3,7 @@
from pyramid.compat import (
    bytes_,
    getargspec,
    is_nonstr_iter,
    )
@@ -201,7 +202,7 @@
            return False
    try:
        argspec = inspect.getargspec(fn)
        argspec = getargspec(fn)
    except TypeError:
        return False
pyramid/config/views.py
@@ -34,6 +34,7 @@
    )
from pyramid import renderers
from pyramid.static import PathSegmentMd5CacheBuster
from pyramid.compat import (
    string_types,
@@ -41,13 +42,9 @@
    url_quote,
    WIN,
    is_bound_method,
    is_nonstr_iter
    is_unbound_method,
    is_nonstr_iter,
    )
from pyramid.encode import (
    quote_plus,
    urlencode,
)
from pyramid.exceptions import (
    ConfigurationError,
@@ -57,6 +54,7 @@
from pyramid.httpexceptions import (
    HTTPForbidden,
    HTTPNotFound,
    default_exceptionresponse_view,
    )
from pyramid.registry import (
@@ -302,7 +300,7 @@
                    raise PredicateMismatch(
                         'predicate mismatch for view %s (%s)' % (
                         view_name, predicate.text()))
            return view(context, request)
            return view(context, request)
        def checker(context, request):
            return all((predicate(context, request) for predicate in
                        preds))
@@ -351,7 +349,6 @@
    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
                response = result
@@ -369,6 +366,8 @@
                            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:
@@ -421,6 +420,12 @@
        self.attr = kw.get('attr')
    def __call__(self, view):
        if is_unbound_method(view) and self.attr is None:
            raise ConfigurationError((
                'Unbound method calls are not supported, please set the class '
                'as your `view` and the method as your `attr`'
            ))
        if inspect.isclass(view):
            view = self.map_class(view)
        else:
@@ -844,6 +849,18 @@
          very useful for 'civilians' who are just developing stock Pyramid
          applications. Pay no attention to the man behind the curtain.
        accept
          This value represents a match query for one or more mimetypes in the
          ``Accept`` HTTP request header.  If this value is specified, it must
          be in one of the following forms: a mimetype match token in the form
          ``text/plain``, a wildcard mimetype match token in the form
          ``text/*`` or a match-all wildcard mimetype match token in the form
          ``*/*``.  If any of the forms matches the ``Accept`` header of the
          request, or if the ``Accept`` header isn't set at all in the request,
          this will match the current view. If this does not match the
          ``Accept`` header of the request, view matching continues.
        Predicate Arguments
        name
@@ -894,8 +911,8 @@
        request_param
          This value can be any string or any sequence of strings.  A view
          declaration with this argument ensures that the view will only be
          This value can be any string or any sequence of strings.  A view
          declaration with this argument ensures that the view will only be
          called when the :term:`request` has a key in the ``request.params``
          dictionary (an HTTP ``GET`` or ``POST`` variable) that has a
          name which matches the supplied value (if the value is a string)
@@ -944,17 +961,6 @@
          This is useful for detecting AJAX requests issued from
          jQuery, Prototype and other Javascript libraries.
        accept
          The value of this argument represents a match query for one
          or more mimetypes in the ``Accept`` HTTP request header.  If
          this value is specified, it must be in one of the following
          forms: a mimetype match token in the form ``text/plain``, a
          wildcard mimetype match token in the form ``text/*`` or a
          match-all wildcard mimetype match token in the form ``*/*``.
          If any of the forms matches the ``Accept`` header of the
          request, this predicate will be true.
        header
          This value represents an HTTP header name or a header
@@ -1001,7 +1007,7 @@
          Note that using this feature requires a :term:`session factory` to
          have been configured.
          .. versionadded:: 1.4a2
        physical_path
@@ -1039,7 +1045,7 @@
                This value should be a sequence of references to custom
                predicate callables.  Use custom predicates when no set of
                predefined predicates do what you need.  Custom predicates
                can be combined with predefined predicates as necessary.
                can be combined with predefined predicates as necessary.
                Each custom predicate callable should accept two arguments:
                ``context`` and ``request`` and should return either
                ``True`` or ``False`` after doing arbitrary evaluation of
@@ -1074,7 +1080,7 @@
                DeprecationWarning,
                stacklevel=4
                )
        view = self.maybe_dotted(view)
        context = self.maybe_dotted(context)
        for_ = self.maybe_dotted(for_)
@@ -1160,7 +1166,7 @@
            view_desc = self.object_description(view)
        tmpl_intr = None
        view_intr = self.introspectable('views',
                                        discriminator,
                                        view_desc,
@@ -1189,10 +1195,6 @@
        predlist = self.get_predlist('view')
        def register(permission=permission, renderer=renderer):
            # the discrim_func above is guaranteed to have been called already
            order = view_intr['order']
            preds = view_intr['predicates']
            phash = view_intr['phash']
            request_iface = IRequest
            if route_name is not None:
                request_iface = self.registry.queryUtility(IRouteRequest,
@@ -1569,7 +1571,7 @@
        wrapper=None,
        route_name=None,
        request_type=None,
        request_method=None,
        request_method=None,
        request_param=None,
        containment=None,
        xhr=None,
@@ -1595,9 +1597,12 @@
            config.add_forbidden_view(forbidden)
        If ``view`` argument is not provided, the view callable defaults to
        :func:`~pyramid.httpexceptions.default_exceptionresponse_view`.
        All arguments have the same meaning as
        :meth:`pyramid.config.Configurator.add_view` and each predicate
        argument restricts the set of circumstances under which this notfound
        argument restricts the set of circumstances under which this forbidden
        view will be invoked.  Unlike
        :meth:`pyramid.config.Configurator.add_view`, this method will raise
        an exception if passed ``name``, ``permission``, ``context``,
@@ -1612,7 +1617,10 @@
                    '%s may not be used as an argument to add_forbidden_view'
                    % arg
                    )
        if view is None:
            view = default_exceptionresponse_view
        settings = dict(
            view=view,
            context=HTTPForbidden,
@@ -1623,7 +1631,7 @@
            containment=containment,
            xhr=xhr,
            accept=accept,
            header=header,
            header=header,
            path_info=path_info,
            custom_predicates=custom_predicates,
            decorator=decorator,
@@ -1638,7 +1646,7 @@
        return self.add_view(**settings)
    set_forbidden_view = add_forbidden_view # deprecated sorta-bw-compat alias
    @viewdefaults
    @action_method
    def add_notfound_view(
@@ -1649,7 +1657,7 @@
        wrapper=None,
        route_name=None,
        request_type=None,
        request_method=None,
        request_method=None,
        request_param=None,
        containment=None,
        xhr=None,
@@ -1675,6 +1683,9 @@
            config.add_notfound_view(notfound)
        If ``view`` argument is not provided, the view callable defaults to
        :func:`~pyramid.httpexceptions.default_exceptionresponse_view`.
        All arguments except ``append_slash`` have the same meaning as
        :meth:`pyramid.config.Configurator.add_view` and each predicate
        argument restricts the set of circumstances under which this notfound
@@ -1692,6 +1703,24 @@
        Pyramid will return the result of the view callable provided as
        ``view``, as normal.
        If the argument provided as ``append_slash`` is not a boolean but
        instead implements :class:`~pyramid.interfaces.IResponse`, the
        append_slash logic will behave as if ``append_slash=True`` was passed,
        but the provided class will be used as the response class instead of
        the default :class:`~pyramid.httpexceptions.HTTPFound` response class
        when a redirect is performed.  For example:
          .. code-block:: python
            from pyramid.httpexceptions import HTTPMovedPermanently
            config.add_notfound_view(append_slash=HTTPMovedPermanently)
        The above means that a redirect to a slash-appended route will be
        attempted, but instead of :class:`~pyramid.httpexceptions.HTTPFound`
        being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will
        be used` for the redirect response if a slash-appended route is found.
        .. versionchanged:: 1.6
        .. versionadded:: 1.3
        """
        for arg in ('name', 'permission', 'context', 'for_', 'http_cache'):
@@ -1700,7 +1729,10 @@
                    '%s may not be used as an argument to add_notfound_view'
                    % arg
                    )
        if view is None:
            view = default_exceptionresponse_view
        settings = dict(
            view=view,
            context=HTTPNotFound,
@@ -1711,7 +1743,7 @@
            containment=containment,
            xhr=xhr,
            accept=accept,
            header=header,
            header=header,
            path_info=path_info,
            custom_predicates=custom_predicates,
            decorator=decorator,
@@ -1723,7 +1755,12 @@
        settings.update(predicates)
        if append_slash:
            view = self._derive_view(view, attr=attr, renderer=renderer)
            view = AppendSlashNotFoundViewFactory(view)
            if IResponse.implementedBy(append_slash):
                view = AppendSlashNotFoundViewFactory(
                    view, redirect_class=append_slash,
                )
            else:
                view = AppendSlashNotFoundViewFactory(view)
            settings['view'] = view
        else:
            settings['attr'] = attr
@@ -1786,7 +1823,20 @@
        ``Expires`` and ``Cache-Control`` headers for static assets served.
        Note that this argument has no effect when the ``name`` is a *url
        prefix*.  By default, this argument is ``None``, meaning that no
        particular Expires or Cache-Control headers are set in the response.
        particular Expires or Cache-Control headers are set in the response,
        unless ``cachebust`` is specified.
        The ``cachebust`` keyword argument may be set to cause
        :meth:`~pyramid.request.Request.static_url` to use cache busting when
        generating URLs. See :ref:`cache_busting` for general information
        about cache busting.  The value of the ``cachebust`` argument may be
        ``True``, in which case a default cache busting implementation is used.
        The value of the ``cachebust`` argument may also be an object which
        implements :class:`~pyramid.interfaces.ICacheBuster`.  See the
        :mod:`~pyramid.static` module for some implementations.  If the
        ``cachebust`` argument is provided, the default for ``cache_max_age``
        is modified to be ten years.  ``cache_max_age`` may still be explicitly
        provided to override this default.
        The ``permission`` keyword argument is used to specify the
        :term:`permission` required by a user to execute the static view.  By
@@ -1884,6 +1934,8 @@
@implementer(IStaticURLInfo)
class StaticURLInfo(object):
    # Indirection for testing
    _default_cachebust = PathSegmentMd5CacheBuster
    def _get_registrations(self, registry):
        try:
@@ -1897,11 +1949,14 @@
            registry = request.registry
        except AttributeError: # bw compat (for tests)
            registry = get_current_registry()
        for (url, spec, route_name) in self._get_registrations(registry):
        registrations = self._get_registrations(registry)
        for (url, spec, route_name, cachebust) in registrations:
            if path.startswith(spec):
                subpath = path[len(spec):]
                if WIN: # pragma: no cover
                    subpath = subpath.replace('\\', '/') # windows
                if cachebust:
                    subpath, kw = cachebust(subpath, kw)
                if url is None:
                    kw['subpath'] = subpath
                    return request.route_url(route_name, **kw)
@@ -1928,7 +1983,7 @@
            sep = os.sep
        else:
            sep = '/'
        if not spec.endswith(sep):
        if not spec.endswith(sep) and not spec.endswith(':'):
            spec = spec + sep
        # we also make sure the name ends with a slash, purely as a
@@ -1941,6 +1996,21 @@
            # make sure it ends with a slash
            name = name + '/'
        if config.registry.settings.get('pyramid.prevent_cachebust'):
            cb = None
        else:
            cb = extra.pop('cachebust', None)
        if cb is True:
            cb = self._default_cachebust()
        if cb:
            def cachebust(subpath, kw):
                subpath_tuple = tuple(subpath.split('/'))
                subpath_tuple, kw = cb.pregenerate(
                    spec + subpath, subpath_tuple, kw)
                return '/'.join(subpath_tuple), kw
        else:
            cachebust = None
        if url_parse(name).netloc:
            # it's a URL
            # url, spec, route_name
@@ -1949,10 +2019,14 @@
        else:
            # it's a view name
            url = None
            cache_max_age = extra.pop('cache_max_age', None)
            ten_years = 10 * 365 * 24 * 60 * 60  # more or less
            default = ten_years if cb else None
            cache_max_age = extra.pop('cache_max_age', default)
            # create a view
            cb_match = getattr(cb, 'match', None)
            view = static_view(spec, cache_max_age=cache_max_age,
                               use_subpath=True)
                               use_subpath=True, cachebust_match=cb_match)
            # Mutate extra to allow factory, etc to be passed through here.
            # Treat permission specially because we'd like to default to
@@ -1993,7 +2067,7 @@
                registrations.pop(idx)
            # url, spec, route_name
            registrations.append((url, spec, route_name))
            registrations.append((url, spec, route_name, cachebust))
        intr = config.introspectable('static views',
                                     name,
@@ -2003,5 +2077,4 @@
        intr['spec'] = spec
        config.action(None, callable=register, introspectables=(intr,))
pyramid/decorator.py
@@ -1,3 +1,6 @@
import functools
class reify(object):
    """ Use as a class method decorator.  It operates almost exactly like the
    Python ``@property`` decorator, but it puts the result of the method it
@@ -26,10 +29,7 @@
    """
    def __init__(self, wrapped):
        self.wrapped = wrapped
        try:
            self.__doc__ = wrapped.__doc__
        except: # pragma: no cover
            pass
        functools.update_wrapper(self, wrapped)
    def __get__(self, inst, objtype=None):
        if inst is None:
pyramid/httpexceptions.py
@@ -52,6 +52,9 @@
        * 422 - HTTPUnprocessableEntity
        * 423 - HTTPLocked
        * 424 - HTTPFailedDependency
        * 428 - HTTPPreconditionRequired
        * 429 - HTTPTooManyRequests
        * 431 - HTTPRequestHeaderFieldsTooLarge
      HTTPServerError
        * 500 - HTTPInternalServerError
        * 501 - HTTPNotImplemented
@@ -868,7 +871,12 @@
    subclass of :class:`~HTTPClientError`
    This indicates that the server is unable to process the contained
    instructions. Only for WebDAV.
    instructions.
    May be used to notify the client that their JSON/XML is well formed, but
    not correct for the current request.
    See RFC4918 section 11 for more information.
    
    code: 422, title: Unprocessable Entity
    """
@@ -881,7 +889,7 @@
    """
    subclass of :class:`~HTTPClientError`
    This indicates that the resource is locked. Only for WebDAV
    This indicates that the resource is locked.
    
    code: 423, title: Locked
    """
@@ -896,7 +904,6 @@
    This indicates that the method could not be performed because the
    requested action depended on another action and that action failed.
    Only for WebDAV.
    
    code: 424, title: Failed Dependency
    """
@@ -907,6 +914,62 @@
        'The method could not be performed because the requested '
        'action dependended on another action and that action failed')
class HTTPPreconditionRequired(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This indicates that the origin server requires the
    request to be conditional.
    Its typical use is to avoid the "lost update" problem, where a client
    GETs a resource's state, modifies it, and PUTs it back to the server,
    when meanwhile a third party has modified the state on the server,
    leading to a conflict.  By requiring requests to be conditional, the
    server can assure that clients are working with the correct copies.
    RFC 6585.3
    code: 428, title: Precondition Required
    """
    code = 428
    title = 'Precondition Required'
    explanation = (
        'The origin server requires the request to be conditional.')
class HTTPTooManyRequests(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This indicates that the user has sent too many
    requests in a given amount of time ("rate limiting").
    RFC 6585.4
    code: 429, title: Too Many Requests
    """
    code = 429
    title = 'Too Many Requests'
    explanation = (
        'The action could not be performed because there were too '
        'many requests by the client.')
class HTTPRequestHeaderFieldsTooLarge(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This indicates that the server is unwilling to process
    the request because its header fields are too large.  The request MAY
    be resubmitted after reducing the size of the request header fields.
    RFC 6585.5
    code: 431, title: Request Header Fields Too Large
    """
    code = 431
    title = 'Request Header Fields Too Large'
    explanation = (
        'The requests header fields were too large.')
############################################################
## 5xx Server Error
############################################################
pyramid/i18n.py
@@ -331,9 +331,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):
@@ -352,10 +352,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
@@ -439,36 +442,55 @@
class IAuthenticationPolicy(Interface):
    """ An object representing a Pyramid authentication policy. """
    def authenticated_userid(request):
        """ Return the authenticated userid or ``None`` if no authenticated
        userid can be found. This method of the policy should ensure that a
        record exists in whatever persistent store is used related to the
        user (the user should not have been deleted); if a record associated
        with the current id does not exist in a persistent store, it should
        return ``None``."""
        """ Return the authenticated :term:`userid` or ``None`` if
        no authenticated userid can be found. This method of the
        policy should ensure that a record exists in whatever
        persistent store is used related to the user (the user
        should not have been deleted); if a record associated with
        the current id does not exist in a persistent store, it
        should return ``None``.
        """
    def unauthenticated_userid(request):
        """ Return the *unauthenticated* userid.  This method performs the
        same duty as ``authenticated_userid`` but is permitted to return the
        userid based only on data present in the request; it needn't (and
        shouldn't) check any persistent store to ensure that the user record
        related to the request userid exists."""
        """ Return the *unauthenticated* userid.  This method
        performs the same duty as ``authenticated_userid`` but is
        permitted to return the userid based only on data present
        in the request; it needn't (and shouldn't) check any
        persistent store to ensure that the user record related to
        the request userid exists.
        This method is intended primarily a helper to assist the
        ``authenticated_userid`` method in pulling credentials out
        of the request data, abstracting away the specific headers,
        query strings, etc that are used to authenticate the request.
        """
    def effective_principals(request):
        """ Return a sequence representing the effective principals
        including the userid and any groups belonged to by the current
        user, including 'system' groups such as Everyone and
        Authenticated. """
        typically including the :term:`userid` and any groups belonged
        to by the current user, always including 'system' groups such
        as ``pyramid.security.Everyone`` and
        ``pyramid.security.Authenticated``.
    def remember(request, principal, **kw):
        """
    def remember(request, userid, **kw):
        """ Return a set of headers suitable for 'remembering' the
        principal named ``principal`` when set in a response.  An
        individual authentication policy and its consumers can decide
        on the composition and meaning of ``**kw.`` """
        :term:`userid` named ``userid`` when set in a response.  An
        individual authentication policy and its consumers can
        decide on the composition and meaning of ``**kw``.
        """
    def forget(request):
        """ Return a set of headers suitable for 'forgetting' the
        current user on subsequent requests. """
        current user on subsequent requests.
        """
class IAuthorizationPolicy(Interface):
    """ An object representing a Pyramid authorization policy. """
@@ -563,18 +585,16 @@
        """ Generate a URL for the given path """
class IResponseFactory(Interface):
    """ A utility which generates a response factory """
    def __call__():
        """ Return a response factory (e.g. a callable that returns an object
        implementing IResponse, e.g. :class:`pyramid.response.Response`). It
        should accept all the arguments that the Pyramid Response class
        accepts."""
    """ A utility which generates a response """
    def __call__(request):
        """ Return a response object implementing IResponse,
        e.g. :class:`pyramid.response.Response`). It should handle the
        case when ``request`` is ``None``."""
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
@@ -708,7 +728,7 @@
    pregenerator = Attribute('This attribute should either be ``None`` or '
                             'a callable object implementing the '
                             '``IRoutePregenerator`` interface')
    def match(path):
        """
        If the ``path`` passed to this function can be matched by the
@@ -803,7 +823,7 @@
    # <__main__.Fudge object at 0x1cda890>
    # <object object at 0x7fa678f3e2a0> <object object at 0x7fa678f3e2a0>
    # <__main__.Another object at 0x1cda850>
    def virtual_root():
        """ Return the virtual root related to a request and the
        current context"""
@@ -837,9 +857,9 @@
    def get_code(fullname):
        """ Return the code object for the module identified by 'fullname'.
        Return 'None' if it's a built-in or extension module.
        If the loader doesn't have the code object but it does have the source
        code, return the compiled source code.
@@ -848,16 +868,16 @@
    def get_source(fullname):
        """ Return the source code for the module identified by 'fullname'.
        Return a string, using newline characters for line endings, or None
        if the source is not available.
        Raise ImportError if the module can't be found by the importer at all.
        """
    def get_filename(fullname):
        """ Return the value of '__file__' if the named module was loaded.
        If the module is not found, raise ImportError.
        """
@@ -1164,10 +1184,54 @@
class IPredicateList(Interface):
    """ Interface representing a predicate list """
class ICacheBuster(Interface):
    """
    Instances of ``ICacheBuster`` may be provided as arguments to
    :meth:`~pyramid.config.Configurator.add_static_view`.  Instances of
    ``ICacheBuster`` provide mechanisms for generating a cache bust token for
    a static asset, modifying a static asset URL to include a cache bust token,
    and, optionally, unmodifying a static asset URL in order to look up an
    asset.  See :ref:`cache_busting`.
    .. versionadded:: 1.6
    """
    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 ``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
        :meth:`~pyramid.request.Request.route_url` for URL generation.  The
        return value should be a two-tuple of ``(subpath, kw)`` which are
        versions of the same arguments modified to include the cachebust token
        in the generated URL.
        """
    def match(subpath):
        """
        Performs the logical inverse of
        :meth:`~pyramid.interfaces.ICacheBuster.pregenerate` by taking a
        subpath from a cache busted URL and removing the cache bust token, so
        that :app:`Pyramid` can find the underlying asset.
        ``subpath`` is the subpath portion of the URL for an incoming request
        for a static asset.  The return value should be the same tuple with the
        cache busting token elided.
        If the cache busting scheme in use doesn't specifically modify the path
        portion of the generated URL (e.g. it adds a query string), a method
        which implements this interface may not be necessary.  It is
        permissible for an instance of
        :class:`~pyramid.interfaces.ICacheBuster` to omit this method.
        """
# configuration phases: a lower phase number means the actions associated
# 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/path.py
@@ -337,8 +337,14 @@
                value = package.__name__
            else:
                value = package.__name__ + value
        return pkg_resources.EntryPoint.parse(
            'x=%s' % value).load(False)
        # Calling EntryPoint.load with an argument is deprecated.
        # See https://pythonhosted.org/setuptools/history.html#id8
        ep = pkg_resources.EntryPoint.parse('x=%s' % value)
        if hasattr(ep, 'resolve'):
            # setuptools>=10.2
            return ep.resolve()  # pragma: NO COVER
        else:
            return ep.load(False)  # pragma: NO COVER
    def _zope_dottedname_style(self, value, package):
        """ package.module.attr style """
pyramid/registry.py
@@ -5,6 +5,7 @@
from zope.interface.registry import Components
from pyramid.compat import text_
from pyramid.decorator import reify
from pyramid.interfaces import (
    ISettings,
@@ -42,6 +43,10 @@
        # defeat bool determination via dict.__len__
        return True
    @reify
    def package_name(self):
        return self.__name__
    def registerSubscriptionAdapter(self, *arg, **kw):
        result = Components.registerSubscriptionAdapter(self, *arg, **kw)
        self.has_listeners = True
pyramid/renderers.py
@@ -1,3 +1,4 @@
import contextlib
import json
import os
@@ -10,7 +11,6 @@
from pyramid.interfaces import (
    IJSONAdapter,
    IRendererFactory,
    IResponseFactory,
    IRendererInfo,
    )
@@ -25,7 +25,7 @@
from pyramid.path import caller_package
from pyramid.response import Response
from pyramid.response import _get_response_factory
from pyramid.threadlocal import get_current_registry
# API
@@ -74,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
@@ -122,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:
@@ -135,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``.
@@ -248,7 +273,7 @@
        When you've done this, the JSON renderer will be able to serialize
        instances of the ``Foo`` class when they're encountered in your view
        results."""
        self.components.registerAdapter(adapter, (type_or_iface,),
                                        IJSONAdapter)
@@ -265,7 +290,7 @@
                    response.content_type = 'application/json'
            default = self._make_default(request)
            return self.serializer(value, default=default, **self.kw)
        return _render
    def _make_default(self, request):
@@ -286,7 +311,7 @@
class JSONP(JSON):
    """ `JSONP <http://en.wikipedia.org/wiki/JSONP>`_ renderer factory helper
    which implements a hybrid json/jsonp renderer.  JSONP is useful for
    making cross-domain AJAX requests.
    making cross-domain AJAX requests.
    Configure a JSONP renderer using the
    :meth:`pyramid.config.Configurator.add_renderer` API at application
@@ -309,7 +334,7 @@
       config = Configurator()
       config.add_renderer('jsonp', JSONP(param_name='callback', indent=4))
    .. versionchanged:: 1.4
       The ability of this class to accept a ``**kw`` in its constructor.
@@ -356,19 +381,19 @@
        ``self.param_name`` is present in request.GET; otherwise returns
        plain-JSON encoded string with content-type ``application/json``"""
        def _render(value, system):
            request = system['request']
            request = system.get('request')
            default = self._make_default(request)
            val = self.serializer(value, default=default, **self.kw)
            callback = request.GET.get(self.param_name)
            if callback is None:
                ct = 'application/json'
                body = val
            else:
                ct = 'application/javascript'
                body = '%s(%s);' % (callback, val)
            response = request.response
            if response.content_type == response.default_content_type:
                response.content_type = ct
            ct = 'application/json'
            body = val
            if request is not None:
                callback = request.GET.get(self.param_name)
                if callback is not None:
                    ct = 'application/javascript'
                    body = '%s(%s);' % (callback, val)
                response = request.response
                if response.content_type == response.default_content_type:
                    response.content_type = ct
            return body
        return _render
@@ -448,14 +473,16 @@
        if response is None:
            # request is None or request is not a pyramid.response.Response
            registry = self.registry
            response_factory = registry.queryUtility(IResponseFactory,
                                                     default=Response)
            response = response_factory()
            response_factory = _get_response_factory(registry)
            response = response_factory(request)
        if result is not None:
            if isinstance(result, text_type):
                response.text = result
            elif isinstance(result, bytes):
                response.body = result
            elif hasattr(result, '__iter__'):
                response.app_iter = result
            else:
                response.body = result
@@ -487,18 +514,18 @@
    @property
    def settings(self):
        return get_current_registry().settings or {}
        return {}
    def render_view(self, request, value, view, context):
        return value
    def render(self, value, system_values, request=None):
        return value
    def render_to_response(self, value, system_values, request=None):
        return value
    def clone(self, name=None, package=None, registry=None):
        return self
null_renderer = NullRendererHelper()
pyramid/request.py
@@ -1,3 +1,4 @@
from collections import deque
import json
from zope.interface import implementer
@@ -7,33 +8,37 @@
from pyramid.interfaces import (
    IRequest,
    IRequestExtensions,
    IResponse,
    ISessionFactory,
    IResponseFactory,
    )
from pyramid.compat import (
    text_,
    bytes_,
    native_,
    iteritems_,
    )
from pyramid.decorator import reify
from pyramid.i18n import LocalizerRequestMixin
from pyramid.response import Response
from pyramid.response import Response,  _get_response_factory
from pyramid.security import (
    AuthenticationAPIMixin,
    AuthorizationAPIMixin,
    )
from pyramid.url import URLMethodsMixin
from pyramid.util import InstancePropertyMixin
from pyramid.util import (
    InstancePropertyHelper,
    InstancePropertyMixin,
)
class TemplateContext(object):
    pass
class CallbackMethodsMixin(object):
    response_callbacks = ()
    finished_callbacks = ()
    response_callbacks = None
    finished_callbacks = None
    def add_response_callback(self, callback):
        """
        Add a callback to the set of callbacks to be called by the
@@ -72,15 +77,15 @@
        """
        callbacks = self.response_callbacks
        if not callbacks:
            callbacks = []
        if callbacks is None:
            callbacks = deque()
        callbacks.append(callback)
        self.response_callbacks = callbacks
    def _process_response_callbacks(self, response):
        callbacks = self.response_callbacks
        while callbacks:
            callback = callbacks.pop(0)
            callback = callbacks.popleft()
            callback(self, response)
    def add_finished_callback(self, callback):
@@ -132,15 +137,15 @@
        """
        callbacks = self.finished_callbacks
        if not callbacks:
            callbacks = []
        if callbacks is None:
            callbacks = deque()
        callbacks.append(callback)
        self.finished_callbacks = callbacks
    def _process_finished_callbacks(self):
        callbacks = self.finished_callbacks
        while callbacks:
            callback = callbacks.pop(0)
            callback = callbacks.popleft()
            callback(self)
@implementer(IRequest)
@@ -213,10 +218,8 @@
        right" attributes (e.g. by calling ``request.response.set_cookie()``)
        within a view that uses a renderer.  Mutations to this response object
        will be preserved in the response sent to the client."""
        registry = self.registry
        response_factory = registry.queryUtility(IResponseFactory,
                                                 default=Response)
        return response_factory()
        response_factory = _get_response_factory(self.registry)
        return response_factory(self)
    def is_response(self, ob):
        """ Return ``True`` if the object passed as ``ob`` is a valid
@@ -309,3 +312,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/response.py
@@ -8,7 +8,8 @@
from webob import Response as _Response
from zope.interface import implementer
from pyramid.interfaces import IResponse
from pyramid.interfaces import IResponse, IResponseFactory
def init_mimetypes(mimetypes):
    # this is a function so it can be unittested
@@ -143,7 +144,7 @@
        @response_adapter(dict, list)
        def myadapter(ob):
            return Response(json.dumps(ob))
    This method will have no effect until a :term:`scan` is performed
    agains the package or module which contains it, ala:
@@ -167,3 +168,15 @@
    def __call__(self, wrapped):
        self.venusian.attach(wrapped, self.register, category='pyramid')
        return wrapped
def _get_response_factory(registry):
    """ Obtain a :class: `pyramid.response.Response` using the
    `pyramid.interfaces.IResponseFactory`.
    """
    response_factory = registry.queryUtility(
        IResponseFactory,
        default=lambda r: Response()
    )
    return response_factory
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/alchemy/development.ini_tmpl
@@ -68,4 +68,4 @@
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
pyramid/scaffolds/alchemy/production.ini_tmpl
@@ -59,4 +59,4 @@
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
pyramid/scaffolds/starter/development.ini_tmpl
@@ -57,4 +57,4 @@
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
pyramid/scaffolds/starter/production.ini_tmpl
@@ -51,4 +51,4 @@
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
pyramid/scaffolds/tests.py
@@ -6,9 +6,9 @@
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
pyramid/scaffolds/zodb/development.ini_tmpl
@@ -62,4 +62,4 @@
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
pyramid/scaffolds/zodb/production.ini_tmpl
@@ -57,4 +57,4 @@
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
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/pcreate.py
@@ -18,7 +18,7 @@
class PCreateCommand(object):
    verbosity = 1 # required
    description = "Render Pyramid scaffolding to an output directory"
    usage = "usage: %prog [options] output_directory"
    usage = "usage: %prog [options] -s <scaffold> output_directory"
    parser = optparse.OptionParser(usage, description=description)
    parser.add_option('-s', '--scaffold',
                      dest='scaffold_name',
@@ -63,8 +63,16 @@
    def run(self):
        if self.options.list:
            return self.show_scaffolds()
        if not self.options.scaffold_name and not self.args:
            if not self.quiet: # pragma: no cover
                self.parser.print_help()
                self.out('')
                self.show_scaffolds()
            return 2
        if not self.options.scaffold_name:
            self.out('You must provide at least one scaffold name')
            self.out('You must provide at least one scaffold name: -s <scaffold name>')
            self.out('')
            self.show_scaffolds()
            return 2
        if not self.args:
            self.out('You must provide a project name')
@@ -81,7 +89,8 @@
        args = self.args
        output_dir = os.path.abspath(os.path.normpath(args[0]))
        project_name = os.path.basename(os.path.split(output_dir)[1])
        pkg_name = _bad_chars_re.sub('', project_name.lower())
        pkg_name = _bad_chars_re.sub(
            '', project_name.lower().replace('-', '_'))
        safe_name = pkg_resources.safe_name(project_name)
        egg_name = pkg_resources.to_filename(safe_name)
pyramid/scripts/prequest.py
@@ -5,7 +5,7 @@
from pyramid.compat import url_unquote
from pyramid.request import Request
from pyramid.paster import get_app
from pyramid.paster import get_app, setup_logging
from pyramid.scripts.common import parse_vars
def main(argv=sys.argv, quiet=False):
@@ -97,12 +97,18 @@
        if not self.quiet:
            print(msg)
    def configure_logging(self, app_spec):
        setup_logging(app_spec)
    def run(self):
        if not len(self.args) >= 2:
            self.out('You must provide at least two arguments')
            return 2
        app_spec = self.args[0]
        path = self.args[1]
        self.configure_logging(app_spec)
        if not path.startswith('/'):
            path = '/' + path
pyramid/scripts/proutes.py
@@ -1,13 +1,232 @@
import fnmatch
import optparse
import sys
import textwrap
import re
from pyramid.paster import bootstrap
from pyramid.compat import (string_types, configparser)
from pyramid.interfaces import (
    IRouteRequest,
    IViewClassifier,
    IView,
)
from pyramid.config import not_
from pyramid.scripts.common import parse_vars
from pyramid.static import static_view
from zope.interface import Interface
PAD = 3
ANY_KEY = '*'
UNKNOWN_KEY = '<unknown>'
def main(argv=sys.argv, quiet=False):
    command = PRoutesCommand(argv, quiet)
    return command.run()
def _get_pattern(route):
    pattern = route.pattern
    if not pattern.startswith('/'):
        pattern = '/%s' % pattern
    return pattern
def _get_print_format(fmt, max_name, max_pattern, max_view, max_method):
    print_fmt = ''
    max_map = {
        'name': max_name,
        'pattern': max_pattern,
        'view': max_view,
        'method': max_method,
    }
    sizes = []
    for index, col in enumerate(fmt):
        size = max_map[col] + PAD
        print_fmt += '{{%s: <{%s}}} ' % (col, index)
        sizes.append(size)
    return print_fmt.format(*sizes)
def _get_request_methods(route_request_methods, view_request_methods):
    excludes = set()
    if route_request_methods:
        route_request_methods = set(route_request_methods)
    if view_request_methods:
        view_request_methods = set(view_request_methods)
        for method in view_request_methods.copy():
            if method.startswith('!'):
                view_request_methods.remove(method)
                excludes.add(method[1:])
    has_route_methods = route_request_methods is not None
    has_view_methods = len(view_request_methods) > 0
    has_methods = has_route_methods or has_view_methods
    if has_route_methods is False and has_view_methods is False:
        request_methods = [ANY_KEY]
    elif has_route_methods is False and has_view_methods is True:
        request_methods = view_request_methods
    elif has_route_methods is True and has_view_methods is False:
        request_methods = route_request_methods
    else:
        request_methods = route_request_methods.intersection(
            view_request_methods
        )
    request_methods = set(request_methods).difference(excludes)
    if has_methods and not request_methods:
        request_methods = '<route mismatch>'
    elif request_methods:
        if excludes and request_methods == set([ANY_KEY]):
            for exclude in excludes:
                request_methods.add('!%s' % exclude)
        request_methods = ','.join(sorted(request_methods))
    return request_methods
def _get_view_module(view_callable):
    if view_callable is None:
        return UNKNOWN_KEY
    if hasattr(view_callable, '__name__'):
        if hasattr(view_callable, '__original_view__'):
            original_view = view_callable.__original_view__
        else:
            original_view = None
        if isinstance(original_view, static_view):
            if original_view.package_name is not None:
                return '%s:%s' % (
                    original_view.package_name,
                    original_view.docroot
                )
            else:
                return original_view.docroot
        else:
            view_name = view_callable.__name__
    else:
        # Currently only MultiView hits this,
        # we could just not run _get_view_module
        # for them and remove this logic
        view_name = str(view_callable)
    view_module = '%s.%s' % (
        view_callable.__module__,
        view_name,
    )
    # If pyramid wraps something in wsgiapp or wsgiapp2 decorators
    # that is currently returned as pyramid.router.decorator, lets
    # hack a nice name in:
    if view_module == 'pyramid.router.decorator':
        view_module = '<wsgiapp>'
    return view_module
def get_route_data(route, registry):
    pattern = _get_pattern(route)
    request_iface = registry.queryUtility(
        IRouteRequest,
        name=route.name
    )
    route_request_methods = None
    view_request_methods_order = []
    view_request_methods = {}
    view_callable = None
    route_intr = registry.introspector.get(
        'routes', route.name
    )
    if request_iface is None:
        return [
            (route.name, _get_pattern(route), UNKNOWN_KEY, ANY_KEY)
        ]
    view_callable = registry.adapters.lookup(
        (IViewClassifier, request_iface, Interface),
        IView,
        name='',
        default=None
    )
    view_module = _get_view_module(view_callable)
    # Introspectables can be turned off, so there could be a chance
    # that we have no `route_intr` but we do have a route + callable
    if route_intr is None:
        view_request_methods[view_module] = []
        view_request_methods_order.append(view_module)
    else:
        if route_intr.get('static', False) is True:
            return [
                (route.name, route_intr['external_url'], UNKNOWN_KEY, ANY_KEY)
            ]
        route_request_methods = route_intr['request_methods']
        view_intr = registry.introspector.related(route_intr)
        if view_intr:
            for view in view_intr:
                request_method = view.get('request_methods')
                if request_method is not None:
                    view_callable = view['callable']
                    view_module = _get_view_module(view_callable)
                    if view_module not in view_request_methods:
                        view_request_methods[view_module] = []
                        view_request_methods_order.append(view_module)
                    if isinstance(request_method, string_types):
                        request_method = (request_method,)
                    elif isinstance(request_method, not_):
                        request_method = ('!%s' % request_method.value,)
                    view_request_methods[view_module].extend(request_method)
                else:
                    if view_module not in view_request_methods:
                        view_request_methods[view_module] = []
                        view_request_methods_order.append(view_module)
        else:
            view_request_methods[view_module] = []
            view_request_methods_order.append(view_module)
    final_routes = []
    for view_module in view_request_methods_order:
        methods = view_request_methods[view_module]
        request_methods = _get_request_methods(
            route_request_methods,
            methods
        )
        final_routes.append((
            route.name,
            pattern,
            view_module,
            request_methods,
        ))
    return final_routes
class PRoutesCommand(object):
    description = """\
@@ -25,62 +244,153 @@
    bootstrap = (bootstrap,)
    stdout = sys.stdout
    usage = '%prog config_uri'
    ConfigParser = configparser.ConfigParser # testing
    parser = optparse.OptionParser(
        usage,
        description=textwrap.dedent(description)
        )
    )
    parser.add_option('-g', '--glob',
                      action='store', type='string', dest='glob',
                      default='', help='Display routes matching glob pattern')
    parser.add_option('-f', '--format',
                      action='store', type='string', dest='format',
                      default='', help=('Choose which columns to display, this '
                                        'will override the format key in the '
                                        '[proutes] ini section'))
    def __init__(self, argv, quiet=False):
        self.options, self.args = self.parser.parse_args(argv[1:])
        self.quiet = quiet
        self.available_formats = [
            'name', 'pattern', 'view', 'method'
        ]
        self.column_format = self.available_formats
    def validate_formats(self, formats):
        invalid_formats = []
        for fmt in formats:
            if fmt not in self.available_formats:
                invalid_formats.append(fmt)
        msg = (
            'You provided invalid formats %s, '
            'Available formats are %s'
        )
        if invalid_formats:
            msg = msg % (invalid_formats, self.available_formats)
            self.out(msg)
            return False
        return True
    def proutes_file_config(self, filename):
        config = self.ConfigParser()
        config.read(filename)
        try:
            items = config.items('proutes')
            for k, v in items:
                if 'format' == k:
                    cols = re.split(r'[,|\s|\n]*', v)
                    self.column_format = [x.strip() for x in cols]
        except configparser.NoSectionError:
            return
    def out(self, msg):  # pragma: no cover
        if not self.quiet:
            print(msg)
    def _get_mapper(self, registry):
        from pyramid.config import Configurator
        config = Configurator(registry = registry)
        config = Configurator(registry=registry)
        return config.get_routes_mapper()
    def out(self, msg): # pragma: no cover
        if not self.quiet:
            print(msg)
    def run(self, quiet=False):
        if not self.args:
            self.out('requires a config file argument')
            return 2
        from pyramid.interfaces import IRouteRequest
        from pyramid.interfaces import IViewClassifier
        from pyramid.interfaces import IView
        from zope.interface import Interface
        config_uri = self.args[0]
        env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:]))
        registry = env['registry']
        mapper = self._get_mapper(registry)
        if mapper is not None:
            routes = mapper.get_routes()
            fmt = '%-15s %-30s %-25s'
            if not routes:
                return 0
            self.out(fmt % ('Name', 'Pattern', 'View'))
            self.out(
                fmt % ('-'*len('Name'), '-'*len('Pattern'), '-'*len('View')))
            for route in routes:
                pattern = route.pattern
                if not pattern.startswith('/'):
                    pattern = '/' + pattern
                request_iface = registry.queryUtility(IRouteRequest,
                                                      name=route.name)
                view_callable = None
                if (request_iface is None) or (route.factory is not None):
                    self.out(fmt % (route.name, pattern, '<unknown>'))
                else:
                    view_callable = registry.adapters.lookup(
                        (IViewClassifier, request_iface, Interface),
                        IView, name='', default=None)
                    self.out(fmt % (route.name, pattern, view_callable))
        self.proutes_file_config(config_uri)
        if self.options.format:
            columns = self.options.format.split(',')
            self.column_format = [x.strip() for x in columns]
        is_valid = self.validate_formats(self.column_format)
        if is_valid is False:
            return 2
        if mapper is None:
            return 0
        max_name = len('Name')
        max_pattern = len('Pattern')
        max_view = len('View')
        max_method = len('Method')
        routes = mapper.get_routes(include_static=True)
        if len(routes) == 0:
            return 0
        mapped_routes = [{
            'name': 'Name',
            'pattern': 'Pattern',
            'view': 'View',
            'method': 'Method'
        },{
            'name': '----',
            'pattern': '-------',
            'view': '----',
            'method': '------'
        }]
        for route in routes:
            route_data = get_route_data(route, registry)
            for name, pattern, view, method in route_data:
                if self.options.glob:
                    match = (fnmatch.fnmatch(name, self.options.glob) or
                             fnmatch.fnmatch(pattern, self.options.glob))
                    if not match:
                        continue
                if len(name) > max_name:
                    max_name = len(name)
                if len(pattern) > max_pattern:
                    max_pattern = len(pattern)
                if len(view) > max_view:
                    max_view = len(view)
                if len(method) > max_method:
                    max_method = len(method)
                mapped_routes.append({
                    'name': name,
                    'pattern': pattern,
                    'view': view,
                    'method': method
                })
        fmt = _get_print_format(
            self.column_format, max_name, max_pattern, max_view, max_method
        )
        for route in mapped_routes:
            self.out(fmt.format(**route))
        return 0
if __name__ == '__main__': # pragma: no cover
if __name__ == '__main__':  # pragma: no cover
    sys.exit(main() or 0)
pyramid/scripts/pserve.py
@@ -21,9 +21,11 @@
import threading
import time
import traceback
import webbrowser
from paste.deploy import loadserver
from paste.deploy import loadapp
from paste.deploy.loadwsgi import loadcontext, SERVER
from pyramid.compat import PY3
from pyramid.compat import WIN
@@ -33,6 +35,11 @@
from pyramid.scripts.common import parse_vars
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
@@ -121,6 +128,11 @@
        dest='monitor_restart',
        action='store_true',
        help="Auto-restart server if it dies")
    parser.add_option(
        '-b', '--browser',
        dest='browser',
        action='store_true',
        help="Open a web browser to server url")
    parser.add_option(
        '--status',
        action='store_true',
@@ -333,6 +345,17 @@
                else:
                    msg = ''
                self.out('Exiting%s (-v to see traceback)' % msg)
        if self.options.browser:
            def open_browser():
                context = loadcontext(SERVER, app_spec, name=app_name, relative_to=base,
                        global_conf=vars)
                url = 'http://127.0.0.1:{port}/'.format(**context.config())
                time.sleep(1)
                webbrowser.open(url)
            t = threading.Thread(target=open_browser)
            t.setDaemon(True)
            t.start()
        serve()
@@ -691,15 +714,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/scripts/pshell.py
@@ -1,9 +1,11 @@
from code import interact
import optparse
import os
import sys
import textwrap
from pyramid.compat import configparser
from pyramid.compat import exec_
from pyramid.util import DottedNameResolver
from pyramid.paster import bootstrap
@@ -51,6 +53,7 @@
    loaded_objects = {}
    object_help = {}
    setup = None
    pystartup = os.environ.get('PYTHONSTARTUP')
    def __init__(self, argv, quiet=False):
        self.quiet = quiet
@@ -144,6 +147,12 @@
        if shell is None:
            shell = self.make_shell()
        if self.pystartup and os.path.isfile(self.pystartup):
            with open(self.pystartup, 'rb') as fp:
                exec_(fp.read().decode('utf-8'), env)
            if '__builtins__' in env:
                del env['__builtins__']
        try:
            shell(env, help)
        finally:
pyramid/security.py
@@ -17,6 +17,8 @@
Allow = 'Allow'
Deny = 'Deny'
_marker = object()
class AllPermissionsList(object):
    """ Stand in 'permission list' to represent all permissions """
    def __iter__(self):
@@ -115,16 +117,16 @@
    '"effective_principals" attribute of the Pyramid request instead.'
    )
def remember(request, principal, **kw):
def remember(request, userid=_marker, **kw):
    """
    Returns a sequence of header tuples (e.g. ``[('Set-Cookie', 'foo=abc')]``)
    on this request's response.
    These headers are suitable for 'remembering' a set of credentials
    implied by the data passed as ``principal`` and ``*kw`` using the
    implied by the data passed as ``userid`` and ``*kw`` using the
    current :term:`authentication policy`.  Common usage might look
    like so within the body of a view function (``response`` is
    assumed to be a :term:`WebOb` -style :term:`response` object
    computed previously by the view code)::
    computed previously by the view code):
    .. code-block:: python
@@ -138,11 +140,28 @@
    always return an empty sequence. If used, the composition and
    meaning of ``**kw`` must be agreed upon by the calling code and
    the effective authentication policy.
    .. deprecated:: 1.6
        Renamed the ``principal`` argument to ``userid`` to clarify its
        purpose.
    """
    if userid is _marker:
        principal = kw.pop('principal', _marker)
        if principal is _marker:
            raise TypeError(
                'remember() missing 1 required positional argument: '
                '\'userid\'')
        else:
            deprecated(
                'principal',
                'The "principal" argument was deprecated in Pyramid 1.6. '
                'It will be removed in Pyramid 1.9. Use the "userid" '
                'argument instead.')
            userid = principal
    policy = _get_authentication_policy(request)
    if policy is None:
        return []
    return policy.remember(request, principal, **kw)
    return policy.remember(request, userid, **kw)
def forget(request):
    """
@@ -151,12 +170,14 @@
    possessed by the currently authenticated user.  A common usage
    might look like so within the body of a view function
    (``response`` is assumed to be an :term:`WebOb` -style
    :term:`response` object computed previously by the view code)::
    :term:`response` object computed previously by the view code):
      from pyramid.security import forget
      headers = forget(request)
      response.headerlist.extend(headers)
      return response
    .. code-block:: python
       from pyramid.security import forget
       headers = forget(request)
       response.headerlist.extend(headers)
       return response
    If no :term:`authentication policy` is in use, this function will
    always return an empty sequence.
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
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import hashlib
import os
from os.path import (
@@ -26,7 +27,7 @@
    HTTPMovedPermanently,
    )
from pyramid.path import caller_package
from pyramid.path import AssetResolver, caller_package
from pyramid.response import FileResponse
from pyramid.traversal import traversal_path_info
@@ -78,7 +79,7 @@
    """
    def __init__(self, root_dir, cache_max_age=3600, package_name=None,
                 use_subpath=False, index='index.html'):
                 use_subpath=False, index='index.html', cachebust_match=None):
        # package_name is for bw compat; it is preferred to pass in a
        # package-relative path as root_dir
        # (e.g. ``anotherpackage:foo/static``).
@@ -91,13 +92,15 @@
        self.docroot = docroot
        self.norm_docroot = normcase(normpath(docroot))
        self.index = index
        self.cachebust_match = cachebust_match
    def __call__(self, context, request):
        if self.use_subpath:
            path_tuple = request.subpath
        else:
            path_tuple = traversal_path_info(request.environ['PATH_INFO'])
        if self.cachebust_match:
            path_tuple = self.cachebust_match(path_tuple)
        path = _secure_path(path_tuple)
        if path is None:
@@ -154,3 +157,128 @@
    encoded = slash.join(path_tuple) # will be unicode
    return encoded
def _generate_md5(spec):
    asset = AssetResolver(None).resolve(spec)
    md5 = hashlib.md5()
    with asset.stream() as stream:
        for block in iter(lambda: stream.read(4096), b''):
            md5.update(block)
    return md5.hexdigest()
class Md5AssetTokenGenerator(object):
    """
    A mixin class which provides an implementation of
    :meth:`~pyramid.interfaces.ICacheBuster.target` which generates an md5
    checksum token for an asset, caching it for subsequent calls.
    """
    def __init__(self):
        self.token_cache = {}
    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
        # to the dict shouldn't cause a segfault or other catastrophic failure.
        # (See: http://effbot.org/pyfaq/what-kinds-of-global-value-mutation-are-thread-safe.htm)
        #
        # We do have a race condition that could result in the same md5
        # checksum getting computed twice or more times in parallel.  Since
        # the program would still function just fine if this were to occur,
        # the extra overhead of using locks to serialize access to the dict
        # seems an unnecessary burden.
        #
        token = self.token_cache.get(pathspec)
        if not token:
            self.token_cache[pathspec] = token = _generate_md5(pathspec)
        return token
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
    asset URL.  Generated md5 checksums are cached in order to speed up
    subsequent calls.
    .. versionadded:: 1.6
    """
    def __init__(self):
        super(PathSegmentMd5CacheBuster, self).__init__()
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.
    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
    URL.  Generated md5 checksums are cached in order to speed up subsequent
    calls.
    The optional ``param`` argument determines the name of the parameter added
    to the query string and defaults to ``'x'``.
    .. versionadded:: 1.6
    """
    def __init__(self, param='x'):
        super(QueryStringMd5CacheBuster, self).__init__(param=param)
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.
    The ``token`` parameter is the token string to use for cache busting and
    will be the same for every request.
    The optional ``param`` argument determines the name of the parameter added
    to the query string and defaults to ``'x'``.
    .. versionadded:: 1.6
    """
    def __init__(self, token, param='x'):
        super(QueryStringConstantCacheBuster, self).__init__(param=param)
        self._token = token
    def tokenize(self, pathspec):
        return self._token
pyramid/testing.py
@@ -9,7 +9,6 @@
from pyramid.interfaces import (
    IRequest,
    IResponseFactory,
    ISession,
    )
@@ -22,7 +21,7 @@
from pyramid.config import Configurator
from pyramid.decorator import reify
from pyramid.path import caller_package
from pyramid.response import Response
from pyramid.response import Response, _get_response_factory
from pyramid.registry import Registry
from pyramid.security import (
@@ -41,6 +40,7 @@
from pyramid.request import CallbackMethodsMixin
from pyramid.url import URLMethodsMixin
from pyramid.util import InstancePropertyMixin
_marker = object()
@@ -79,8 +79,8 @@
            effective_principals.extend(self.groupids)
        return effective_principals
    def remember(self, request, principal, **kw):
        self.remembered = principal
    def remember(self, request, userid, **kw):
        self.remembered = userid
        return self.remember_result
    def forget(self, request):
@@ -383,8 +383,8 @@
    @reify
    def response(self):
        f =  self.registry.queryUtility(IResponseFactory, default=Response)
        return f()
        f =  _get_response_factory(self.registry)
        return f(self)
have_zca = True
pyramid/tests/test_authentication.py
@@ -1211,26 +1211,26 @@
        self._assertRaisesBadTicket('secret', ticket, '0.0.0.0')
    def test_correct_with_user_data(self):
        ticket = '66f9cc3e423dc57c91df696cf3d1f0d80000000auserid!a,b!'
        ticket = text_('66f9cc3e423dc57c91df696cf3d1f0d80000000auserid!a,b!')
        result = self._callFUT('secret', ticket, '0.0.0.0')
        self.assertEqual(result, (10, 'userid', ['a', 'b'], ''))
    def test_correct_with_user_data_sha512(self):
        ticket = '7d947cdef99bad55f8e3382a8bd089bb9dd0547f7925b7d189adc1160cab'\
                 '0ec0e6888faa41eba641a18522b26f19109f3ffafb769767ba8a26d02aae'\
                 'ae56599a0000000auserid!a,b!'
        ticket = text_('7d947cdef99bad55f8e3382a8bd089bb9dd0547f7925b7d189adc1'
                       '160cab0ec0e6888faa41eba641a18522b26f19109f3ffafb769767'
                       'ba8a26d02aaeae56599a0000000auserid!a,b!')
        result = self._callFUT('secret', ticket, '0.0.0.0', 'sha512')
        self.assertEqual(result, (10, 'userid', ['a', 'b'], ''))
    def test_ipv4(self):
        ticket = 'b3e7156db4f8abde4439c4a6499a0668f9e7ffd7fa27b798400ecdade8d7'\
                 '6c530000000auserid!'
        ticket = text_('b3e7156db4f8abde4439c4a6499a0668f9e7ffd7fa27b798400ecd'
                       'ade8d76c530000000auserid!')
        result = self._callFUT('secret', ticket, '198.51.100.1', 'sha256')
        self.assertEqual(result, (10, 'userid', [''], ''))
    def test_ipv6(self):
        ticket = 'd025b601a0f12ca6d008aa35ff3a22b7d8f3d1c1456c85becf8760cd7a2f'\
                 'a4910000000auserid!'
        ticket = text_('d025b601a0f12ca6d008aa35ff3a22b7d8f3d1c1456c85becf8760'
                       'cd7a2fa4910000000auserid!')
        result = self._callFUT('secret', ticket, '2001:db8::1', 'sha256')
        self.assertEqual(result, (10, 'userid', [''], ''))
        pass
pyramid/tests/test_compat.py
New file
@@ -0,0 +1,26 @@
import unittest
from pyramid.compat import is_unbound_method
class TestUnboundMethods(unittest.TestCase):
    def test_old_style_bound(self):
        self.assertFalse(is_unbound_method(OldStyle().run))
    def test_new_style_bound(self):
        self.assertFalse(is_unbound_method(NewStyle().run))
    def test_old_style_unbound(self):
        self.assertTrue(is_unbound_method(OldStyle.run))
    def test_new_style_unbound(self):
        self.assertTrue(is_unbound_method(NewStyle.run))
    def test_normal_func_unbound(self):
        def func(): return 'OK'
        self.assertFalse(is_unbound_method(func))
class OldStyle:
    def run(self): return 'OK'
class NewStyle(object):
    def run(self): return 'OK'
pyramid/tests/test_config/pkgs/asset/models.py
File was deleted
pyramid/tests/test_config/pkgs/asset/subpackage/templates/bar.pt
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
@@ -1,5 +1,9 @@
import os.path
import unittest
from pyramid.testing import cleanUp
# we use this folder
here = os.path.dirname(os.path.abspath(__file__))
class TestAssetsConfiguratorMixin(unittest.TestCase):
    def _makeOne(self, *arg, **kw):
@@ -10,27 +14,31 @@
    def test_override_asset_samename(self):
        from pyramid.exceptions import ConfigurationError
        config = self._makeOne()
        self.assertRaises(ConfigurationError, config.override_asset,'a', 'a')
        self.assertRaises(ConfigurationError, config.override_asset, 'a', 'a')
    def test_override_asset_directory_with_file(self):
        from pyramid.exceptions import ConfigurationError
        config = self._makeOne()
        self.assertRaises(ConfigurationError, config.override_asset,
                          'a:foo/', 'a:foo.pt')
                          'a:foo/',
                          'pyramid.tests.test_config.pkgs.asset:foo.pt')
    def test_override_asset_file_with_directory(self):
        from pyramid.exceptions import ConfigurationError
        config = self._makeOne()
        self.assertRaises(ConfigurationError, config.override_asset,
                          'a:foo.pt', 'a:foo/')
                          'a:foo.pt',
                          'pyramid.tests.test_config.pkgs.asset:templates/')
    def test_override_asset_file_with_package(self):
        from pyramid.exceptions import ConfigurationError
        config = self._makeOne()
        self.assertRaises(ConfigurationError, config.override_asset,
                          'a:foo.pt', 'a')
                          'a:foo.pt',
                          'pyramid.tests.test_config.pkgs.asset')
    def test_override_asset_file_with_file(self):
        from pyramid.config.assets import PackageAssetSource
        config = self._makeOne(autocommit=True)
        override = DummyUnderOverride()
        config.override_asset(
@@ -41,10 +49,19 @@
        from pyramid.tests.test_config.pkgs.asset import subpackage
        self.assertEqual(override.package, asset)
        self.assertEqual(override.path, 'templates/foo.pt')
        self.assertEqual(override.override_package, subpackage)
        self.assertEqual(override.override_prefix, 'templates/bar.pt')
        source = override.source
        self.assertTrue(isinstance(source, PackageAssetSource))
        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)
        override = DummyUnderOverride()
        config.override_asset(
@@ -55,10 +72,19 @@
        from pyramid.tests.test_config.pkgs.asset import subpackage
        self.assertEqual(override.package, asset)
        self.assertEqual(override.path, '')
        self.assertEqual(override.override_package, subpackage)
        self.assertEqual(override.override_prefix, '')
        source = override.source
        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
        config = self._makeOne(autocommit=True)
        override = DummyUnderOverride()
        config.override_asset(
@@ -69,10 +95,19 @@
        from pyramid.tests.test_config.pkgs.asset import subpackage
        self.assertEqual(override.package, asset)
        self.assertEqual(override.path, 'templates/')
        self.assertEqual(override.override_package, subpackage)
        self.assertEqual(override.override_prefix, 'templates/')
        source = override.source
        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_package(self):
        from pyramid.config.assets import PackageAssetSource
        config = self._makeOne(autocommit=True)
        override = DummyUnderOverride()
        config.override_asset(
@@ -83,10 +118,19 @@
        from pyramid.tests.test_config.pkgs.asset import subpackage
        self.assertEqual(override.package, asset)
        self.assertEqual(override.path, 'templates/')
        self.assertEqual(override.override_package, subpackage)
        self.assertEqual(override.override_prefix, '')
        source = override.source
        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_package_with_directory(self):
        from pyramid.config.assets import PackageAssetSource
        config = self._makeOne(autocommit=True)
        override = DummyUnderOverride()
        config.override_asset(
@@ -97,32 +141,129 @@
        from pyramid.tests.test_config.pkgs.asset import subpackage
        self.assertEqual(override.package, asset)
        self.assertEqual(override.path, '')
        self.assertEqual(override.override_package, subpackage)
        self.assertEqual(override.override_prefix, 'templates/')
        source = override.source
        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
        config = self._makeOne()
        self.assertRaises(ConfigurationError, config.override_asset,
                          'a:foo/',
                          os.path.join(here, 'pkgs', 'asset', 'foo.pt'))
    def test_override_asset_file_with_absdirectory(self):
        from pyramid.exceptions import ConfigurationError
        config = self._makeOne()
        abspath = os.path.join(here, 'pkgs', 'asset', 'subpackage', 'templates')
        self.assertRaises(ConfigurationError, config.override_asset,
                          'a:foo.pt',
                          abspath)
    def test_override_asset_file_with_missing_abspath(self):
        from pyramid.exceptions import ConfigurationError
        config = self._makeOne()
        self.assertRaises(ConfigurationError, config.override_asset,
                          'a:foo.pt',
                          os.path.join(here, 'wont_exist'))
    def test_override_asset_file_with_absfile(self):
        from pyramid.config.assets import FSAssetSource
        config = self._makeOne(autocommit=True)
        override = DummyUnderOverride()
        abspath = os.path.join(here, 'pkgs', 'asset', 'subpackage',
                               'templates', 'bar.pt')
        config.override_asset(
            'pyramid.tests.test_config.pkgs.asset:templates/foo.pt',
            abspath,
            _override=override)
        from pyramid.tests.test_config.pkgs import asset
        self.assertEqual(override.package, asset)
        self.assertEqual(override.path, 'templates/foo.pt')
        source = override.source
        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)
        override = DummyUnderOverride()
        abspath = os.path.join(here, 'pkgs', 'asset', 'subpackage', 'templates')
        config.override_asset(
            'pyramid.tests.test_config.pkgs.asset:templates/',
            abspath,
            _override=override)
        from pyramid.tests.test_config.pkgs import asset
        self.assertEqual(override.package, asset)
        self.assertEqual(override.path, 'templates/')
        source = override.source
        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)
        override = DummyUnderOverride()
        abspath = os.path.join(here, 'pkgs', 'asset', 'subpackage', 'templates')
        config.override_asset(
            'pyramid.tests.test_config.pkgs.asset',
            abspath,
            _override=override)
        from pyramid.tests.test_config.pkgs import asset
        self.assertEqual(override.package, asset)
        self.assertEqual(override.path, '')
        source = override.source
        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')
        opackage = DummyPackage('opackage')
        source = DummyAssetSource()
        config = self._makeOne()
        config._override(package, 'path', opackage, 'oprefix',
        config._override(package, 'path', source,
                         PackageOverrides=DummyPackageOverrides)
        overrides = config.registry.queryUtility(IPackageOverrides,
                                                 name='package')
        self.assertEqual(overrides.inserted, [('path', 'opackage', 'oprefix')])
        self.assertEqual(overrides.inserted, [('path', source)])
        self.assertEqual(overrides.package, package)
    def test__override_already_registered(self):
        from pyramid.interfaces import IPackageOverrides
        package = DummyPackage('package')
        opackage = DummyPackage('opackage')
        source = DummyAssetSource()
        overrides = DummyPackageOverrides(package)
        config = self._makeOne()
        config.registry.registerUtility(overrides, IPackageOverrides,
                                        name='package')
        config._override(package, 'path', opackage, 'oprefix',
        config._override(package, 'path', source,
                         PackageOverrides=DummyPackageOverrides)
        self.assertEqual(overrides.inserted, [('path', 'opackage', 'oprefix')])
        self.assertEqual(overrides.inserted, [('path', source)])
        self.assertEqual(overrides.package, package)
@@ -148,30 +289,24 @@
        reg.registerUtility(overrides, IPackageOverrides, name=name)
    def test_get_resource_filename_no_overrides(self):
        import os
        resource_name = 'test_assets.py'
        import pyramid.tests.test_config
        provider = self._makeOne(pyramid.tests.test_config)
        here = os.path.dirname(os.path.abspath(__file__))
        expected = os.path.join(here, resource_name)
        result = provider.get_resource_filename(None, resource_name)
        self.assertEqual(result, expected)
    def test_get_resource_stream_no_overrides(self):
        import os
        resource_name = 'test_assets.py'
        import pyramid.tests.test_config
        provider = self._makeOne(pyramid.tests.test_config)
        here = os.path.dirname(os.path.abspath(__file__))
        with provider.get_resource_stream(None, resource_name) as result:
            _assertBody(result.read(), os.path.join(here, resource_name))
    def test_get_resource_string_no_overrides(self):
        import os
        resource_name = 'test_assets.py'
        import pyramid.tests.test_config
        provider = self._makeOne(pyramid.tests.test_config)
        here = os.path.dirname(os.path.abspath(__file__))
        result = provider.get_resource_string(None, resource_name)
        _assertBody(result, os.path.join(here, resource_name))
@@ -202,11 +337,9 @@
    def test_get_resource_filename_override_returns_None(self):
        overrides = DummyOverrides(None)
        self._registerOverrides(overrides)
        import os
        resource_name = 'test_assets.py'
        import pyramid.tests.test_config
        provider = self._makeOne(pyramid.tests.test_config)
        here = os.path.dirname(os.path.abspath(__file__))
        expected = os.path.join(here, resource_name)
        result = provider.get_resource_filename(None, resource_name)
        self.assertEqual(result, expected)
@@ -214,22 +347,18 @@
    def test_get_resource_stream_override_returns_None(self):
        overrides = DummyOverrides(None)
        self._registerOverrides(overrides)
        import os
        resource_name = 'test_assets.py'
        import pyramid.tests.test_config
        provider = self._makeOne(pyramid.tests.test_config)
        here = os.path.dirname(os.path.abspath(__file__))
        with provider.get_resource_stream(None, resource_name) as result:
            _assertBody(result.read(), os.path.join(here, resource_name))
    def test_get_resource_string_override_returns_None(self):
        overrides = DummyOverrides(None)
        self._registerOverrides(overrides)
        import os
        resource_name = 'test_assets.py'
        import pyramid.tests.test_config
        provider = self._makeOne(pyramid.tests.test_config)
        here = os.path.dirname(os.path.abspath(__file__))
        result = provider.get_resource_string(None, resource_name)
        _assertBody(result, os.path.join(here, resource_name))
@@ -378,8 +507,8 @@
        from pyramid.config.assets import DirectoryOverride
        package = DummyPackage('package')
        po = self._makeOne(package)
        po.overrides= [None]
        po.insert('foo/', 'package', 'bar/')
        po.overrides = [None]
        po.insert('foo/', DummyAssetSource())
        self.assertEqual(len(po.overrides), 2)
        override = po.overrides[0]
        self.assertEqual(override.__class__, DirectoryOverride)
@@ -388,8 +517,8 @@
        from pyramid.config.assets import FileOverride
        package = DummyPackage('package')
        po = self._makeOne(package)
        po.overrides= [None]
        po.insert('foo.pt', 'package', 'bar.pt')
        po.overrides = [None]
        po.insert('foo.pt', DummyAssetSource())
        self.assertEqual(len(po.overrides), 2)
        override = po.overrides[0]
        self.assertEqual(override.__class__, FileOverride)
@@ -399,132 +528,137 @@
        from pyramid.config.assets import DirectoryOverride
        package = DummyPackage('package')
        po = self._makeOne(package)
        po.overrides= [None]
        po.insert('', 'package', 'bar/')
        po.overrides = [None]
        source = DummyAssetSource()
        po.insert('', source)
        self.assertEqual(len(po.overrides), 2)
        override = po.overrides[0]
        self.assertEqual(override.__class__, DirectoryOverride)
    def test_search_path(self):
        overrides = [ DummyOverride(None), DummyOverride(('package', 'name'))]
    def test_filtered_sources(self):
        overrides = [ DummyOverride(None), DummyOverride('foo')]
        package = DummyPackage('package')
        po = self._makeOne(package)
        po.overrides= overrides
        self.assertEqual(list(po.search_path('whatever')),
                         [('package', 'name')])
        po.overrides = overrides
        self.assertEqual(list(po.filtered_sources('whatever')), ['foo'])
    def test_get_filename(self):
        import os
        overrides = [ DummyOverride(None), DummyOverride(
            ('pyramid.tests.test_config', 'test_assets.py'))]
        source = DummyAssetSource(filename='foo.pt')
        overrides = [ DummyOverride(None), DummyOverride((source, ''))]
        package = DummyPackage('package')
        po = self._makeOne(package)
        po.overrides= overrides
        here = os.path.dirname(os.path.abspath(__file__))
        expected = os.path.join(here, 'test_assets.py')
        self.assertEqual(po.get_filename('whatever'), expected)
        po.overrides = overrides
        result = po.get_filename('whatever')
        self.assertEqual(result, 'foo.pt')
        self.assertEqual(source.resource_name, '')
    def test_get_filename_file_doesnt_exist(self):
        overrides = [ DummyOverride(None), DummyOverride(
            ('pyramid.tests.test_config', 'wont_exist'))]
        source = DummyAssetSource(filename=None)
        overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))]
        package = DummyPackage('package')
        po = self._makeOne(package)
        po.overrides= overrides
        po.overrides = overrides
        self.assertEqual(po.get_filename('whatever'), None)
        self.assertEqual(source.resource_name, 'wont_exist')
    def test_get_stream(self):
        import os
        overrides = [ DummyOverride(None), DummyOverride(
            ('pyramid.tests.test_config', 'test_assets.py'))]
        source = DummyAssetSource(stream='a stream?')
        overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))]
        package = DummyPackage('package')
        po = self._makeOne(package)
        po.overrides= overrides
        here = os.path.dirname(os.path.abspath(__file__))
        with po.get_stream('whatever') as stream:
            _assertBody(stream.read(), os.path.join(here, 'test_assets.py'))
        po.overrides = overrides
        self.assertEqual(po.get_stream('whatever'), 'a stream?')
        self.assertEqual(source.resource_name, 'foo.pt')
        
    def test_get_stream_file_doesnt_exist(self):
        overrides = [ DummyOverride(None), DummyOverride(
            ('pyramid.tests.test_config', 'wont_exist'))]
        source = DummyAssetSource(stream=None)
        overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))]
        package = DummyPackage('package')
        po = self._makeOne(package)
        po.overrides= overrides
        po.overrides = overrides
        self.assertEqual(po.get_stream('whatever'), None)
        self.assertEqual(source.resource_name, 'wont_exist')
    def test_get_string(self):
        import os
        overrides = [ DummyOverride(None), DummyOverride(
            ('pyramid.tests.test_config', 'test_assets.py'))]
        source = DummyAssetSource(string='a string')
        overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))]
        package = DummyPackage('package')
        po = self._makeOne(package)
        po.overrides= overrides
        here = os.path.dirname(os.path.abspath(__file__))
        _assertBody(po.get_string('whatever'),
                    os.path.join(here, 'test_assets.py'))
        po.overrides = overrides
        self.assertEqual(po.get_string('whatever'), 'a string')
        self.assertEqual(source.resource_name, 'foo.pt')
        
    def test_get_string_file_doesnt_exist(self):
        overrides = [ DummyOverride(None), DummyOverride(
            ('pyramid.tests.test_config', 'wont_exist'))]
        source = DummyAssetSource(string=None)
        overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))]
        package = DummyPackage('package')
        po = self._makeOne(package)
        po.overrides= overrides
        po.overrides = overrides
        self.assertEqual(po.get_string('whatever'), None)
        self.assertEqual(source.resource_name, 'wont_exist')
    def test_has_resource(self):
        overrides = [ DummyOverride(None), DummyOverride(
            ('pyramid.tests.test_config', 'test_assets.py'))]
        source = DummyAssetSource(exists=True)
        overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))]
        package = DummyPackage('package')
        po = self._makeOne(package)
        po.overrides= overrides
        po.overrides = overrides
        self.assertEqual(po.has_resource('whatever'), True)
        self.assertEqual(source.resource_name, 'foo.pt')
    def test_has_resource_file_doesnt_exist(self):
        overrides = [ DummyOverride(None), DummyOverride(
            ('pyramid.tests.test_config', 'wont_exist'))]
        source = DummyAssetSource(exists=None)
        overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))]
        package = DummyPackage('package')
        po = self._makeOne(package)
        po.overrides= overrides
        po.overrides = overrides
        self.assertEqual(po.has_resource('whatever'), None)
        self.assertEqual(source.resource_name, 'wont_exist')
    def test_isdir_false(self):
        overrides = [ DummyOverride(
            ('pyramid.tests.test_config', 'test_assets.py'))]
        source = DummyAssetSource(isdir=False)
        overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))]
        package = DummyPackage('package')
        po = self._makeOne(package)
        po.overrides= overrides
        po.overrides = overrides
        self.assertEqual(po.isdir('whatever'), False)
        self.assertEqual(source.resource_name, 'foo.pt')
    def test_isdir_true(self):
        overrides = [ DummyOverride(
            ('pyramid.tests.test_config', 'files'))]
        source = DummyAssetSource(isdir=True)
        overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))]
        package = DummyPackage('package')
        po = self._makeOne(package)
        po.overrides= overrides
        po.overrides = overrides
        self.assertEqual(po.isdir('whatever'), True)
        self.assertEqual(source.resource_name, 'foo.pt')
    def test_isdir_doesnt_exist(self):
        overrides = [ DummyOverride(None), DummyOverride(
            ('pyramid.tests.test_config', 'wont_exist'))]
        source = DummyAssetSource(isdir=None)
        overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))]
        package = DummyPackage('package')
        po = self._makeOne(package)
        po.overrides= overrides
        po.overrides = overrides
        self.assertEqual(po.isdir('whatever'), None)
        self.assertEqual(source.resource_name, 'wont_exist')
    def test_listdir(self):
        overrides = [ DummyOverride(
            ('pyramid.tests.test_config', 'files'))]
        source = DummyAssetSource(listdir=True)
        overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))]
        package = DummyPackage('package')
        po = self._makeOne(package)
        po.overrides= overrides
        self.assertTrue(po.listdir('whatever'))
        po.overrides = overrides
        self.assertEqual(po.listdir('whatever'), True)
        self.assertEqual(source.resource_name, 'foo.pt')
    def test_listdir_doesnt_exist(self):
        overrides = [ DummyOverride(None), DummyOverride(
            ('pyramid.tests.test_config', 'wont_exist'))]
        source = DummyAssetSource(listdir=None)
        overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))]
        package = DummyPackage('package')
        po = self._makeOne(package)
        po.overrides= overrides
        po.overrides = overrides
        self.assertEqual(po.listdir('whatever'), None)
        self.assertEqual(source.resource_name, 'wont_exist')
    # PEP 302 __loader__ extensions:  use the "real" __loader__, if present.
    def test_get_data_pkg_has_no___loader__(self):
@@ -570,27 +704,124 @@
    def test_get_source_pkg_has___loader__(self):
        package = DummyPackage('package')
        loader = package.__loader__  = DummyLoader()
        loader = package.__loader__ = DummyLoader()
        po = self._makeOne(package)
        self.assertEqual(po.get_source('whatever'), 'def foo():\n    pass')
        self.assertEqual(loader._got_source, 'whatever')
class AssetSourceIntegrationTests(object):
    def test_get_filename(self):
        source = self._makeOne('')
        self.assertEqual(source.get_filename('test_assets.py'),
                         os.path.join(here, 'test_assets.py'))
    def test_get_filename_with_prefix(self):
        source = self._makeOne('test_assets.py')
        self.assertEqual(source.get_filename(''),
                         os.path.join(here, 'test_assets.py'))
    def test_get_filename_file_doesnt_exist(self):
        source = self._makeOne('')
        self.assertEqual(source.get_filename('wont_exist'), None)
    def test_get_stream(self):
        source = self._makeOne('')
        with source.get_stream('test_assets.py') as stream:
            _assertBody(stream.read(), os.path.join(here, 'test_assets.py'))
    def test_get_stream_with_prefix(self):
        source = self._makeOne('test_assets.py')
        with source.get_stream('') as stream:
            _assertBody(stream.read(), os.path.join(here, 'test_assets.py'))
    def test_get_stream_file_doesnt_exist(self):
        source = self._makeOne('')
        self.assertEqual(source.get_stream('wont_exist'), None)
    def test_get_string(self):
        source = self._makeOne('')
        _assertBody(source.get_string('test_assets.py'),
                    os.path.join(here, 'test_assets.py'))
    def test_get_string_with_prefix(self):
        source = self._makeOne('test_assets.py')
        _assertBody(source.get_string(''),
                    os.path.join(here, 'test_assets.py'))
    def test_get_string_file_doesnt_exist(self):
        source = self._makeOne('')
        self.assertEqual(source.get_string('wont_exist'), None)
    def test_exists(self):
        source = self._makeOne('')
        self.assertEqual(source.exists('test_assets.py'), True)
    def test_exists_with_prefix(self):
        source = self._makeOne('test_assets.py')
        self.assertEqual(source.exists(''), True)
    def test_exists_file_doesnt_exist(self):
        source = self._makeOne('')
        self.assertEqual(source.exists('wont_exist'), None)
    def test_isdir_false(self):
        source = self._makeOne('')
        self.assertEqual(source.isdir('test_assets.py'), False)
    def test_isdir_true(self):
        source = self._makeOne('')
        self.assertEqual(source.isdir('files'), True)
    def test_isdir_doesnt_exist(self):
        source = self._makeOne('')
        self.assertEqual(source.isdir('wont_exist'), None)
    def test_listdir(self):
        source = self._makeOne('')
        self.assertTrue(source.listdir('files'))
    def test_listdir_doesnt_exist(self):
        source = self._makeOne('')
        self.assertEqual(source.listdir('wont_exist'), None)
class TestPackageAssetSource(AssetSourceIntegrationTests, unittest.TestCase):
    def _getTargetClass(self):
        from pyramid.config.assets import PackageAssetSource
        return PackageAssetSource
    def _makeOne(self, prefix, package='pyramid.tests.test_config'):
        klass = self._getTargetClass()
        return klass(package, prefix)
class TestFSAssetSource(AssetSourceIntegrationTests, unittest.TestCase):
    def _getTargetClass(self):
        from pyramid.config.assets import FSAssetSource
        return FSAssetSource
    def _makeOne(self, prefix, base_prefix=here):
        klass = self._getTargetClass()
        return klass(os.path.join(base_prefix, prefix))
class TestDirectoryOverride(unittest.TestCase):
    def _getTargetClass(self):
        from pyramid.config.assets import DirectoryOverride
        return DirectoryOverride
    def _makeOne(self, path, package, prefix):
    def _makeOne(self, path, source):
        klass = self._getTargetClass()
        return klass(path, package, prefix)
        return klass(path, source)
    def test_it_match(self):
        o = self._makeOne('foo/', 'package', 'bar/')
        source = DummyAssetSource()
        o = self._makeOne('foo/', source)
        result = o('foo/something.pt')
        self.assertEqual(result, ('package', 'bar/something.pt'))
        self.assertEqual(result, (source, 'something.pt'))
        
    def test_it_no_match(self):
        o = self._makeOne('foo/', 'package', 'bar/')
        source = DummyAssetSource()
        o = self._makeOne('foo/', source)
        result = o('baz/notfound.pt')
        self.assertEqual(result, None)
@@ -599,17 +830,19 @@
        from pyramid.config.assets import FileOverride
        return FileOverride
    def _makeOne(self, path, package, prefix):
    def _makeOne(self, path, source):
        klass = self._getTargetClass()
        return klass(path, package, prefix)
        return klass(path, source)
    def test_it_match(self):
        o = self._makeOne('foo.pt', 'package', 'bar.pt')
        source = DummyAssetSource()
        o = self._makeOne('foo.pt', source)
        result = o('foo.pt')
        self.assertEqual(result, ('package', 'bar.pt'))
        self.assertEqual(result, (source, ''))
        
    def test_it_no_match(self):
        o = self._makeOne('foo.pt', 'package', 'bar.pt')
        source = DummyAssetSource()
        o = self._makeOne('foo.pt', source)
        result = o('notfound.pt')
        self.assertEqual(result, None)
@@ -634,8 +867,8 @@
        self.package = package
        self.inserted = []
    def insert(self, path, package, prefix):
        self.inserted.append((path, package, prefix))
    def insert(self, path, source):
        self.inserted.append((path, source))
    
class DummyPkgResources:
    def __init__(self):
@@ -647,6 +880,34 @@
class DummyPackage:
    def __init__(self, name):
        self.__name__ = name
class DummyAssetSource:
    def __init__(self, **kw):
        self.kw = kw
    def get_filename(self, resource_name):
        self.resource_name = resource_name
        return self.kw['filename']
    def get_stream(self, resource_name):
        self.resource_name = resource_name
        return self.kw['stream']
    def get_string(self, resource_name):
        self.resource_name = resource_name
        return self.kw['string']
    def exists(self, resource_name):
        self.resource_name = resource_name
        return self.kw['exists']
    def isdir(self, resource_name):
        self.resource_name = resource_name
        return self.kw['isdir']
    def listdir(self, resource_name):
        self.resource_name = resource_name
        return self.kw['listdir']
 
class DummyLoader:
    _got_data = _is_package = None
@@ -664,12 +925,10 @@
        return 'def foo():\n    pass'
class DummyUnderOverride:
    def __call__(self, package, path, override_package, override_prefix,
                 _info=''):
    def __call__(self, package, path, source, _info=''):
        self.package = package
        self.path = path
        self.override_package = override_package
        self.override_prefix = override_prefix
        self.source = source
def read_(src):
    with open(src, 'rb') as f:
pyramid/tests/test_config/test_factories.py
@@ -23,6 +23,21 @@
        self.assertEqual(config.registry.getUtility(IRequestFactory),
                         dummyfactory)
    def test_set_response_factory(self):
        from pyramid.interfaces import IResponseFactory
        config = self._makeOne(autocommit=True)
        factory = lambda r: object()
        config.set_response_factory(factory)
        self.assertEqual(config.registry.getUtility(IResponseFactory), factory)
    def test_set_response_factory_dottedname(self):
        from pyramid.interfaces import IResponseFactory
        config = self._makeOne(autocommit=True)
        config.set_response_factory(
            'pyramid.tests.test_config.dummyfactory')
        self.assertEqual(config.registry.getUtility(IResponseFactory),
                         dummyfactory)
    def test_set_root_factory(self):
        from pyramid.interfaces import IRootFactory
        config = self._makeOne()
@@ -111,6 +126,23 @@
        config = self._makeOne(autocommit=True)
        self.assertRaises(AttributeError, config.add_request_method)
    def test_add_request_method_with_text_type_name(self):
        from pyramid.interfaces import IRequestExtensions
        from pyramid.compat import text_, PY3
        from pyramid.exceptions import ConfigurationError
        config = self._makeOne(autocommit=True)
        def boomshaka(r): pass
        def get_bad_name():
            if PY3:  # pragma: nocover
                name = b'La Pe\xc3\xb1a'
            else:  # pragma: nocover
                name = text_(b'La Pe\xc3\xb1a', 'utf-8')
            config.add_request_method(boomshaka, name=name)
        self.assertRaises(ConfigurationError, get_bad_name)
class TestDeprecatedFactoriesMixinMethods(unittest.TestCase):
    def setUp(self):
@@ -120,7 +152,7 @@
    def tearDown(self):
        from zope.deprecation import __show__
        __show__.on()
    def _makeOne(self, *arg, **kw):
        from pyramid.config import Configurator
        config = Configurator(*arg, **kw)
pyramid/tests/test_config/test_init.py
@@ -546,6 +546,18 @@
        utility = reg.getUtility(IRequestFactory)
        self.assertEqual(utility, factory)
    def test_setup_registry_response_factory(self):
        from pyramid.registry import Registry
        from pyramid.interfaces import IResponseFactory
        reg = Registry()
        config = self._makeOne(reg)
        factory = lambda r: object()
        config.setup_registry(response_factory=factory)
        self.assertEqual(reg.queryUtility(IResponseFactory), None)
        config.commit()
        utility = reg.getUtility(IResponseFactory)
        self.assertEqual(utility, factory)
    def test_setup_registry_request_factory_dottedname(self):
        from pyramid.registry import Registry
        from pyramid.interfaces import IRequestFactory
@@ -735,6 +747,18 @@
                "file must exist, refusing to use orphan .pyc or .pyo file).")
        else: # pragma: no cover
            raise AssertionError
    def test_include_constant_root_package(self):
        from pyramid import tests
        from pyramid.tests import test_config
        config = self._makeOne(root_package=tests)
        results = {}
        def include(config):
            results['package'] = config.package
            results['root_package'] = config.root_package
        config.include(include)
        self.assertEqual(results['root_package'], tests)
        self.assertEqual(results['package'], test_config)
    def test_action_branching_kw_is_None(self):
        config = self._makeOne(autocommit=True)
@@ -1491,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_settings.py
@@ -57,7 +57,7 @@
        self.assertEqual(settings['a'], 1)
class TestSettings(unittest.TestCase):
    def _getTargetClass(self):
        from pyramid.config.settings import Settings
        return Settings
@@ -131,6 +131,35 @@
        self.assertEqual(result['prevent_http_cache'], True)
        self.assertEqual(result['pyramid.prevent_http_cache'], True)
    def test_prevent_cachebust(self):
        settings = self._makeOne({})
        self.assertEqual(settings['prevent_cachebust'], False)
        self.assertEqual(settings['pyramid.prevent_cachebust'], False)
        result = self._makeOne({'prevent_cachebust':'false'})
        self.assertEqual(result['prevent_cachebust'], False)
        self.assertEqual(result['pyramid.prevent_cachebust'], False)
        result = self._makeOne({'prevent_cachebust':'t'})
        self.assertEqual(result['prevent_cachebust'], True)
        self.assertEqual(result['pyramid.prevent_cachebust'], True)
        result = self._makeOne({'prevent_cachebust':'1'})
        self.assertEqual(result['prevent_cachebust'], True)
        self.assertEqual(result['pyramid.prevent_cachebust'], True)
        result = self._makeOne({'pyramid.prevent_cachebust':'t'})
        self.assertEqual(result['prevent_cachebust'], True)
        self.assertEqual(result['pyramid.prevent_cachebust'], True)
        result = self._makeOne({}, {'PYRAMID_PREVENT_CACHEBUST':'1'})
        self.assertEqual(result['prevent_cachebust'], True)
        self.assertEqual(result['pyramid.prevent_cachebust'], True)
        result = self._makeOne({'prevent_cachebust':'false',
                                'pyramid.prevent_cachebust':'1'})
        self.assertEqual(result['prevent_cachebust'], True)
        self.assertEqual(result['pyramid.prevent_cachebust'], True)
        result = self._makeOne({'prevent_cachebust':'false',
                                'pyramid.prevent_cachebust':'f'},
                               {'PYRAMID_PREVENT_CACHEBUST':'1'})
        self.assertEqual(result['prevent_cachebust'], True)
        self.assertEqual(result['pyramid.prevent_cachebust'], True)
    def test_reload_templates(self):
        settings = self._makeOne({})
        self.assertEqual(settings['reload_templates'], False)
pyramid/tests/test_config/test_util.py
@@ -568,6 +568,13 @@
        foo = Foo()
        self.assertTrue(self._callFUT(foo.method))
    def test_function_annotations(self):
        def foo(bar):
            """ """
        # avoid SyntaxErrors in python2, this if effectively nop
        getattr(foo, '__annotations__', {}).update({'bar': 'baz'})
        self.assertTrue(self._callFUT(foo))
class TestNotted(unittest.TestCase):
    def _makeOne(self, predicate):
        from pyramid.config.util import Notted
pyramid/tests/test_config/test_views.py
@@ -113,7 +113,7 @@
        config.add_view(renderer='dummy.pt')
        view = self._getViewCallable(config)
        self.assertRaises(ValueError, view, None, None)
    def test_add_view_with_tmpl_renderer_factory_no_renderer_factory(self):
        config = self._makeOne(autocommit=True)
        introspector = DummyIntrospector()
@@ -136,7 +136,7 @@
            ('renderer factories', '.pt') in introspector.related[-1])
        view = self._getViewCallable(config)
        self.assertTrue(b'Hello!' in view(None, None).body)
    def test_add_view_wrapped_view_is_decorated(self):
        def view(request): # request-only wrapper
            """ """
@@ -1666,6 +1666,20 @@
            renderer=null_renderer)
        self.assertRaises(ConfigurationConflictError, config.commit)
    def test_add_view_class_method_no_attr(self):
        from pyramid.renderers import null_renderer
        from zope.interface import directlyProvides
        from pyramid.exceptions import ConfigurationError
        config = self._makeOne(autocommit=True)
        class DummyViewClass(object):
            def run(self): pass
        def configure_view():
            config.add_view(view=DummyViewClass.run, renderer=null_renderer)
        self.assertRaises(ConfigurationError, configure_view)
    def test_derive_view_function(self):
        from pyramid.renderers import null_renderer
        def view(request):
@@ -1783,6 +1797,21 @@
        result = view(None, request)
        self.assertEqual(result, 'OK')
    def test_add_forbidden_view_no_view_argument(self):
        from zope.interface import implementedBy
        from pyramid.interfaces import IRequest
        from pyramid.httpexceptions import HTTPForbidden
        config = self._makeOne(autocommit=True)
        config.setup_registry()
        config.add_forbidden_view()
        request = self._makeRequest(config)
        view = self._getViewCallable(config,
                                     ctx_iface=implementedBy(HTTPForbidden),
                                     request_iface=IRequest)
        context = HTTPForbidden()
        result = view(context, request)
        self.assertEqual(result, context)
    def test_add_forbidden_view_allows_other_predicates(self):
        from pyramid.renderers import null_renderer
        config = self._makeOne(autocommit=True)
@@ -1860,6 +1889,21 @@
        result = view(None, request)
        self.assertEqual(result, (None, request))
    def test_add_notfound_view_no_view_argument(self):
        from zope.interface import implementedBy
        from pyramid.interfaces import IRequest
        from pyramid.httpexceptions import HTTPNotFound
        config = self._makeOne(autocommit=True)
        config.setup_registry()
        config.add_notfound_view()
        request = self._makeRequest(config)
        view = self._getViewCallable(config,
                                     ctx_iface=implementedBy(HTTPNotFound),
                                     request_iface=IRequest)
        context = HTTPNotFound()
        result = view(context, request)
        self.assertEqual(result, context)
    def test_add_notfound_view_allows_other_predicates(self):
        from pyramid.renderers import null_renderer
        config = self._makeOne(autocommit=True)
@@ -1897,7 +1941,7 @@
        from pyramid.renderers import null_renderer
        from zope.interface import implementedBy
        from pyramid.interfaces import IRequest
        from pyramid.httpexceptions import HTTPNotFound
        from pyramid.httpexceptions import HTTPFound, HTTPNotFound
        config = self._makeOne(autocommit=True)
        config.add_route('foo', '/foo/')
        def view(request): return Response('OK')
@@ -1910,6 +1954,30 @@
                                     ctx_iface=implementedBy(HTTPNotFound),
                                     request_iface=IRequest)
        result = view(None, request)
        self.assertTrue(isinstance(result, HTTPFound))
        self.assertEqual(result.location, '/scriptname/foo/?a=1&b=2')
    def test_add_notfound_view_append_slash_custom_response(self):
        from pyramid.response import Response
        from pyramid.renderers import null_renderer
        from zope.interface import implementedBy
        from pyramid.interfaces import IRequest
        from pyramid.httpexceptions import HTTPMovedPermanently, HTTPNotFound
        config = self._makeOne(autocommit=True)
        config.add_route('foo', '/foo/')
        def view(request): return Response('OK')
        config.add_notfound_view(
            view, renderer=null_renderer,append_slash=HTTPMovedPermanently
        )
        request = self._makeRequest(config)
        request.environ['PATH_INFO'] = '/foo'
        request.query_string = 'a=1&b=2'
        request.path = '/scriptname/foo'
        view = self._getViewCallable(config,
                                     ctx_iface=implementedBy(HTTPNotFound),
                                     request_iface=IRequest)
        result = view(None, request)
        self.assertTrue(isinstance(result, HTTPMovedPermanently))
        self.assertEqual(result.location, '/scriptname/foo/?a=1&b=2')
    def test_add_notfound_view_with_view_defaults(self):
@@ -2504,6 +2572,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())
@@ -2541,6 +2611,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())
@@ -3135,6 +3207,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
@@ -3159,6 +3233,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
@@ -3183,6 +3259,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
@@ -3207,6 +3285,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
@@ -3231,6 +3311,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'}
@@ -3253,6 +3335,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'}
@@ -3742,8 +3826,9 @@
    def test_generate_registration_miss(self):
        inst = self._makeOne()
        registrations = [(None, 'spec', 'route_name'),
                         ('http://example.com/foo/', 'package:path/', None)]
        registrations = [
            (None, 'spec', 'route_name', None),
            ('http://example.com/foo/', 'package:path/', None, None)]
        inst._get_registrations = lambda *x: registrations
        request = self._makeRequest()
        result = inst.generate('package:path/abc', request)
@@ -3751,7 +3836,8 @@
    def test_generate_registration_no_registry_on_request(self):
        inst = self._makeOne()
        registrations = [('http://example.com/foo/', 'package:path/', None)]
        registrations = [
            ('http://example.com/foo/', 'package:path/', None, None)]
        inst._get_registrations = lambda *x: registrations
        request = self._makeRequest()
        del request.registry
@@ -3760,7 +3846,8 @@
    def test_generate_slash_in_name1(self):
        inst = self._makeOne()
        registrations = [('http://example.com/foo/', 'package:path/', None)]
        registrations = [
            ('http://example.com/foo/', 'package:path/', None, None)]
        inst._get_registrations = lambda *x: registrations
        request = self._makeRequest()
        result = inst.generate('package:path/abc', request)
@@ -3768,7 +3855,8 @@
    def test_generate_slash_in_name2(self):
        inst = self._makeOne()
        registrations = [('http://example.com/foo/', 'package:path/', None)]
        registrations = [
            ('http://example.com/foo/', 'package:path/', None, None)]
        inst._get_registrations = lambda *x: registrations
        request = self._makeRequest()
        result = inst.generate('package:path/', request)
@@ -3788,7 +3876,7 @@
    def test_generate_route_url(self):
        inst = self._makeOne()
        registrations = [(None, 'package:path/', '__viewname/')]
        registrations = [(None, 'package:path/', '__viewname/', None)]
        inst._get_registrations = lambda *x: registrations
        def route_url(n, **kw):
            self.assertEqual(n, '__viewname/')
@@ -3801,7 +3889,7 @@
    def test_generate_url_unquoted_local(self):
        inst = self._makeOne()
        registrations = [(None, 'package:path/', '__viewname/')]
        registrations = [(None, 'package:path/', '__viewname/', None)]
        inst._get_registrations = lambda *x: registrations
        def route_url(n, **kw):
            self.assertEqual(n, '__viewname/')
@@ -3814,7 +3902,7 @@
    def test_generate_url_quoted_remote(self):
        inst = self._makeOne()
        registrations = [('http://example.com/', 'package:path/', None)]
        registrations = [('http://example.com/', 'package:path/', None, None)]
        inst._get_registrations = lambda *x: registrations
        request = self._makeRequest()
        result = inst.generate('package:path/abc def', request, a=1)
@@ -3822,7 +3910,7 @@
    def test_generate_url_with_custom_query(self):
        inst = self._makeOne()
        registrations = [('http://example.com/', 'package:path/', None)]
        registrations = [('http://example.com/', 'package:path/', None, None)]
        inst._get_registrations = lambda *x: registrations
        request = self._makeRequest()
        result = inst.generate('package:path/abc def', request, a=1,
@@ -3832,7 +3920,7 @@
    def test_generate_url_with_custom_anchor(self):
        inst = self._makeOne()
        registrations = [('http://example.com/', 'package:path/', None)]
        registrations = [('http://example.com/', 'package:path/', None, None)]
        inst._get_registrations = lambda *x: registrations
        request = self._makeRequest()
        uc = text_(b'La Pe\xc3\xb1a', 'utf-8')
@@ -3841,33 +3929,57 @@
        self.assertEqual(result,
                         'http://example.com/abc%20def#La%20Pe%C3%B1a')
    def test_generate_url_cachebust(self):
        def cachebust(subpath, kw):
            kw['foo'] = 'bar'
            return 'foo' + '/' + subpath, kw
        inst = self._makeOne()
        registrations = [(None, 'package:path/', '__viewname', cachebust)]
        inst._get_registrations = lambda *x: registrations
        request = self._makeRequest()
        def route_url(n, **kw):
            self.assertEqual(n, '__viewname')
            self.assertEqual(kw, {'subpath':'foo/abc', 'foo':'bar'})
        request.route_url = route_url
        inst.generate('package:path/abc', request)
    def test_add_already_exists(self):
        inst = self._makeOne()
        config = self._makeConfig(
            [('http://example.com/', 'package:path/', None)])
        inst.add(config, 'http://example.com', 'anotherpackage:path')
        expected = [('http://example.com/',  'anotherpackage:path/', None)]
        expected = [
            ('http://example.com/',  'anotherpackage:path/', None, None)]
        self._assertRegistrations(config, expected)
    def test_add_package_root(self):
        inst = self._makeOne()
        config = self._makeConfig()
        inst.add(config, 'http://example.com', 'package:')
        expected = [('http://example.com/',  'package:', None, None)]
        self._assertRegistrations(config, expected)
    def test_add_url_withendslash(self):
        inst = self._makeOne()
        config = self._makeConfig()
        inst.add(config, 'http://example.com/', 'anotherpackage:path')
        expected = [('http://example.com/', 'anotherpackage:path/', None)]
        expected = [
            ('http://example.com/', 'anotherpackage:path/', None, None)]
        self._assertRegistrations(config, expected)
    def test_add_url_noendslash(self):
        inst = self._makeOne()
        config = self._makeConfig()
        inst.add(config, 'http://example.com', 'anotherpackage:path')
        expected = [('http://example.com/', 'anotherpackage:path/', None)]
        expected = [
            ('http://example.com/', 'anotherpackage:path/', None, None)]
        self._assertRegistrations(config, expected)
    def test_add_url_noscheme(self):
        inst = self._makeOne()
        config = self._makeConfig()
        inst.add(config, '//example.com', 'anotherpackage:path')
        expected = [('//example.com/', 'anotherpackage:path/', None)]
        expected = [('//example.com/', 'anotherpackage:path/', None, None)]
        self._assertRegistrations(config, expected)
    def test_add_viewname(self):
@@ -3876,7 +3988,7 @@
        config = self._makeConfig()
        inst = self._makeOne()
        inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1)
        expected = [(None, 'anotherpackage:path/', '__view/')]
        expected = [(None, 'anotherpackage:path/', '__view/', None)]
        self._assertRegistrations(config, expected)
        self.assertEqual(config.route_args, ('__view/', 'view/*subpath'))
        self.assertEqual(config.view_kw['permission'], NO_PERMISSION_REQUIRED)
@@ -3887,7 +3999,7 @@
        config.route_prefix = '/abc'
        inst = self._makeOne()
        inst.add(config, 'view', 'anotherpackage:path',)
        expected = [(None, 'anotherpackage:path/', '__/abc/view/')]
        expected = [(None, 'anotherpackage:path/', '__/abc/view/', None)]
        self._assertRegistrations(config, expected)
        self.assertEqual(config.route_args, ('__/abc/view/', 'view/*subpath'))
@@ -3904,7 +4016,7 @@
        inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1,
                 context=DummyContext)
        self.assertEqual(config.view_kw['context'], DummyContext)
    def test_add_viewname_with_for_(self):
        config = self._makeConfig()
        inst = self._makeOne()
@@ -3919,6 +4031,34 @@
                 renderer='mypackage:templates/index.pt')
        self.assertEqual(config.view_kw['renderer'],
                         'mypackage:templates/index.pt')
    def test_add_cachebust_default(self):
        config = self._makeConfig()
        inst = self._makeOne()
        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', {})
        self.assertEqual(subpath, 'some/path')
        self.assertEqual(kw['x'], 'foo')
    def test_add_cachebust_prevented(self):
        config = self._makeConfig()
        config.registry.settings['pyramid.prevent_cachebust'] = True
        inst = self._makeOne()
        inst.add(config, 'view', 'mypackage:path', cachebust=True)
        cachebust = config.registry._static_url_registrations[0][3]
        self.assertEqual(cachebust, None)
    def test_add_cachebust_custom(self):
        config = self._makeConfig()
        inst = self._makeOne()
        inst.add(config, 'view', 'mypackage:path',
                 cachebust=DummyCacheBuster('foo'))
        cachebust = config.registry._static_url_registrations[0][3]
        subpath, kw = cachebust('some/path', {})
        self.assertEqual(subpath, 'some/path')
        self.assertEqual(kw['x'], 'foo')
class Test_view_description(unittest.TestCase):
    def _callFUT(self, view):
@@ -3939,7 +4079,8 @@
class DummyRegistry:
    pass
    def __init__(self):
        self.settings = {}
from zope.interface import implementer
from pyramid.interfaces import IResponse
@@ -4025,6 +4166,13 @@
    def __permitted__(self, context, request):
        """ """
class DummyCacheBuster(object):
    def __init__(self, token):
        self.token = token
    def pregenerate(self, pathspec, subpath, kw):
        kw['x'] = self.token
        return subpath, kw
def parse_httpdate(s):
    import datetime
    # cannot use %Z, must use literal GMT; Jython honors timezone
pyramid/tests/test_decorator.py
@@ -15,15 +15,19 @@
        self.assertEqual(inst.__dict__['wrapped'], 'a')
    def test___get__noinst(self):
        decorator = self._makeOne(None)
        def wrapped(inst):
            return 'a'  # pragma: no cover
        decorator = self._makeOne(wrapped)
        result = decorator.__get__(None)
        self.assertEqual(result, decorator)
    def test___doc__copied(self):
        def wrapped(inst):
            """My doc"""
        decorator = self._makeOne(wrapped)
        self.assertEqual(decorator.__doc__, "My doc")
    def test_dunder_attrs_copied(self):
        from pyramid.util import viewdefaults
        decorator = self._makeOne(viewdefaults)
        self.assertEqual(decorator.__doc__, viewdefaults.__doc__)
        self.assertEqual(decorator.__name__, viewdefaults.__name__)
        self.assertEqual(decorator.__module__, viewdefaults.__module__)
class Dummy(object):
    pass
pyramid/tests/test_integration.py
@@ -81,7 +81,7 @@
        res = self.testapp.get('/static/.hiddenfile', status=200)
        _assertBody(res.body, os.path.join(here, 'fixtures/static/.hiddenfile'))
    if defaultlocale is not None:
    if defaultlocale is not None: # pragma: no cover
        # These tests are expected to fail on LANG=C systems due to decode
        # errors and on non-Linux systems due to git highchar handling
        # vagaries
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_registry.py
@@ -12,6 +12,11 @@
        registry = self._makeOne()
        self.assertEqual(registry.__nonzero__(), True)
    def test_package_name(self):
        package_name = 'testing'
        registry = self._getTargetClass()(package_name)
        self.assertEqual(registry.package_name, package_name)
    def test_registerHandler_and_notify(self):
        registry = self._makeOne()
        self.assertEqual(registry.has_listeners, False)
pyramid/tests/test_renderers.py
@@ -182,7 +182,10 @@
        from pyramid.interfaces import IResponseFactory
        class ResponseFactory(object):
            pass
        self.config.registry.registerUtility(ResponseFactory, IResponseFactory)
        self.config.registry.registerUtility(
            lambda r: ResponseFactory(), IResponseFactory
        )
    def test_render_to_response(self):
        self._registerRendererFactory()
@@ -191,8 +194,8 @@
        helper = self._makeOne('loo.foo')
        response = helper.render_to_response('values', {},
                                             request=request)
        self.assertEqual(response.body[0], 'values')
        self.assertEqual(response.body[1], {})
        self.assertEqual(response.app_iter[0], 'values')
        self.assertEqual(response.app_iter[1], {})
    def test_get_renderer(self):
        factory = self._registerRendererFactory()
@@ -209,8 +212,8 @@
        request = testing.DummyRequest()
        response = 'response'
        response = helper.render_view(request, response, view, context)
        self.assertEqual(response.body[0], 'response')
        self.assertEqual(response.body[1],
        self.assertEqual(response.app_iter[0], 'response')
        self.assertEqual(response.app_iter[1],
                          {'renderer_info': helper,
                           'renderer_name': 'loo.foo',
                           'request': request,
@@ -287,6 +290,23 @@
        response = helper._make_response(la.encode('utf-8'), request)
        self.assertEqual(response.body, la.encode('utf-8'))
    def test__make_response_result_is_iterable(self):
        from pyramid.response import Response
        request = testing.DummyRequest()
        request.response = Response()
        helper = self._makeOne('loo.foo')
        la = text_('/La Pe\xc3\xb1a', 'utf-8')
        response = helper._make_response([la.encode('utf-8')], request)
        self.assertEqual(response.body, la.encode('utf-8'))
    def test__make_response_result_is_other(self):
        self._registerResponseFactory()
        request = None
        helper = self._makeOne('loo.foo')
        result = object()
        response = helper._make_response(result, request)
        self.assertEqual(response.body, result)
    def test__make_response_result_is_None_no_body(self):
        from pyramid.response import Response
        request = testing.DummyRequest()
@@ -310,7 +330,9 @@
        class ResponseFactory(object):
            def __init__(self):
                pass
        self.config.registry.registerUtility(ResponseFactory, IResponseFactory)
        self.config.registry.registerUtility(
            lambda r: ResponseFactory(), IResponseFactory
        )
        request = testing.DummyRequest()
        helper = self._makeOne('loo.foo')
        response = helper._make_response(b'abc', request)
@@ -495,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(
@@ -531,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):
@@ -580,13 +640,26 @@
        self.assertEqual(request.response.content_type,
                         'application/json')
    def test_render_without_request(self):
        renderer_factory = self._makeOne()
        renderer = renderer_factory(None)
        result = renderer({'a':'1'}, {})
        self.assertEqual(result, '{"a": "1"}')
class Dummy:
    pass
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
@@ -1,3 +1,4 @@
from collections import deque
import unittest
from pyramid import testing
@@ -119,13 +120,13 @@
    def test_add_response_callback(self):
        inst = self._makeOne()
        self.assertEqual(inst.response_callbacks, ())
        self.assertEqual(inst.response_callbacks, None)
        def callback(request, response):
            """ """
        inst.add_response_callback(callback)
        self.assertEqual(inst.response_callbacks, [callback])
        self.assertEqual(list(inst.response_callbacks), [callback])
        inst.add_response_callback(callback)
        self.assertEqual(inst.response_callbacks, [callback, callback])
        self.assertEqual(list(inst.response_callbacks), [callback, callback])
    def test__process_response_callbacks(self):
        inst = self._makeOne()
@@ -135,24 +136,48 @@
        def callback2(request, response):
            request.called2  = True
            response.called2 = True
        inst.response_callbacks = [callback1, callback2]
        inst.add_response_callback(callback1)
        inst.add_response_callback(callback2)
        response = DummyResponse()
        inst._process_response_callbacks(response)
        self.assertEqual(inst.called1, True)
        self.assertEqual(inst.called2, True)
        self.assertEqual(response.called1, True)
        self.assertEqual(response.called2, True)
        self.assertEqual(inst.response_callbacks, [])
        self.assertEqual(len(inst.response_callbacks), 0)
    def test__process_response_callback_adding_response_callback(self):
        """
        When a response callback adds another callback, that new callback should still be called.
        See https://github.com/Pylons/pyramid/pull/1373
        """
        inst = self._makeOne()
        def callback1(request, response):
            request.called1 = True
            response.called1 = True
            request.add_response_callback(callback2)
        def callback2(request, response):
            request.called2  = True
            response.called2 = True
        inst.add_response_callback(callback1)
        response = DummyResponse()
        inst._process_response_callbacks(response)
        self.assertEqual(inst.called1, True)
        self.assertEqual(inst.called2, True)
        self.assertEqual(response.called1, True)
        self.assertEqual(response.called2, True)
        self.assertEqual(len(inst.response_callbacks), 0)
    def test_add_finished_callback(self):
        inst = self._makeOne()
        self.assertEqual(inst.finished_callbacks, ())
        self.assertEqual(inst.finished_callbacks, None)
        def callback(request):
            """ """
        inst.add_finished_callback(callback)
        self.assertEqual(inst.finished_callbacks, [callback])
        self.assertEqual(list(inst.finished_callbacks), [callback])
        inst.add_finished_callback(callback)
        self.assertEqual(inst.finished_callbacks, [callback, callback])
        self.assertEqual(list(inst.finished_callbacks), [callback, callback])
    def test__process_finished_callbacks(self):
        inst = self._makeOne()
@@ -160,11 +185,12 @@
            request.called1 = True
        def callback2(request):
            request.called2  = True
        inst.finished_callbacks = [callback1, callback2]
        inst.add_finished_callback(callback1)
        inst.add_finished_callback(callback2)
        inst._process_finished_callbacks()
        self.assertEqual(inst.called1, True)
        self.assertEqual(inst.called2, True)
        self.assertEqual(inst.finished_callbacks, [])
        self.assertEqual(len(inst.finished_callbacks), 0)
    def test_resource_url(self):
        self._registerResourceURL()
@@ -284,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')
@@ -409,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_response.py
@@ -1,4 +1,5 @@
import io
import mimetypes
import os
import unittest
from pyramid import testing
@@ -7,7 +8,7 @@
    def _getTargetClass(self):
        from pyramid.response import Response
        return Response
    def test_implements_IResponse(self):
        from pyramid.interfaces import IResponse
        cls = self._getTargetClass()
@@ -51,15 +52,11 @@
        r.app_iter.close()
    def test_without_content_type(self):
        for suffix, content_type in (
            ('txt', 'text/plain; charset=UTF-8'),
            ('xml', 'application/xml; charset=UTF-8'),
            ('pdf', 'application/pdf')
        ):
        for suffix in ('txt', 'xml', 'pdf'):
            path = self._getPath(suffix)
            r = self._makeOne(path)
            self.assertEqual(r.content_type, content_type.split(';')[0])
            self.assertEqual(r.headers['content-type'], content_type)
            self.assertEqual(r.headers['content-type'].split(';')[0],
                             mimetypes.guess_type(path, strict=False)[0])
            r.app_iter.close()
    def test_python_277_bug_15207(self):
@@ -122,7 +119,7 @@
        result = self._callFUT(module)
        self.assertEqual(result, True)
        self.assertEqual(module.initted, True)
    def test_missing_init(self):
        class DummyMimetypes(object):
            pass
@@ -177,6 +174,17 @@
        self.assertEqual(dummy_venusian.attached,
                         [(foo, dec.register, 'pyramid')])
class TestGetResponseFactory(unittest.TestCase):
    def test_get_factory(self):
        from pyramid.registry import Registry
        from pyramid.response import Response, _get_response_factory
        registry = Registry()
        response = _get_response_factory(registry)(None)
        self.assertTrue(isinstance(response, Response))
class Dummy(object):
    pass
@@ -193,5 +201,3 @@
    def attach(self, wrapped, fn, category=None):
        self.attached.append((wrapped, fn, category))
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
@@ -522,7 +524,7 @@
        def view(context, request):
            def callback(request, response):
                response.called_back = True
            request.response_callbacks = [callback]
            request.add_response_callback(callback)
            return response
        environ = self._makeEnviron()
        self._registerView(view, '', IViewClassifier, IRequest, IContext)
@@ -545,7 +547,7 @@
        def view(context, request):
            def callback(request):
                request.environ['called_back'] = True
            request.finished_callbacks = [callback]
            request.add_finished_callback(callback)
            return response
        environ = self._makeEnviron()
        self._registerView(view, '', IViewClassifier, IRequest, IContext)
@@ -567,7 +569,7 @@
        def view(context, request):
            def callback(request):
                request.environ['called_back'] = True
            request.finished_callbacks = [callback]
            request.add_finished_callback(callback)
            raise NotImplementedError
        environ = self._makeEnviron()
        self._registerView(view, '', IViewClassifier, IRequest, IContext)
@@ -599,17 +601,19 @@
        environ = self._makeEnviron()
        self._registerView(view, '', IViewClassifier, None, None)
        request_events = self._registerEventListener(INewRequest)
        aftertraversal_events = self._registerEventListener(IContextFound)
        context_found_events = self._registerEventListener(IContextFound)
        response_events = self._registerEventListener(INewResponse)
        router = self._makeOne()
        start_response = DummyStartResponse()
        result = router(environ, start_response)
        self.assertEqual(len(request_events), 1)
        self.assertEqual(request_events[0].request.environ, environ)
        self.assertEqual(len(aftertraversal_events), 1)
        self.assertEqual(aftertraversal_events[0].request.environ, environ)
        self.assertEqual(len(context_found_events), 1)
        self.assertEqual(context_found_events[0].request.environ, environ)
        self.assertEqual(context_found_events[0].request.context, context)
        self.assertEqual(len(response_events), 1)
        self.assertEqual(response_events[0].response, response)
        self.assertEqual(response_events[0].request.context, context)
        self.assertEqual(result, response.app_iter)
    def test_call_newrequest_evllist_exc_can_be_caught_by_exceptionview(self):
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/dummy.py
@@ -60,7 +60,7 @@
    def __init__(self, *routes):
        self.routes = routes
    def get_routes(self):
    def get_routes(self, include_static=False):
        return self.routes
class DummyRoute(object):
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_pcreate.py
@@ -12,10 +12,10 @@
        from pyramid.scripts.pcreate import PCreateCommand
        return PCreateCommand
    def _makeOne(self, *args):
    def _makeOne(self, *args, **kw):
        effargs = ['pcreate']
        effargs.extend(args)
        cmd = self._getTargetClass()(effargs)
        cmd = self._getTargetClass()(effargs, **kw)
        cmd.out = self.out
        return cmd
@@ -34,8 +34,13 @@
        out = self.out_.getvalue()
        self.assertTrue(out.startswith('No scaffolds available'))
    def test_run_no_scaffold_no_args(self):
        cmd = self._makeOne(quiet=True)
        result = cmd.run()
        self.assertEqual(result, 2)
    def test_run_no_scaffold_name(self):
        cmd = self._makeOne()
        cmd = self._makeOne('dummy')
        result = cmd.run()
        self.assertEqual(result, 2)
        out = self.out_.getvalue()
@@ -73,6 +78,23 @@
            {'project': 'Distro', 'egg': 'Distro', 'package': 'distro',
             'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'})
    def test_scaffold_with_hyphen_in_project_name(self):
        import os
        cmd = self._makeOne('-s', 'dummy', 'Distro-')
        scaffold = DummyScaffold('dummy')
        cmd.scaffolds = [scaffold]
        cmd.pyramid_dist = DummyDist("0.1")
        result = cmd.run()
        self.assertEqual(result, 0)
        self.assertEqual(
            scaffold.output_dir,
            os.path.normpath(os.path.join(os.getcwd(), 'Distro-'))
            )
        self.assertEqual(
            scaffold.vars,
            {'project': 'Distro-', 'egg': 'Distro_', 'package': 'distro_',
             'pyramid_version': '0.1', 'pyramid_docs_branch':'0.1-branch'})
    def test_known_scaffold_absolute_path(self):
        import os
        path = os.path.abspath('Distro')
pyramid/tests/test_scripts/test_prequest.py
@@ -210,8 +210,21 @@
        self.assertEqual(self._path_info, '/')
        self.assertEqual(self._spec, 'development.ini')
        self.assertEqual(self._app_name, None)
        self.assertEqual(self._out, [b'abc'])
    def test_command_method_configures_logging(self):
        command = self._makeOne(['', 'development.ini', '/'])
        called_args = []
        def configure_logging(app_spec):
            called_args.append(app_spec)
        command.configure_logging = configure_logging
        command.run()
        self.assertEqual(called_args, ['development.ini'])
class Test_main(unittest.TestCase):
    def _callFUT(self, argv):
        from pyramid.scripts.prequest import main
pyramid/tests/test_scripts/test_proutes.py
@@ -1,6 +1,16 @@
import unittest
from pyramid.tests.test_scripts import dummy
class DummyIntrospector(object):
    def __init__(self):
        self.relations = {}
        self.introspectables = {}
    def get(self, name, discrim):
        pass
class TestPRoutesCommand(unittest.TestCase):
    def _getTargetClass(self):
        from pyramid.scripts.proutes import PRoutesCommand
@@ -10,7 +20,19 @@
        cmd = self._getTargetClass()([])
        cmd.bootstrap = (dummy.DummyBootstrap(),)
        cmd.args = ('/foo/bar/myapp.ini#myapp',)
        return cmd
    def _makeRegistry(self):
        from pyramid.registry import Registry
        registry = Registry()
        registry.introspector = DummyIntrospector()
        return registry
    def _makeConfig(self, *arg, **kw):
        from pyramid.config import Configurator
        config = Configurator(*arg, **kw)
        return config
    def test_good_args(self):
        cmd = self._getTargetClass()([])
@@ -19,6 +41,8 @@
        route = dummy.DummyRoute('a', '/a')
        mapper = dummy.DummyMapper(route)
        cmd._get_mapper = lambda *arg: mapper
        registry = self._makeRegistry()
        cmd.bootstrap = (dummy.DummyBootstrap(registry=registry),)
        L = []
        cmd.out = lambda msg: L.append(msg)
        cmd.run()
@@ -58,12 +82,15 @@
        route = dummy.DummyRoute('a', '/a')
        mapper = dummy.DummyMapper(route)
        command._get_mapper = lambda *arg: mapper
        registry = self._makeRegistry()
        command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
        L = []
        command.out = L.append
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>'])
        self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>', '*'])
    def test_route_with_no_slash_prefix(self):
        command = self._makeOne()
@@ -72,16 +99,18 @@
        command._get_mapper = lambda *arg: mapper
        L = []
        command.out = L.append
        registry = self._makeRegistry()
        command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>'])
        self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>', '*'])
    def test_single_route_no_views_registered(self):
        from zope.interface import Interface
        from pyramid.registry import Registry
        from pyramid.interfaces import IRouteRequest
        registry = Registry()
        registry = self._makeRegistry()
        def view():pass
        class IMyRoute(Interface):
            pass
@@ -96,15 +125,15 @@
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        self.assertEqual(L[-1].split()[:3], ['a', '/a', 'None'])
        self.assertEqual(L[-1].split()[:3], ['a', '/a', '<unknown>'])
    def test_single_route_one_view_registered(self):
        from zope.interface import Interface
        from pyramid.registry import Registry
        from pyramid.interfaces import IRouteRequest
        from pyramid.interfaces import IViewClassifier
        from pyramid.interfaces import IView
        registry = Registry()
        registry = self._makeRegistry()
        def view():pass
        class IMyRoute(Interface):
            pass
@@ -123,15 +152,60 @@
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()[:3]
        self.assertEqual(compare_to, ['a', '/a', '<function'])
    def test_single_route_one_view_registered_with_factory(self):
        self.assertEqual(
            compare_to,
            ['a', '/a', 'pyramid.tests.test_scripts.test_proutes.view']
        )
    def test_one_route_with_long_name_one_view_registered(self):
        from zope.interface import Interface
        from pyramid.registry import Registry
        from pyramid.interfaces import IRouteRequest
        from pyramid.interfaces import IViewClassifier
        from pyramid.interfaces import IView
        registry = Registry()
        registry = self._makeRegistry()
        def view():pass
        class IMyRoute(Interface):
            pass
        registry.registerAdapter(
            view,
            (IViewClassifier, IMyRoute, Interface),
            IView, ''
        )
        registry.registerUtility(IMyRoute, IRouteRequest,
                                 name='very_long_name_123')
        command = self._makeOne()
        route = dummy.DummyRoute(
            'very_long_name_123',
            '/and_very_long_pattern_as_well'
        )
        mapper = dummy.DummyMapper(route)
        command._get_mapper = lambda *arg: mapper
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()[:3]
        self.assertEqual(
            compare_to,
            ['very_long_name_123',
             '/and_very_long_pattern_as_well',
             'pyramid.tests.test_scripts.test_proutes.view']
        )
    def test_single_route_one_view_registered_with_factory(self):
        from zope.interface import Interface
        from pyramid.interfaces import IRouteRequest
        from pyramid.interfaces import IViewClassifier
        from pyramid.interfaces import IView
        registry = self._makeRegistry()
        def view():pass
        class IMyRoot(Interface):
            pass
@@ -154,14 +228,529 @@
        self.assertEqual(len(L), 3)
        self.assertEqual(L[-1].split()[:3], ['a', '/a', '<unknown>'])
    def test_single_route_multiview_registered(self):
        from zope.interface import Interface
        from pyramid.interfaces import IRouteRequest
        from pyramid.interfaces import IViewClassifier
        from pyramid.interfaces import IMultiView
        registry = self._makeRegistry()
        def view(): pass
        class IMyRoute(Interface):
            pass
        multiview1 = dummy.DummyMultiView(
            view, context='context',
            view_name='a1'
        )
        registry.registerAdapter(
            multiview1,
            (IViewClassifier, IMyRoute, Interface),
            IMultiView, ''
        )
        registry.registerUtility(IMyRoute, IRouteRequest, name='a')
        command = self._makeOne()
        route = dummy.DummyRoute('a', '/a')
        mapper = dummy.DummyMapper(route)
        command._get_mapper = lambda *arg: mapper
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()[:3]
        view_module = 'pyramid.tests.test_scripts.dummy'
        view_str = '<pyramid.tests.test_scripts.dummy.DummyMultiView'
        final = '%s.%s' % (view_module, view_str)
        self.assertEqual(
            compare_to,
            ['a', '/a', final]
        )
    def test__get_mapper(self):
        from pyramid.registry import Registry
        from pyramid.urldispatch import RoutesMapper
        command = self._makeOne()
        registry = Registry()
        registry = self._makeRegistry()
        result = command._get_mapper(registry)
        self.assertEqual(result.__class__, RoutesMapper)
    def test_one_route_all_methods_view_only_post(self):
        from pyramid.renderers import null_renderer as nr
        def view1(context, request): return 'view1'
        config = self._makeConfig(autocommit=True)
        config.add_route('foo', '/a/b')
        config.add_view(
            route_name='foo',
            view=view1,
            renderer=nr,
            request_method='POST'
        )
        command = self._makeOne()
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()
        expected = [
            'foo', '/a/b',
            'pyramid.tests.test_scripts.test_proutes.view1', 'POST'
        ]
        self.assertEqual(compare_to, expected)
    def test_one_route_only_post_view_all_methods(self):
        from pyramid.renderers import null_renderer as nr
        def view1(context, request): return 'view1'
        config = self._makeConfig(autocommit=True)
        config.add_route('foo', '/a/b', request_method='POST')
        config.add_view(
            route_name='foo',
            view=view1,
            renderer=nr,
        )
        command = self._makeOne()
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()
        expected = [
            'foo', '/a/b',
            'pyramid.tests.test_scripts.test_proutes.view1', 'POST'
        ]
        self.assertEqual(compare_to, expected)
    def test_one_route_only_post_view_post_and_get(self):
        from pyramid.renderers import null_renderer as nr
        def view1(context, request): return 'view1'
        config = self._makeConfig(autocommit=True)
        config.add_route('foo', '/a/b', request_method='POST')
        config.add_view(
            route_name='foo',
            view=view1,
            renderer=nr,
            request_method=('POST', 'GET')
        )
        command = self._makeOne()
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()
        expected = [
            'foo', '/a/b',
            'pyramid.tests.test_scripts.test_proutes.view1', 'POST'
        ]
        self.assertEqual(compare_to, expected)
    def test_route_request_method_mismatch(self):
        from pyramid.renderers import null_renderer as nr
        def view1(context, request): return 'view1'
        config = self._makeConfig(autocommit=True)
        config.add_route('foo', '/a/b', request_method='POST')
        config.add_view(
            route_name='foo',
            view=view1,
            renderer=nr,
            request_method='GET'
        )
        command = self._makeOne()
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()
        expected = [
            'foo', '/a/b',
            'pyramid.tests.test_scripts.test_proutes.view1',
            '<route', 'mismatch>'
        ]
        self.assertEqual(compare_to, expected)
    def test_route_static_views(self):
        from pyramid.renderers import null_renderer as nr
        config = self._makeConfig(autocommit=True)
        config.add_static_view('static', 'static', cache_max_age=3600)
        config.add_static_view(name='static2', path='/var/www/static')
        config.add_static_view(
            name='pyramid_scaffold',
            path='pyramid:scaffolds/starter/+package+/static'
        )
        command = self._makeOne()
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 5)
        expected = [
            ['__static/', '/static/*subpath',
             'pyramid.tests.test_scripts:static/', '*'],
            ['__static2/', '/static2/*subpath', '/var/www/static/', '*'],
            ['__pyramid_scaffold/', '/pyramid_scaffold/*subpath',
             'pyramid:scaffolds/starter/+package+/static/',  '*'],
        ]
        for index, line in enumerate(L[2:]):
            data = line.split()
            self.assertEqual(data, expected[index])
    def test_route_no_view(self):
        from pyramid.renderers import null_renderer as nr
        config = self._makeConfig(autocommit=True)
        config.add_route('foo', '/a/b', request_method='POST')
        command = self._makeOne()
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()
        expected = [
            'foo', '/a/b',
            '<unknown>',
            'POST',
        ]
        self.assertEqual(compare_to, expected)
    def test_route_as_wsgiapp(self):
        from pyramid.wsgi import wsgiapp2
        config1 = self._makeConfig(autocommit=True)
        def view1(context, request): return 'view1'
        config1.add_route('foo', '/a/b', request_method='POST')
        config1.add_view(view=view1, route_name='foo')
        config2 = self._makeConfig(autocommit=True)
        config2.add_route('foo', '/a/b', request_method='POST')
        config2.add_view(
            wsgiapp2(config1.make_wsgi_app()),
            route_name='foo',
        )
        command = self._makeOne()
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=config2.registry),)
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()
        expected = [
            'foo', '/a/b',
            '<wsgiapp>',
            'POST',
        ]
        self.assertEqual(compare_to, expected)
    def test_route_is_get_view_request_method_not_post(self):
        from pyramid.renderers import null_renderer as nr
        from pyramid.config import not_
        def view1(context, request): return 'view1'
        config = self._makeConfig(autocommit=True)
        config.add_route('foo', '/a/b', request_method='GET')
        config.add_view(
            route_name='foo',
            view=view1,
            renderer=nr,
            request_method=not_('POST')
        )
        command = self._makeOne()
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()
        expected = [
            'foo', '/a/b',
            'pyramid.tests.test_scripts.test_proutes.view1',
            'GET'
        ]
        self.assertEqual(compare_to, expected)
    def test_view_request_method_not_post(self):
        from pyramid.renderers import null_renderer as nr
        from pyramid.config import not_
        def view1(context, request): return 'view1'
        config = self._makeConfig(autocommit=True)
        config.add_route('foo', '/a/b')
        config.add_view(
            route_name='foo',
            view=view1,
            renderer=nr,
            request_method=not_('POST')
        )
        command = self._makeOne()
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()
        expected = [
            'foo', '/a/b',
            'pyramid.tests.test_scripts.test_proutes.view1',
            '!POST,*'
        ]
        self.assertEqual(compare_to, expected)
    def test_view_glob(self):
        from pyramid.renderers import null_renderer as nr
        from pyramid.config import not_
        def view1(context, request): return 'view1'
        def view2(context, request): return 'view2'
        config = self._makeConfig(autocommit=True)
        config.add_route('foo', '/a/b')
        config.add_view(
            route_name='foo',
            view=view1,
            renderer=nr,
            request_method=not_('POST')
        )
        config.add_route('bar', '/b/a')
        config.add_view(
            route_name='bar',
            view=view2,
            renderer=nr,
            request_method=not_('POST')
        )
        command = self._makeOne()
        command.options.glob = '*foo*'
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()
        expected = [
            'foo', '/a/b',
            'pyramid.tests.test_scripts.test_proutes.view1',
            '!POST,*'
        ]
        self.assertEqual(compare_to, expected)
    def test_good_format(self):
        from pyramid.renderers import null_renderer as nr
        from pyramid.config import not_
        def view1(context, request): return 'view1'
        config = self._makeConfig(autocommit=True)
        config.add_route('foo', '/a/b')
        config.add_view(
            route_name='foo',
            view=view1,
            renderer=nr,
            request_method=not_('POST')
        )
        command = self._makeOne()
        command.options.glob = '*foo*'
        command.options.format = 'method,name'
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()
        expected = ['!POST,*', 'foo']
        self.assertEqual(compare_to, expected)
        self.assertEqual(L[0].split(), ['Method', 'Name'])
    def test_bad_format(self):
        from pyramid.renderers import null_renderer as nr
        from pyramid.config import not_
        def view1(context, request): return 'view1'
        config = self._makeConfig(autocommit=True)
        config.add_route('foo', '/a/b')
        config.add_view(
            route_name='foo',
            view=view1,
            renderer=nr,
            request_method=not_('POST')
        )
        command = self._makeOne()
        command.options.glob = '*foo*'
        command.options.format = 'predicates,name,pattern'
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
        expected = (
            "You provided invalid formats ['predicates'], "
            "Available formats are ['name', 'pattern', 'view', 'method']"
        )
        result = command.run()
        self.assertEqual(result, 2)
        self.assertEqual(L[0], expected)
    def test_config_format_ini_newlines(self):
        from pyramid.renderers import null_renderer as nr
        from pyramid.config import not_
        def view1(context, request): return 'view1'
        config = self._makeConfig(autocommit=True)
        config.add_route('foo', '/a/b')
        config.add_view(
            route_name='foo',
            view=view1,
            renderer=nr,
            request_method=not_('POST')
        )
        command = self._makeOne()
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
        config_factory = dummy.DummyConfigParserFactory()
        command.ConfigParser = config_factory
        config_factory.items = [('format', 'method\nname')]
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()
        expected = ['!POST,*', 'foo']
        self.assertEqual(compare_to, expected)
        self.assertEqual(L[0].split(), ['Method', 'Name'])
    def test_config_format_ini_spaces(self):
        from pyramid.renderers import null_renderer as nr
        from pyramid.config import not_
        def view1(context, request): return 'view1'
        config = self._makeConfig(autocommit=True)
        config.add_route('foo', '/a/b')
        config.add_view(
            route_name='foo',
            view=view1,
            renderer=nr,
            request_method=not_('POST')
        )
        command = self._makeOne()
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
        config_factory = dummy.DummyConfigParserFactory()
        command.ConfigParser = config_factory
        config_factory.items = [('format', 'method name')]
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()
        expected = ['!POST,*', 'foo']
        self.assertEqual(compare_to, expected)
        self.assertEqual(L[0].split(), ['Method', 'Name'])
    def test_config_format_ini_commas(self):
        from pyramid.renderers import null_renderer as nr
        from pyramid.config import not_
        def view1(context, request): return 'view1'
        config = self._makeConfig(autocommit=True)
        config.add_route('foo', '/a/b')
        config.add_view(
            route_name='foo',
            view=view1,
            renderer=nr,
            request_method=not_('POST')
        )
        command = self._makeOne()
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
        config_factory = dummy.DummyConfigParserFactory()
        command.ConfigParser = config_factory
        config_factory.items = [('format', 'method,name')]
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()
        expected = ['!POST,*', 'foo']
        self.assertEqual(compare_to, expected)
        self.assertEqual(L[0].split(), ['Method', 'Name'])
    def test_static_routes_included_in_list(self):
        from pyramid.renderers import null_renderer as nr
        config = self._makeConfig(autocommit=True)
        config.add_route('foo', 'http://example.com/bar.aspx', static=True)
        command = self._makeOne()
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()
        expected = [
            'foo', 'http://example.com/bar.aspx',
            '<unknown>', '*',
        ]
        self.assertEqual(compare_to, expected)
class Test_main(unittest.TestCase):
    def _callFUT(self, argv):
        from pyramid.scripts.proutes import main
@@ -170,4 +759,3 @@
    def test_it(self):
        result = self._callFUT(['proutes'])
        self.assertEqual(result, 2)
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
@@ -1,3 +1,4 @@
import os
import unittest
from pyramid.tests.test_scripts import dummy
@@ -24,6 +25,9 @@
            self.options.python_shell = ''
            self.options.setup = None
            cmd.options = self.options
        # default to None to prevent side-effects from running tests in
        # unknown environments
        cmd.pystartup = None
        return cmd
    def test_make_default_shell(self):
@@ -369,6 +373,25 @@
        self.assertTrue(self.bootstrap.closer.called)
        self.assertTrue(shell.help)
    def test_command_loads_pythonstartup(self):
        command = self._makeOne()
        command.pystartup = (
            os.path.abspath(
                os.path.join(
                    os.path.dirname(__file__),
                    'pystartup.txt')))
        shell = dummy.DummyShell()
        command.run(shell)
        self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp')
        self.assertEqual(shell.env, {
            'app':self.bootstrap.app, 'root':self.bootstrap.root,
            'registry':self.bootstrap.registry,
            'request':self.bootstrap.request,
            'root_factory':self.bootstrap.root_factory,
            'foo':1,
        })
        self.assertTrue(self.bootstrap.closer.called)
        self.assertTrue(shell.help)
class Test_main(unittest.TestCase):
    def _callFUT(self, argv):
pyramid/tests/test_security.py
@@ -134,9 +134,9 @@
    def tearDown(self):
        testing.tearDown()
    def _callFUT(self, *arg):
    def _callFUT(self, *arg, **kwarg):
        from pyramid.security import remember
        return remember(*arg)
        return remember(*arg, **kwarg)
    def test_no_authentication_policy(self):
        request = _makeRequest()
@@ -158,6 +158,19 @@
        _registerAuthenticationPolicy(registry, 'yo')
        result = self._callFUT(request, 'me')
        self.assertEqual(result, [('X-Pyramid-Test', 'me')])
    def test_with_deprecated_principal_arg(self):
        request = _makeRequest()
        registry = request.registry
        _registerAuthenticationPolicy(registry, 'yo')
        result = self._callFUT(request, principal='me')
        self.assertEqual(result, [('X-Pyramid-Test', 'me')])
    def test_with_missing_arg(self):
        request = _makeRequest()
        registry = request.registry
        _registerAuthenticationPolicy(registry, 'yo')
        self.assertRaises(TypeError, lambda: self._callFUT(request))
class TestForget(unittest.TestCase):
    def setUp(self):
@@ -462,8 +475,8 @@
    def authenticated_userid(self, request):
        return self.result
    def remember(self, request, principal, **kw):
        headers = [(_TEST_HEADER, principal)]
    def remember(self, request, userid, **kw):
        headers = [(_TEST_HEADER, userid)]
        self._header_remembered = headers[0]
        return headers
pyramid/tests/test_session.py
@@ -521,7 +521,7 @@
        result = wrapper(session, 'a')
        self.assertEqual(result, 1)
        callbacks = request.response_callbacks
        self.assertEqual(len(callbacks), 0)
        if callbacks is not None: self.assertEqual(len(callbacks), 0)
class Test_manage_changed(unittest.TestCase):
    def _makeOne(self, wrapped):
pyramid/tests/test_static.py
@@ -26,7 +26,7 @@
        if kw is not None:
            environ.update(kw)
        return Request(environ=environ)
    def test_ctor_defaultargs(self):
        inst = self._makeOne('package:resource_name')
        self.assertEqual(inst.package_name, 'package')
@@ -106,6 +106,14 @@
    def test_resource_is_file(self):
        inst = self._makeOne('pyramid.tests:fixtures/static')
        request = self._makeRequest({'PATH_INFO':'/index.html'})
        context = DummyContext()
        response = inst(context, request)
        self.assertTrue(b'<html>static</html>' in response.body)
    def test_cachebust_match(self):
        inst = self._makeOne('pyramid.tests:fixtures/static')
        inst.cachebust_match = lambda subpath: subpath[1:]
        request = self._makeRequest({'PATH_INFO':'/foo/index.html'})
        context = DummyContext()
        response = inst(context, request)
        self.assertTrue(b'<html>static</html>' in response.body)
@@ -218,7 +226,7 @@
        if kw is not None:
            environ.update(kw)
        return Request(environ=environ)
    def test_ctor_defaultargs(self):
        inst = self._makeOne('package:resource_name')
        self.assertEqual(inst.package_name, 'package')
@@ -273,7 +281,7 @@
        context = DummyContext()
        from pyramid.httpexceptions import HTTPNotFound
        self.assertRaises(HTTPNotFound, inst, context, request)
    def test_oob_os_sep(self):
        import os
        inst = self._makeOne('pyramid.tests:fixtures/static')
@@ -360,6 +368,155 @@
        from pyramid.httpexceptions import HTTPNotFound
        self.assertRaises(HTTPNotFound, inst, context, request)
class TestMd5AssetTokenGenerator(unittest.TestCase):
    _fspath = None
    _tmp = None
    @property
    def fspath(self):
        if self._fspath:
            return self._fspath
        import os
        import tempfile
        self._tmp = tmp = tempfile.mkdtemp()
        self._fspath = os.path.join(tmp, 'test.txt')
        return self._fspath
    def tearDown(self):
        import shutil
        if self._tmp:
            shutil.rmtree(self._tmp)
    def _makeOne(self):
        from pyramid.static import Md5AssetTokenGenerator as cls
        return cls()
    def test_package_resource(self):
        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().tokenize
        expected = 'd5155f250bef0e9923e894dbc713c5dd'
        with open(self.fspath, 'w') as f:
            f.write("Are we rich yet?")
        token = fut(self.fspath)
        self.assertEqual(token, expected)
    def test_cache(self):
        fut = self._makeOne().tokenize
        expected = 'd5155f250bef0e9923e894dbc713c5dd'
        with open(self.fspath, 'w') as f:
            f.write("Are we rich yet?")
        token = fut(self.fspath)
        self.assertEqual(token, expected)
        # md5 shouldn't change because we've cached it
        with open(self.fspath, 'w') as f:
            f.write("Sorry for the convenience.")
        token = fut(self.fspath)
        self.assertEqual(token, expected)
class TestPathSegmentMd5CacheBuster(unittest.TestCase):
    def _makeOne(self):
        from pyramid.static import PathSegmentMd5CacheBuster as cls
        inst = cls()
        inst.tokenize = lambda pathspec: 'foo'
        return inst
    def test_token(self):
        fut = self._makeOne().tokenize
        self.assertEqual(fut('whatever'), 'foo')
    def test_pregenerate(self):
        fut = self._makeOne().pregenerate
        self.assertEqual(fut('foo', ('bar',), 'kw'), (('foo', 'bar'), 'kw'))
    def test_match(self):
        fut = self._makeOne().match
        self.assertEqual(fut(('foo', 'bar')), ('bar',))
class TestQueryStringMd5CacheBuster(unittest.TestCase):
    def _makeOne(self, param=None):
        from pyramid.static import QueryStringMd5CacheBuster as cls
        if param:
            inst = cls(param)
        else:
            inst = cls()
        inst.tokenize = lambda pathspec: 'foo'
        return inst
    def test_token(self):
        fut = self._makeOne().tokenize
        self.assertEqual(fut('whatever'), 'foo')
    def test_pregenerate(self):
        fut = self._makeOne().pregenerate
        self.assertEqual(
            fut('foo', ('bar',), {}),
            (('bar',), {'_query': {'x': 'foo'}}))
    def test_pregenerate_change_param(self):
        fut = self._makeOne('y').pregenerate
        self.assertEqual(
            fut('foo', ('bar',), {}),
            (('bar',), {'_query': {'y': 'foo'}}))
    def test_pregenerate_query_is_already_tuples(self):
        fut = self._makeOne().pregenerate
        self.assertEqual(
            fut('foo', ('bar',), {'_query': [('a', 'b')]}),
            (('bar',), {'_query': (('a', 'b'), ('x', 'foo'))}))
    def test_pregenerate_query_is_tuple_of_tuples(self):
        fut = self._makeOne().pregenerate
        self.assertEqual(
            fut('foo', ('bar',), {'_query': (('a', 'b'),)}),
            (('bar',), {'_query': (('a', 'b'), ('x', 'foo'))}))
class TestQueryStringConstantCacheBuster(TestQueryStringMd5CacheBuster):
    def _makeOne(self, param=None):
        from pyramid.static import QueryStringConstantCacheBuster as cls
        if param:
            inst = cls('foo', param)
        else:
            inst = cls('foo')
        return inst
    def test_token(self):
        fut = self._makeOne().tokenize
        self.assertEqual(fut('whatever'), 'foo')
    def test_pregenerate(self):
        fut = self._makeOne().pregenerate
        self.assertEqual(
            fut('foo', ('bar',), {}),
            (('bar',), {'_query': {'x': 'foo'}}))
    def test_pregenerate_change_param(self):
        fut = self._makeOne('y').pregenerate
        self.assertEqual(
            fut('foo', ('bar',), {}),
            (('bar',), {'_query': {'y': 'foo'}}))
    def test_pregenerate_query_is_already_tuples(self):
        fut = self._makeOne().pregenerate
        self.assertEqual(
            fut('foo', ('bar',), {'_query': [('a', 'b')]}),
            (('bar',), {'_query': (('a', 'b'), ('x', 'foo'))}))
    def test_pregenerate_query_is_tuple_of_tuples(self):
        fut = self._makeOne().pregenerate
        self.assertEqual(
            fut('foo', ('bar',), {'_query': (('a', 'b'),)}),
            (('bar',), {'_query': (('a', 'b'), ('x', 'foo'))}))
class DummyContext:
    pass
pyramid/tests/test_testing.py
@@ -217,7 +217,7 @@
    def test_add_response_callback(self):
        request = self._makeOne()
        request.add_response_callback(1)
        self.assertEqual(request.response_callbacks, [1])
        self.assertEqual(list(request.response_callbacks), [1])
    def test_registry_is_config_registry_when_setup_is_called_after_ctor(self):
        # see https://github.com/Pylons/pyramid/issues/165
@@ -259,7 +259,9 @@
        registry = Registry('this_test')
        class ResponseFactory(object):
            pass
        registry.registerUtility(ResponseFactory, IResponseFactory)
        registry.registerUtility(
            lambda r: ResponseFactory(), IResponseFactory
        )
        request = self._makeOne()
        request.registry = registry
        resp = request.response
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
@@ -1,9 +1,193 @@
import unittest
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()
        class Foo(cls):
            pass
        return Foo()
@@ -109,43 +293,6 @@
        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__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):
        from pyramid.config import WeakOrderedSet
@@ -217,6 +364,49 @@
        self.assertEqual(list(wos), [])
        self.assertEqual(wos.last, None)
class Test_strings_differ(unittest.TestCase):
    def _callFUT(self, *args, **kw):
        from pyramid.util import strings_differ
        return strings_differ(*args, **kw)
    def test_it(self):
        self.assertFalse(self._callFUT(b'foo', b'foo'))
        self.assertTrue(self._callFUT(b'123', b'345'))
        self.assertTrue(self._callFUT(b'1234', b'123'))
        self.assertTrue(self._callFUT(b'123', b'1234'))
    def test_it_with_internal_comparator(self):
        result = self._callFUT(b'foo', b'foo', compare_digest=None)
        self.assertFalse(result)
        result = self._callFUT(b'123', b'abc', compare_digest=None)
        self.assertTrue(result)
    def test_it_with_external_comparator(self):
        class DummyComparator(object):
            called = False
            def __init__(self, ret_val):
                self.ret_val = ret_val
            def __call__(self, a, b):
                self.called = True
                return self.ret_val
        dummy_compare = DummyComparator(True)
        result = self._callFUT(b'foo', b'foo', compare_digest=dummy_compare)
        self.assertTrue(dummy_compare.called)
        self.assertFalse(result)
        dummy_compare = DummyComparator(False)
        result = self._callFUT(b'123', b'345', compare_digest=dummy_compare)
        self.assertTrue(dummy_compare.called)
        self.assertTrue(result)
        dummy_compare = DummyComparator(False)
        result = self._callFUT(b'abc', b'abc', compare_digest=dummy_compare)
        self.assertTrue(dummy_compare.called)
        self.assertTrue(result)
class Test_object_description(unittest.TestCase):
    def _callFUT(self, object):
        from pyramid.util import object_description
@@ -241,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):
@@ -281,7 +471,7 @@
        self.assertEqual(
            self._callFUT(inst),
            "object %s" % str(inst))
    def test_shortened_repr(self):
        inst = ['1'] * 1000
        self.assertEqual(
@@ -549,7 +739,7 @@
    def _getTargetClass(self):
        from pyramid.util import ActionInfo
        return ActionInfo
    def _makeOne(self, filename, lineno, function, linerepr):
        return self._getTargetClass()(filename, lineno, function, linerepr)
@@ -576,7 +766,36 @@
                         "Line 0 of file filename:\n       linerepr  ")
class TestCallableName(unittest.TestCase):
    def test_valid_ascii(self):
        from pyramid.util import get_callable_name
        from pyramid.compat import text_, PY3
        if PY3:  # pragma: nocover
            name = b'hello world'
        else:  # pragma: nocover
            name = text_(b'hello world', 'utf-8')
        self.assertEqual(get_callable_name(name), 'hello world')
    def test_invalid_ascii(self):
        from pyramid.util import get_callable_name
        from pyramid.compat import text_, PY3
        from pyramid.exceptions import ConfigurationError
        def get_bad_name():
            if PY3:  # pragma: nocover
                name = b'La Pe\xc3\xb1a'
            else:  # pragma: nocover
                name = text_(b'La Pe\xc3\xb1a', 'utf-8')
            get_callable_name(name)
        self.assertRaises(ConfigurationError, get_bad_name)
def dummyfunc(): pass
class Dummy(object):
    pass
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/url.py
@@ -223,7 +223,7 @@
        named portion in the generated URL.  For example, if you pass
        ``_host='foo.com'``, and the URL that would have been generated
        without the host replacement is ``http://example.com/a``, the result
        will be ``https://foo.com/a``.
        will be ``http://foo.com/a``.
        
        Note that if ``_scheme`` is passed as ``https``, and ``_port`` is not
        passed, the ``_port`` value is assumed to have been passed as
@@ -414,7 +414,7 @@
        portion in the generated URL.  For example, if you pass
        ``host='foo.com'``, and the URL that would have been generated
        without the host replacement is ``http://example.com/a``, the result
        will be ``https://foo.com/a``.
        will be ``http://foo.com/a``.
        
        If ``scheme`` is passed as ``https``, and an explicit ``port`` is not
        passed, the ``port`` value is assumed to have been passed as ``443``.
pyramid/urldispatch.py
@@ -42,12 +42,17 @@
class RoutesMapper(object):
    def __init__(self):
        self.routelist = []
        self.static_routes = []
        self.routes = {}
    def has_routes(self):
        return bool(self.routelist)
    def get_routes(self):
    def get_routes(self, include_static=False):
        if include_static is True:
            return self.routelist + self.static_routes
        return self.routelist
    def get_route(self, name):
@@ -59,9 +64,13 @@
            oldroute = self.routes[name]
            if oldroute in self.routelist:
                self.routelist.remove(oldroute)
        route = Route(name, pattern, factory, predicates, pregenerator)
        if not static:
            self.routelist.append(route)
        else:
            self.static_routes.append(route)
        self.routes[name] = route
        return route
@@ -201,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
@@ -1,4 +1,9 @@
import functools
try:
    # py2.7.7+ and py3.3+ have native comparison support
    from hmac import compare_digest
except ImportError: # pragma: nocover
    compare_digest = None
import inspect
import traceback
import weakref
@@ -17,6 +22,7 @@
    string_types,
    text_,
    PY3,
    native_
    )
from pyramid.interfaces import IActionInfo
@@ -28,14 +34,21 @@
_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.
@@ -50,7 +63,7 @@
                raise ValueError('cannot reify a property')
        elif name is not None:
            fn = lambda this: callable(this)
            fn.__name__ = name
            fn.__name__ = get_callable_name(name)
            fn.__doc__ = callable.__doc__
        else:
            name = callable.__name__
@@ -63,25 +76,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
@@ -100,14 +103,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)
        self._set_properties(extensions.descriptors)
    @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])
    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.
@@ -161,8 +184,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.
@@ -227,7 +250,7 @@
            oid = self._order[-1]
            return self._items[oid]()
def strings_differ(string1, string2):
def strings_differ(string1, string2, compare_digest=compare_digest):
    """Check whether two strings differ while avoiding timing attacks.
    This function returns True if the given strings differ and False
@@ -237,14 +260,25 @@
        http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf
    .. versionchanged:: 1.6
       Support :func:`hmac.compare_digest` if it is available (Python 2.7.7+
       and Python 3.3+).
    """
    if len(string1) != len(string2):
        return True
    len_eq = len(string1) == len(string2)
    if len_eq:
        invalid_bits = 0
        left = string1
    else:
        invalid_bits = 1
        left = string2
    right = string2
    invalid_bits = 0
    for a, b in zip(string1, string2):
        invalid_bits += a != b
    if compare_digest is not None:
        invalid_bits += not compare_digest(left, right)
    else:
        for a, b in zip(left, right):
            invalid_bits += a != b
    return invalid_bits != 0
def object_description(object):
@@ -275,7 +309,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, ')')
@@ -535,3 +569,17 @@
    wrapper.__docobj__ = wrapped
    return wrapper
def get_callable_name(name):
    """
    Verifies that the ``name`` is ascii and will raise a ``ConfigurationError``
    if it is not.
    """
    try:
        return native_(name, 'ascii')
    except (UnicodeEncodeError, UnicodeDecodeError):
        msg = (
            '`name="%s"` is invalid. `name` must be ascii because it is '
            'used on __name__ of the method'
        )
        raise ConfigurationError(msg % name)
pyramid/view.py
@@ -252,10 +252,11 @@
    .. deprecated:: 1.3
    """
    def __init__(self, notfound_view=None):
    def __init__(self, notfound_view=None, redirect_class=HTTPFound):
        if notfound_view is None:
            notfound_view = default_exceptionresponse_view
        self.notfound_view = notfound_view
        self.redirect_class = redirect_class
    def __call__(self, context, request):
        path = decode_path_info(request.environ['PATH_INFO'] or '/')
@@ -268,7 +269,7 @@
                    qs = request.query_string
                    if qs:
                        qs = '?' + qs
                    return HTTPFound(location=request.path+'/'+qs)
                    return self.redirect_class(location=request.path+'/'+qs)
        return self.notfound_view(context, request)
append_slash_notfound_view = AppendSlashNotFoundViewFactory()
@@ -331,6 +332,31 @@
    redirect to the URL implied by the route; if it does not, Pyramid will
    return the result of the view callable provided as ``view``, as normal.
    If the argument provided as ``append_slash`` is not a boolean but
    instead implements :class:`~pyramid.interfaces.IResponse`, the
    append_slash logic will behave as if ``append_slash=True`` was passed,
    but the provided class will be used as the response class instead of
    the default :class:`~pyramid.httpexceptions.HTTPFound` response class
    when a redirect is performed.  For example:
      .. code-block:: python
        from pyramid.httpexceptions import (
            HTTPMovedPermanently,
            HTTPNotFound
            )
        @notfound_view_config(append_slash=HTTPMovedPermanently)
        def aview(request):
            return HTTPNotFound('not found')
    The above means that a redirect to a slash-appended route will be
    attempted, but instead of :class:`~pyramid.httpexceptions.HTTPFound`
    being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will
    be used` for the redirect response if a slash-appended route is found.
    .. versionchanged:: 1.6
    See :ref:`changing_the_notfound_view` for detailed usage information.
    """
@@ -380,7 +406,7 @@
        @forbidden_view_config()
        def forbidden(request):
            return Response('You are not allowed', status='401 Unauthorized')
            return Response('You are not allowed', status='403 Forbidden')
    All arguments passed to this function have the same meaning as
    :meth:`pyramid.view.view_config` and each predicate argument restricts
rtd.txt
@@ -1,4 +1,4 @@
Sphinx >= 1.2.3
repoze.sphinx.autointerface
repoze.lru
pylons_sphinx_latesturl
setup.cfg
@@ -5,8 +5,6 @@
match=^test
where=pyramid
nocapture=1
cover-package=pyramid
cover-erase=1
[aliases]
dev = develop easy_install pyramid[testing]
setup.py
@@ -56,7 +56,7 @@
    tests_require.append('zope.component>=3.11.0')
docs_extras = [
    'Sphinx',
    'Sphinx >= 1.2.3',
    'docutils',
    'repoze.sphinx.autointerface',
    ]
@@ -68,7 +68,7 @@
    ]
setup(name='pyramid',
      version='1.6dev',
      version='1.6.dev0',
      description='The Pyramid Web Framework, a Pylons project',
      long_description=README + '\n\n' +  CHANGES,
      classifiers=[
tox.ini
@@ -1,22 +1,63 @@
[tox]
envlist =
    py26,py27,py32,py33,py34,pypy,cover
envlist =
    py26,py27,py32,py33,py34,pypy,pypy3,
    {py2,py3}-docs,
    {py2,py3}-cover,coverage
[testenv]
commands =
    python setup.py dev
    python setup.py 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
    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: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 = 
    python setup.py dev
    python setup.py nosetests --with-xunit --with-xcoverage
    coverage erase
    coverage combine
    coverage xml
    coverage report --show-missing --fail-under=100
deps =
    nosexcover
# 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.
    coverage
setenv =
    COVERAGE_FILE=.coverage