summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml6
-rw-r--r--CHANGES.txt84
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--HACKING.txt25
-rw-r--r--docs/api/config.rst6
-rw-r--r--docs/api/registry.rst14
-rw-r--r--docs/glossary.rst6
-rw-r--r--docs/index.rst12
-rw-r--r--docs/narr/advconfig.rst1
-rw-r--r--docs/narr/commandline.rst4
-rw-r--r--docs/narr/firstapp.rst13
-rw-r--r--docs/narr/helloworld.py15
-rw-r--r--docs/narr/hooks.rst98
-rw-r--r--docs/narr/introduction.rst4
-rw-r--r--docs/narr/sessions.rst13
-rw-r--r--docs/narr/templates.rst16
-rw-r--r--docs/narr/views.rst2
-rw-r--r--docs/tutorials/modwsgi/index.rst11
-rw-r--r--docs/tutorials/wiki/definingviews.rst2
-rw-r--r--docs/tutorials/wiki2/authorization.rst2
-rw-r--r--docs/tutorials/wiki2/basiclayout.rst4
-rw-r--r--docs/tutorials/wiki2/definingviews.rst2
-rw-r--r--docs/tutorials/wiki2/design.rst2
-rw-r--r--pyramid/config/__init__.py166
-rw-r--r--pyramid/config/factories.py125
-rw-r--r--pyramid/config/predicates.py228
-rw-r--r--pyramid/config/routes.py109
-rw-r--r--pyramid/config/security.py20
-rw-r--r--pyramid/config/tweens.py114
-rw-r--r--pyramid/config/util.py459
-rw-r--r--pyramid/config/views.py367
-rw-r--r--pyramid/interfaces.py13
-rw-r--r--pyramid/mako_templating.py57
-rw-r--r--pyramid/registry.py33
-rw-r--r--pyramid/scripts/proutes.py6
-rw-r--r--pyramid/scripts/pviews.py8
-rw-r--r--pyramid/testing.py3
-rw-r--r--pyramid/tests/fixtures/components.mak3
-rw-r--r--pyramid/tests/fixtures/hellocompo.mak3
-rw-r--r--pyramid/tests/test_config/test_factories.py96
-rw-r--r--pyramid/tests/test_config/test_init.py59
-rw-r--r--pyramid/tests/test_config/test_predicates.py268
-rw-r--r--pyramid/tests/test_config/test_routes.py2
-rw-r--r--pyramid/tests/test_config/test_security.py9
-rw-r--r--pyramid/tests/test_config/test_tweens.py46
-rw-r--r--pyramid/tests/test_config/test_util.py376
-rw-r--r--pyramid/tests/test_config/test_views.py126
-rw-r--r--pyramid/tests/test_events.py98
-rw-r--r--pyramid/tests/test_mako_templating.py86
-rw-r--r--pyramid/tests/test_path.py20
-rw-r--r--pyramid/tests/test_request.py55
-rw-r--r--pyramid/tests/test_scaffolds/test_copydir.py8
-rw-r--r--pyramid/tests/test_scripts/test_pviews.py4
-rw-r--r--pyramid/tests/test_testing.py3
-rw-r--r--pyramid/tests/test_url.py10
-rw-r--r--pyramid/tests/test_urldispatch.py8
-rw-r--r--pyramid/tests/test_view.py19
-rw-r--r--pyramid/urldispatch.py4
-rw-r--r--pyramid/util.py1
-rw-r--r--pyramid/view.py68
60 files changed, 2505 insertions, 919 deletions
diff --git a/.travis.yml b/.travis.yml
index 2e737af04..490fd2df7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,8 +6,14 @@ python:
- pypy
- 3.2
+# Why does travis-ci's PyPy blow up? Pyramid's tests
+# run fine under tox.
matrix:
allow_failures:
- python: pypy
script: python setup.py test
+
+notifications:
+ email:
+ - pyramid-checkins@lists.repoze.org
diff --git a/CHANGES.txt b/CHANGES.txt
index 7c2af4451..30df788b3 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -9,9 +9,48 @@ Bug Fixes
return the empty list. This was incorrect, it should have unconditionally
returned ``[Everyone]``, and now does.
+- Explicit url dispatch regexes can now contain colons.
+ https://github.com/Pylons/pyramid/issues/629
+
+- On at least one 64-bit Ubuntu system under Python 3.2, using the
+ ``view_config`` decorator caused a ``RuntimeError: dictionary changed size
+ during iteration`` exception. It no longer does. See
+ https://github.com/Pylons/pyramid/issues/635 for more information.
+
+- In Mako Templates lookup, check if the uri is already adjusted and bring
+ it back to an asset spec. Normally occurs with inherited templates or
+ included components.
+ https://github.com/Pylons/pyramid/issues/606
+ https://github.com/Pylons/pyramid/issues/607
+
+- In Mako Templates lookup, check for absolute uri (using mako directories)
+ when mixing up inheritance with asset specs.
+ https://github.com/Pylons/pyramid/issues/662
+
Features
--------
+- Third-party custom view and route predicates can now be added for use by
+ view authors via ``pyramid.config.Configurator.add_view_predicate`` and
+ ``pyramid.config.Configurator.add_route_predicate``. So, for example,
+ doing this::
+
+ config.add_view_predicate('abc', my.package.ABCPredicate)
+
+ Might allow a view author to do this in an application that configured that
+ predicate::
+
+ @view_config(abc=1)
+
+ See "Adding A Third Party View or Route Predicate" in the Hooks chapter for
+ more information.
+
+ Note that changes made to support the above feature now means that only
+ actions registered using the same "order" can conflict with one another.
+ It used to be the case that actions registered at different orders could
+ potentially conflict, but to my knowledge nothing ever depended on this
+ behavior (it was a bit silly).
+
- Custom objects can be made easily JSON-serializable in Pyramid by defining
a ``__json__`` method on the object's class. This method should return
values natively serializable by ``json.dumps`` (such as ints, lists,
@@ -29,15 +68,52 @@ Features
HEAD is a variant of GET that omits the body, and WebOb has special support
to return an empty body when a HEAD is used.
-- ``config.set_request_property`` now causes less code to be executed at
- request construction time.
+- ``config.set_request_method`` has been introduced to support extending
+ request objects with arbitrary callables. This method expands on the
+ previous ``config.set_request_property`` by supporting methods as well as
+ properties. This method now causes less code to be executed at
+ request construction time than ``config.set_request_property`` in
+ version 1.3.
-- Don't add a ``?`` to URLs generated by request.resource_url if the
+- Don't add a ``?`` to URLs generated by ``request.resource_url`` if the
``query`` argument is provided but empty.
-- Don't add a ``?`` to URLs generated by request.route_url if the
+- Don't add a ``?`` to URLs generated by ``request.route_url`` if the
``_query`` argument is provided but empty.
- The static view machinery now raises (rather than returns) ``HTTPNotFound``
and ``HTTPMovedPermanently`` exceptions, so these can be caught by the
NotFound view (and other exception views).
+
+- The mako renderer now accepts a def name and returns the template def
+ result for the view being called. The uri format using an asset spec is
+ package:path/to/template#defname.mako. The old way of returning a tuple
+ from the view is supported for backward compatibility, ('defname', {}).
+
+- When there is a predicate mismatch exception (seen when no view matches for
+ a given request due to predicates not working), the exception now contains
+ a textual description of the predicate which didn't match.
+
+- An ``add_permission`` directive method was added to the Configurator. This
+ directive registers a free-standing permission introspectable into the
+ Pyramid introspection system. Frameworks built atop Pyramid can thus use
+ the the ``permissions`` introspectable category data to build a
+ comprehensive list of permissions supported by a running system. Before
+ this method was added, permissions were already registered in this
+ introspectable category as a side effect of naming them in an ``add_view``
+ call, this method just makes it possible to arrange for a permission to be
+ put into the ``permissions`` introspectable category without naming it
+ along with an associated view. Here's an example of usage of
+ ``add_permission``::
+
+ config = Configurator()
+ config.add_permission('view')
+
+Deprecations
+------------
+
+- The ``pyramid.config.Configurator.set_request_property`` has been
+ documentation-deprecated. The method remains usable but the more
+ featureful ``pyramid.config.Configurator.set_request_method`` should be
+ used in its place (it has all of the same capabilities but can also extend
+ the request object with methods).
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 5ccac50d3..e44117af3 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -178,3 +178,5 @@ Contributors
- Jeff Cook, 2012/06/16
- Ian Wilson, 2012/06/17
+
+- Roman Kozlovskyi, 2012/08/11
diff --git a/HACKING.txt b/HACKING.txt
index dd735bf22..87d1422dc 100644
--- a/HACKING.txt
+++ b/HACKING.txt
@@ -30,11 +30,13 @@ checkout.
$ env/bin/easy_install setuptools-git
- Install Pyramid from the checkout into the virtualenv using ``setup.py
- develop`` (running ``setup.py develop`` *must* be done while the current
- working directory is the ``pyramid`` checkout directory)::
+ dev``. ``setup.py dev`` is an alias for "setup.py develop" which also
+ installs testing requirements such as nose and coverage. Running
+ ``setup.py dev`` *must* be done while the current working directory is the
+ ``pyramid`` checkout directory::
$ cd pyramid
- $ ../env/bin/python setup.py develop
+ $ ../env/bin/python setup.py dev
- At that point, you should be able to create new Pyramid projects by using
``pcreate``::
@@ -85,6 +87,9 @@ Coding Style
2 newlines between classes. But 80-column lines, in particular, are
mandatory.
+- Please do not remove trailing whitespace. Configure your editor to reduce
+ diff noise.
+
Running Tests
--------------
@@ -110,8 +115,8 @@ Test Coverage
- The codebase *must* have 100% test statement coverage after each commit.
You can test coverage via ``tox -e coverage``, or alternately by installing
- ``nose`` and ``coverage`` into your virtualenv, and running ``setup.py
- nosetests --with-coverage``.
+ ``nose`` and ``coverage`` into your virtualenv (easiest via ``setup.py
+ dev``) , and running ``setup.py nosetests --with-coverage``.
Documentation Coverage and Building HTML Documentation
------------------------------------------------------
@@ -127,13 +132,19 @@ using to develop Pyramid):
1. Run ``$yourvenv/bin/python setup.py dev docs``. This will cause Sphinx
and all development requirements to be installed in your virtualenv.
-2. cd to the ``docs`` directory within your Pyramid checkout and execute
+2. Update all git submodules from the top-level of your Pyramid checkout, like
+ so:
+ git submodule update --init --recursive
+ This will checkout theme subrepositories and prevent error conditions when
+ HTML docs are generated.
+
+3. cd to the ``docs`` directory within your Pyramid checkout and execute
``make clean html SPHINXBUILD=$yourvenv/bin/sphinx-build``. The
``SPHINXBUILD=...`` hair is there in order to tell it to use the
virtualenv Python, which will have both Sphinx and Pyramid (for API
documentation generation) installed.
-3. Open the ``docs/_build/html/index.html`` file to see the resulting HTML
+4. Open the ``docs/_build/html/index.html`` file to see the resulting HTML
rendering.
Change Log
diff --git a/docs/api/config.rst b/docs/api/config.rst
index cd58e74d3..028a55d4b 100644
--- a/docs/api/config.rst
+++ b/docs/api/config.rst
@@ -36,9 +36,11 @@
.. automethod:: set_authentication_policy
.. automethod:: set_authorization_policy
.. automethod:: set_default_permission
+ .. automethod:: add_permission
- :methodcategory:`Setting Request Properties`
+ :methodcategory:`Extending the Request Object`
+ .. automethod:: set_request_method
.. automethod:: set_request_property
:methodcategory:`Using I18N`
@@ -66,6 +68,8 @@
.. automethod:: add_response_adapter
.. automethod:: add_traverser
.. automethod:: add_tween
+ .. automethod:: add_route_predicate
+ .. automethod:: add_view_predicate
.. automethod:: set_request_factory
.. automethod:: set_root_factory
.. automethod:: set_session_factory
diff --git a/docs/api/registry.rst b/docs/api/registry.rst
index e62e2ba6f..1d5d52248 100644
--- a/docs/api/registry.rst
+++ b/docs/api/registry.rst
@@ -38,3 +38,17 @@
This class is new as of :app:`Pyramid` 1.3.
+.. autoclass:: Deferred
+
+ This class is new as of :app:`Pyramid` 1.4.
+
+.. autofunction:: undefer
+
+ This function is new as of :app:`Pyramid` 1.4.
+
+.. autoclass:: predvalseq
+
+ This class is new as of :app:`Pyramid` 1.4.
+
+
+
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 45a79326f..ba3203f89 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -994,3 +994,9 @@ Glossary
Aka ``gunicorn``, a fast :term:`WSGI` server that runs on UNIX under
Python 2.5+ (although at the time of this writing does not support
Python 3). See http://gunicorn.org/ for detailed information.
+
+ predicate factory
+ A callable which is used by a third party during the registration of a
+ route or view predicates to extend the view and route configuration
+ system. See :ref:`registering_thirdparty_predicates` for more
+ information.
diff --git a/docs/index.rst b/docs/index.rst
index 31c2fde6b..c84314274 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -13,15 +13,9 @@ Here is one of the simplest :app:`Pyramid` applications you can make:
.. literalinclude:: narr/helloworld.py
-When saved to ``helloworld.py``, the above application can be run via:
-
-.. code-block:: text
-
- $ easy_install pyramid
- $ python helloworld.py
-
-When you visit ``http://localhost:8080/hello/world`` in a browser, you will
-see the text ``Hello, world!``.
+After you install :app:`Pyramid` and run this application, when you visit
+``http://localhost:8080/hello/world`` in a browser, you will see the text
+``Hello, world!``
See :ref:`firstapp_chapter` for a full explanation of how this application
works. Read the :ref:`html_narrative_documentation` to understand how
diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst
index 2949dc808..b1ea652d6 100644
--- a/docs/narr/advconfig.rst
+++ b/docs/narr/advconfig.rst
@@ -296,6 +296,7 @@ These are the methods of the configurator which provide conflict detection:
:meth:`~pyramid.config.Configurator.add_renderer`,
:meth:`~pyramid.config.Configurator.set_request_factory`,
:meth:`~pyramid.config.Configurator.set_session_factory`,
+:meth:`~pyramid.config.Configurator.set_request_method`,
:meth:`~pyramid.config.Configurator.set_request_property`,
:meth:`~pyramid.config.Configurator.set_root_factory`,
:meth:`~pyramid.config.Configurator.set_view_mapper`,
diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst
index af53c1f78..3bdf8c5cd 100644
--- a/docs/narr/commandline.rst
+++ b/docs/narr/commandline.rst
@@ -349,7 +349,7 @@ setting) orderings using the ``ptweens`` command. Tween factories
will show up represented by their standard Python dotted name in the
``ptweens`` output.
-For example, here's the ``pwteens`` command run against a system
+For example, here's the ``ptweens`` command run against a system
configured without any explicit tweens:
.. code-block:: text
@@ -367,7 +367,7 @@ configured without any explicit tweens:
1 pyramid.tweens.excview_tween_factory excview
- - MAIN
-Here's the ``pwteens`` command run against a system configured *with*
+Here's the ``ptweens`` command run against a system configured *with*
explicit tweens defined in its ``development.ini`` file:
.. code-block:: text
diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst
index 1ca188d7e..ccaa6e9e2 100644
--- a/docs/narr/firstapp.rst
+++ b/docs/narr/firstapp.rst
@@ -8,7 +8,8 @@ Creating Your First :app:`Pyramid` Application
In this chapter, we will walk through the creation of a tiny :app:`Pyramid`
application. After we're finished creating the application, we'll explain in
-more detail how it works.
+more detail how it works. It assumes you already have :app:`Pyramid` installed.
+If you do not, head over to the :ref:`installing_chapter` section.
.. _helloworld_imperative:
@@ -126,7 +127,7 @@ defined imports and function definitions, placed within the confines of an
.. literalinclude:: helloworld.py
:linenos:
- :lines: 8-13
+ :lines: 9-15
Let's break this down piece-by-piece.
@@ -135,7 +136,7 @@ Configurator Construction
.. literalinclude:: helloworld.py
:linenos:
- :lines: 8-9
+ :lines: 9-10
The ``if __name__ == '__main__':`` line in the code sample above represents a
Python idiom: the code inside this if clause is not invoked unless the script
@@ -168,7 +169,7 @@ Adding Configuration
.. ignore-next-block
.. literalinclude:: helloworld.py
:linenos:
- :lines: 10-11
+ :lines: 11-12
First line above calls the :meth:`pyramid.config.Configurator.add_route`
method, which registers a :term:`route` to match any URL path that begins
@@ -188,7 +189,7 @@ WSGI Application Creation
.. ignore-next-block
.. literalinclude:: helloworld.py
:linenos:
- :lines: 12
+ :lines: 13
After configuring views and ending configuration, the script creates a WSGI
*application* via the :meth:`pyramid.config.Configurator.make_wsgi_app`
@@ -217,7 +218,7 @@ WSGI Application Serving
.. ignore-next-block
.. literalinclude:: helloworld.py
:linenos:
- :lines: 13
+ :lines: 14-15
Finally, we actually serve the application to requestors by starting up a
WSGI server. We happen to use the :mod:`wsgiref` ``make_server`` server
diff --git a/docs/narr/helloworld.py b/docs/narr/helloworld.py
index 7c26c8cdc..c01329af9 100644
--- a/docs/narr/helloworld.py
+++ b/docs/narr/helloworld.py
@@ -2,14 +2,15 @@ from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
+
def hello_world(request):
- return Response('Hello %(name)s!' % request.matchdict)
+ return Response('Hello %(name)s!' % request.matchdict)
if __name__ == '__main__':
- config = Configurator()
- config.add_route('hello', '/hello/{name}')
- config.add_view(hello_world, route_name='hello')
- app = config.make_wsgi_app()
- server = make_server('0.0.0.0', 8080, app)
- server.serve_forever()
+ config = Configurator()
+ config.add_route('hello', '/hello/{name}')
+ config.add_view(hello_world, route_name='hello')
+ app = config.make_wsgi_app()
+ server = make_server('0.0.0.0', 8080, app)
+ server.serve_forever()
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 332805152..2c15cd690 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -1232,3 +1232,101 @@ Displaying Tween Ordering
The ``ptweens`` command-line utility can be used to report the current
implict and explicit tween chains used by an application. See
:ref:`displaying_tweens`.
+
+.. _registering_thirdparty_predicates:
+
+Adding A Third Party View or Route Predicate
+--------------------------------------------
+
+.. note::
+
+ Third-party predicates are a feature new as of Pyramid 1.4.
+
+View and route predicates used during view configuration allow you to narrow
+the set of circumstances under which a view or route will match. For
+example, the ``request_method`` view predicate can be used to ensure a view
+callable is only invoked when the request's method is ``POST``:
+
+.. code-block:: python
+
+ @view_config(request_method='POST')
+ def someview(request):
+ ...
+
+Likewise, a similar predicate can be used as a *route* predicate:
+
+.. code-block:: python
+
+ config.add_route('name', '/foo', request_method='POST')
+
+Many other built-in predicates exists (``request_param``, and others). You
+can add third-party predicates to the list of available predicates by using
+one of :meth:`pyramid.config.Configurator.add_view_predicate` or
+:meth:`pyramid.config.Configurator.add_route_predicate`. The former adds a
+view predicate, the latter a route predicate.
+
+When using one of those APIs, you pass a *name* and a *factory* to add a
+predicate during Pyramid's configuration stage. For example:
+
+.. code-block:: python
+
+ config.add_view_predicate('content_type', ContentTypePredicate)
+
+The above example adds a new predicate named ``content_type`` to the list of
+available predicates for views. This will allow the following view
+configuration statement to work:
+
+.. code-block:: python
+ :linenos:
+
+ @view_config(content_type='File')
+ def aview(request): ...
+
+The first argument to :meth:`pyramid.config.Configurator.add_view_predicate`,
+the name, is a string representing the name that is expected to be passed to
+``view_config`` (or its imperative analogue ``add_view``).
+
+The second argument is a predicate factory. A predicate factory is most
+often a class with a constructor (``__init__``), a ``text`` method, a
+``phash`` method and a ``__call__`` method. For example:
+
+.. code-block:: python
+ :linenos:
+
+ class ContentTypePredicate(object):
+ def __init__(self, val, config):
+ self.val = val
+
+ def text(self):
+ return 'content_type = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ return getattr(context, 'content_type', None) == self.val
+
+The constructor of a predicate factory takes two arguments: ``val`` and
+``config``. The ``val`` argument will be the argument passed to
+``view_config`` (or ``add_view``). In the example above, it will be the
+string ``File``. The second arg, ``config`` will be the Configurator
+instance at the time of configuration.
+
+The ``text`` method must return a string. It should be useful to describe
+the behavior of the predicate in error messages.
+
+The ``phash`` method must return a string or a sequence of strings. It's
+most often the same as ``text``, as long as ``text`` uniquely describes the
+predicate's name and the value passed to the constructor. If ``text`` is
+more general, or doesn't describe things that way, ``phash`` should return a
+string with the name and the value serialized. The result of ``phash`` is
+not seen in output anywhere, it just informs the uniqueness constraints for
+view configuration.
+
+The ``__call__`` method of a predicate factory must accept a resource
+(``context``) and a request, and must return ``True`` or ``False``. It is
+the "meat" of the predicate.
+
+You can use the same predicate factory as both a view predicate and as a
+route predicate, but you'll need to call ``add_view_predicate`` and
+``add_route_predicate`` separately with the same factory.
+
diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst
index b5fa6a9f7..7c0f9223f 100644
--- a/docs/narr/introduction.rst
+++ b/docs/narr/introduction.rst
@@ -803,7 +803,7 @@ within a function called when another user uses the
See also :ref:`add_directive`.
Programmatic Introspection
---------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~
If you're building a large system that other users may plug code into, it's
useful to be able to get an enumeration of what code they plugged in *at
@@ -831,7 +831,7 @@ callable:
See also :ref:`using_introspection`.
Python 3 Compatibility
-----------------------
+~~~~~~~~~~~~~~~~~~~~~~
Pyramid and most of its add-ons are Python 3 compatible. If you develop a
Pyramid application today, you won't need to worry that five years from now
diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst
index 6ff9e3dea..1aa1b6341 100644
--- a/docs/narr/sessions.rst
+++ b/docs/narr/sessions.rst
@@ -151,13 +151,12 @@ Using Alternate Session Factories
---------------------------------
At the time of this writing, exactly one alternate session factory
-implementation exists, named ``pyramid_beaker``. This is a session
-factory that uses the `Beaker <http://beaker.groovie.org/>`_ library
-as a backend. Beaker has support for file-based sessions, database
-based sessions, and encrypted cookie-based sessions. See
-`http://github.com/Pylons/pyramid_beaker
-<http://github.com/Pylons/pyramid_beaker>`_ for more information about
-``pyramid_beaker``.
+implementation exists, named ``pyramid_beaker``. This is a session factory
+that uses the `Beaker <http://beaker.groovie.org/>`_ library as a backend.
+Beaker has support for file-based sessions, database based sessions, and
+encrypted cookie-based sessions. See `the pyramid_beaker documentation
+<http://docs.pylonsproject.org/projects/pyramid_beaker/en/latest/>`_ for more
+information about ``pyramid_beaker``.
.. index::
single: session factory (custom)
diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst
index 9db0b1c4d..860010a1a 100644
--- a/docs/narr/templates.rst
+++ b/docs/narr/templates.rst
@@ -714,6 +714,22 @@ This template doesn't use any advanced features of Mako, only the
:term:`renderer globals`. See the `the Mako documentation
<http://www.makotemplates.org/>`_ to use more advanced features.
+Using def inside Mako Templates
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To use a def inside a Mako template, given a :term:`Mako` template file named
+``foo.mak`` and a def named ``bar``, you can configure the template as a
+:term:`renderer` like so:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view import view_config
+
+ @view_config(renderer='foo#bar.mak')
+ def my_view(request):
+ return {'project':'my project'}
+
.. index::
single: automatic reloading of templates
single: template automatic reload
diff --git a/docs/narr/views.rst b/docs/narr/views.rst
index f6ee9a8d5..9e41464a6 100644
--- a/docs/narr/views.rst
+++ b/docs/narr/views.rst
@@ -177,7 +177,7 @@ HTTP Exceptions
~~~~~~~~~~~~~~~
All classes documented in the :mod:`pyramid.httpexceptions` module documented
-as inheriting from the :class:`pryamid.httpexceptions.HTTPException` are
+as inheriting from the :class:`pyramid.httpexceptions.HTTPException` are
:term:`http exception` objects. Instances of an HTTP exception object may
either be *returned* or *raised* from within view code. In either case
(return or raise) the instance will be used as as the view's response.
diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst
index e070f8eda..d11167344 100644
--- a/docs/tutorials/modwsgi/index.rst
+++ b/docs/tutorials/modwsgi/index.rst
@@ -73,9 +73,10 @@ commands and files.
.. code-block:: python
- from pyramid.paster import get_app
- application = get_app(
- '/Users/chrism/modwsgi/env/myapp/production.ini', 'main')
+ from pyramid.paster import get_app, setup_logging
+ ini_path = '/Users/chrism/modwsgi/env/myapp/production.ini'
+ setup_logging(ini_path)
+ application = get_app(ini_path, 'main')
The first argument to ``get_app`` is the project configuration file
name. It's best to use the ``production.ini`` file provided by your
@@ -85,6 +86,10 @@ commands and files.
``application`` is important: mod_wsgi requires finding such an
assignment when it opens the file.
+ The call to ``setup_logging`` initializes the standard library's
+ `logging` module to allow logging within your application.
+ See :ref:`logging_config`.
+
#. Make the ``pyramid.wsgi`` script executable.
.. code-block:: text
diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst
index 28cecb787..529603546 100644
--- a/docs/tutorials/wiki/definingviews.rst
+++ b/docs/tutorials/wiki/definingviews.rst
@@ -251,7 +251,7 @@ wiki page. It includes:
- A ``div`` element that is replaced with the ``content``
value provided by the view (rows 45-47). ``content``
contains HTML, so the ``structure`` keyword is used
- to prevent escaping it (i.e. changing ">" to &gt;, etc.)
+ to prevent escaping it (i.e. changing ">" to "&gt;", etc.)
- A link that points
at the "edit" URL which invokes the ``edit_page`` view for
the page being viewed (rows 49-51).
diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst
index 2ef55d15b..d7bd24a53 100644
--- a/docs/tutorials/wiki2/authorization.rst
+++ b/docs/tutorials/wiki2/authorization.rst
@@ -353,7 +353,7 @@ when we're done:
.. literalinclude:: src/authorization/tutorial/views.py
:linenos:
- :emphasize-lines: 11,14-18,31,37,58,61,73,76,88,91-117,119-123
+ :emphasize-lines: 11,14-18,25,31,37,58,61,73,76,88,91-117,119-123
:language: python
(Only the highlighted lines need to be added.)
diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst
index 5f4ea671c..b3184c4fc 100644
--- a/docs/tutorials/wiki2/basiclayout.rst
+++ b/docs/tutorials/wiki2/basiclayout.rst
@@ -100,7 +100,7 @@ used when the URL is ``/``:
:language: py
Since this route has a ``pattern`` equalling ``/`` it is the route that will
-be matched when the URL ``/`` is visted, e.g. ``http://localhost:6543/``.
+be matched when the URL ``/`` is visited, e.g. ``http://localhost:6543/``.
``main`` next calls the ``scan`` method of the configurator, which will
recursively scan our ``tutorial`` package, looking for ``@view_config`` (and
@@ -190,7 +190,7 @@ Next we set up a SQLAlchemy "DBSession" object:
``scoped_session`` allows us to access our database connection globally.
``sessionmaker`` creates a database session object. We pass to
``sessionmaker`` the ``extension=ZopeTransactionExtension()`` extension
-option in order to allow the system to automatically manage datbase
+option in order to allow the system to automatically manage database
transactions. With ``ZopeTransactionExtension`` activated, our application
will automatically issue a transaction commit after every request unless an
exception is raised, in which case the transaction will be aborted.
diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst
index efb72230e..24ac4338d 100644
--- a/docs/tutorials/wiki2/definingviews.rst
+++ b/docs/tutorials/wiki2/definingviews.rst
@@ -248,7 +248,7 @@ wiki page. It includes:
- A ``div`` element that is replaced with the ``content``
value provided by the view (rows 45-47). ``content``
contains HTML, so the ``structure`` keyword is used
- to prevent escaping it (i.e. changing ">" to &gt;, etc.)
+ to prevent escaping it (i.e. changing ">" to "&gt;", etc.)
- A link that points
at the "edit" URL which invokes the ``edit_page`` view for
the page being viewed (rows 49-51).
diff --git a/docs/tutorials/wiki2/design.rst b/docs/tutorials/wiki2/design.rst
index 2e6fc0e77..deaf32ef6 100644
--- a/docs/tutorials/wiki2/design.rst
+++ b/docs/tutorials/wiki2/design.rst
@@ -20,7 +20,7 @@ Models
We'll be using a SQLite database to hold our wiki data, and we'll be using
:term:`SQLAlchemy` to access the data in this database.
-Within the database, we define a single table named `tables`, whose elements
+Within the database, we define a single table named `pages`, whose elements
will store the wiki pages. There are two columns: `name` and `data`.
URLs like ``/PageName`` will try to find an element in
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py
index 52d7aca83..6010740ca 100644
--- a/pyramid/config/__init__.py
+++ b/pyramid/config/__init__.py
@@ -1,9 +1,9 @@
import inspect
+import itertools
import logging
import operator
import os
import sys
-import types
import warnings
import venusian
@@ -22,7 +22,6 @@ from pyramid.compat import (
text_,
reraise,
string_types,
- PY3,
)
from pyramid.events import ApplicationCreated
@@ -44,6 +43,7 @@ from pyramid.registry import (
Introspectable,
Introspector,
Registry,
+ undefer,
)
from pyramid.router import Router
@@ -353,6 +353,9 @@ class Configurator(
for name, renderer in DEFAULT_RENDERERS:
self.add_renderer(name, renderer)
+ self.add_default_view_predicates()
+ self.add_default_route_predicates()
+
if exceptionresponse_view is not None:
exceptionresponse_view = self.maybe_dotted(exceptionresponse_view)
self.add_view(exceptionresponse_view, context=IExceptionResponse)
@@ -547,6 +550,9 @@ class Configurator(
introspectables = ()
if autocommit:
+ # callables can depend on the side effects of resolving a
+ # deferred discriminator
+ undefer(discriminator)
if callable is not None:
callable(*args, **kw)
for introspectable in introspectables:
@@ -776,10 +782,9 @@ class Configurator(
c, action_wrap = c
if action_wrap:
c = action_method(c)
- if PY3: # pragma: no cover
- m = types.MethodType(c, self)
- else:
- m = types.MethodType(c, self, self.__class__)
+ # Create a bound method (works on both Py2 and Py3)
+ # http://stackoverflow.com/a/1015405/209039
+ m = c.__get__(self, self.__class__)
return m
def with_package(self, package):
@@ -1070,73 +1075,96 @@ def resolveConflicts(actions):
other conflicting actions.
"""
- # organize actions by discriminators
- unique = {}
- output = []
- for i, action in enumerate(actions):
- if not isinstance(action, dict):
+ def orderandpos(v):
+ n, v = v
+ if not isinstance(v, dict):
+ # old-style tuple action
+ v = expand_action(*v)
+ return (v['order'] or 0, n)
+
+ sactions = sorted(enumerate(actions), key=orderandpos)
+
+ def orderonly(v):
+ n, v = v
+ if not isinstance(v, dict):
# old-style tuple action
- action = expand_action(*action)
+ v = expand_action(*v)
+ return v['order'] or 0
+ for order, actiongroup in itertools.groupby(sactions, orderonly):
# "order" is an integer grouping. Actions in a lower order will be
- # executed before actions in a higher order. Within an order,
- # actions are executed sequentially based on original action ordering
- # ("i").
- order = action['order'] or 0
- discriminator = action['discriminator']
-
- # "ainfo" is a tuple of (order, i, action) where "order" is a
- # user-supplied grouping, "i" is an integer expressing the relative
- # position of this action in the action list being resolved, and
- # "action" is an action dictionary. The purpose of an ainfo is to
- # associate an "order" and an "i" with a particular action; "order"
- # and "i" exist for sorting purposes after conflict resolution.
- ainfo = (order, i, action)
-
- if discriminator is None:
- # The discriminator is None, so this action can never conflict.
- # We can add it directly to the result.
+ # executed before actions in a higher order. All of the actions in
+ # one grouping will be executed (its callable, if any will be called)
+ # before any of the actions in the next.
+
+ unique = {}
+ output = []
+
+ for i, action in actiongroup:
+ # Within an order, actions are executed sequentially based on
+ # original action ordering ("i").
+
+ if not isinstance(action, dict):
+ # old-style tuple action
+ action = expand_action(*action)
+
+ # "ainfo" is a tuple of (order, i, action) where "order" is a
+ # user-supplied grouping, "i" is an integer expressing the relative
+ # position of this action in the action list being resolved, and
+ # "action" is an action dictionary. The purpose of an ainfo is to
+ # associate an "order" and an "i" with a particular action; "order"
+ # and "i" exist for sorting purposes after conflict resolution.
+ ainfo = (order, i, action)
+
+ discriminator = undefer(action['discriminator'])
+ action['discriminator'] = discriminator
+
+ if discriminator is None:
+ # The discriminator is None, so this action can never conflict.
+ # We can add it directly to the result.
+ output.append(ainfo)
+ continue
+
+ L = unique.setdefault(discriminator, [])
+ L.append(ainfo)
+
+ # Check for conflicts
+ conflicts = {}
+
+ for discriminator, ainfos in unique.items():
+ # We use (includepath, order, i) as a sort key because we need to
+ # sort the actions by the paths so that the shortest path with a
+ # given prefix comes first. The "first" action is the one with the
+ # shortest include path. We break sorting ties using "order", then
+ # "i".
+ def bypath(ainfo):
+ path, order, i = ainfo[2]['includepath'], ainfo[0], ainfo[1]
+ return path, order, i
+
+ ainfos.sort(key=bypath)
+ ainfo, rest = ainfos[0], ainfos[1:]
output.append(ainfo)
- continue
-
- L = unique.setdefault(discriminator, [])
- L.append(ainfo)
-
- # Check for conflicts
- conflicts = {}
-
- for discriminator, ainfos in unique.items():
-
- # We use (includepath, order, i) as a sort key because we need to
- # sort the actions by the paths so that the shortest path with a
- # given prefix comes first. The "first" action is the one with the
- # shortest include path. We break sorting ties using "order", then
- # "i".
- def bypath(ainfo):
- path, order, i = ainfo[2]['includepath'], ainfo[0], ainfo[1]
- return path, order, i
-
- ainfos.sort(key=bypath)
- ainfo, rest = ainfos[0], ainfos[1:]
- output.append(ainfo)
- _, _, action = ainfo
- basepath, baseinfo, discriminator = (action['includepath'],
- action['info'],
- action['discriminator'])
-
- for _, _, action in rest:
- includepath = action['includepath']
- # Test whether path is a prefix of opath
- if (includepath[:len(basepath)] != basepath # not a prefix
- or includepath == basepath):
- L = conflicts.setdefault(discriminator, [baseinfo])
- L.append(action['info'])
-
- if conflicts:
- raise ConfigurationConflictError(conflicts)
-
- # sort conflict-resolved actions by (order, i) and return them
- return [ x[2] for x in sorted(output, key=operator.itemgetter(0, 1))]
+ _, _, action = ainfo
+ basepath, baseinfo, discriminator = (
+ action['includepath'],
+ action['info'],
+ action['discriminator'],
+ )
+
+ for _, _, action in rest:
+ includepath = action['includepath']
+ # Test whether path is a prefix of opath
+ if (includepath[:len(basepath)] != basepath # not a prefix
+ or includepath == basepath):
+ L = conflicts.setdefault(discriminator, [baseinfo])
+ L.append(action['info'])
+
+ if conflicts:
+ raise ConfigurationConflictError(conflicts)
+
+ # sort conflict-resolved actions by (order, i) and yield them one by one
+ for a in [x[2] for x in sorted(output, key=operator.itemgetter(0, 1))]:
+ yield a
def expand_action(discriminator, callable=None, args=(), kw=None,
includepath=(), info=None, order=0, introspectables=()):
diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py
index ccbf3bbe9..e8043ed28 100644
--- a/pyramid/config/factories.py
+++ b/pyramid/config/factories.py
@@ -1,10 +1,13 @@
+from zope.interface import implementer
+
+from pyramid.compat import iteritems_
from pyramid.config.util import action_method
from pyramid.interfaces import (
IDefaultRootFactory,
INewRequest,
IRequestFactory,
- IRequestProperties,
+ IRequestExtensions,
IRootFactory,
ISessionFactory,
)
@@ -93,55 +96,115 @@ class FactoriesConfiguratorMixin(object):
self.action(IRequestFactory, register, introspectables=(intr,))
@action_method
- def set_request_property(self, callable, name=None, reify=False):
- """ Add a property to the request object.
+ def set_request_method(self,
+ callable=None,
+ name=None,
+ property=False,
+ reify=False):
+ """ Add a property or method to the request object.
+
+ When adding a method to the request, ``callable`` may be any
+ function that receives the request object as the first
+ parameter. If ``name`` is ``None`` then it will be computed
+ from the name of the ``callable``.
- ``callable`` can either be a callable that accepts the request
- as its single positional parameter, or it can be a property
- descriptor. It may also be a :term:`dotted Python name` which
- refers to either a callable or a property descriptor.
+ When adding a property to the request, ``callable`` can either
+ be a callable that accepts the request as its single positional
+ parameter, or it can be a property descriptor. If ``name`` is
+ ``None``, the name of the property will be computed from the
+ name of the ``callable``.
If the ``callable`` is a property descriptor a ``ValueError``
will be raised if ``name`` is ``None`` or ``reify`` is ``True``.
- If ``name`` is None, the name of the property will be computed
- from the name of the ``callable``.
-
See :meth:`pyramid.request.Request.set_property` for more
- information on its usage.
+ details on ``property`` vs ``reify``. When ``reify`` is
+ ``True``, the value of ``property`` is assumed to also be
+ ``True``.
+
+ In all cases, ``callable`` may also be a
+ :term:`dotted Python name` which refers to either a callable or
+ a property descriptor.
+
+ If ``callable`` is ``None`` then the method is only used to
+ assist in conflict detection between different addons requesting
+ the same attribute on the request object.
This is the recommended method for extending the request object
and should be used in favor of providing a custom request
factory via
:meth:`pyramid.config.Configurator.set_request_factory`.
- .. versionadded:: 1.3
+ .. versionadded:: 1.4
"""
- callable = self.maybe_dotted(callable)
+ if callable is not None:
+ callable = self.maybe_dotted(callable)
- name, callable = InstancePropertyMixin._make_property(
- callable, name=name, reify=reify)
+ property = property or reify
+ if property:
+ name, callable = InstancePropertyMixin._make_property(
+ callable, name=name, reify=reify)
+ elif name is None:
+ name = callable.__name__
def register():
- plist = self.registry.queryUtility(IRequestProperties)
+ exts = self.registry.queryUtility(IRequestExtensions)
- if plist is None:
- plist = []
- self.registry.registerUtility(plist, IRequestProperties)
- self.registry.registerHandler(_set_request_properties,
+ if exts is None:
+ exts = _RequestExtensions()
+ self.registry.registerUtility(exts, IRequestExtensions)
+ self.registry.registerHandler(_set_request_extensions,
(INewRequest,))
- plist.append((name, callable))
+ plist = exts.descriptors if property else exts.methods
+ plist[name] = callable
+
+ if callable is None:
+ self.action(('request extensions', name), None)
+ elif property:
+ intr = self.introspectable('request extensions', name,
+ self.object_description(callable),
+ 'request property')
+ intr['callable'] = callable
+ intr['property'] = True
+ intr['reify'] = reify
+ self.action(('request extensions', name), register,
+ introspectables=(intr,))
+ else:
+ intr = self.introspectable('request extensions', name,
+ self.object_description(callable),
+ 'request method')
+ intr['callable'] = callable
+ intr['property'] = False
+ intr['reify'] = False
+ self.action(('request extensions', name), register,
+ introspectables=(intr,))
+
+ @action_method
+ def set_request_property(self, callable, name=None, reify=False):
+ """ Add a property to the request object.
+
+ .. warning::
+
+ This method has been deprecated as of Pyramid 1.4.
+ :meth:`pyramid.config.Configurator.set_request_method` should be
+ used instead.
+
+ .. versionadded:: 1.3
+ """
+ self.set_request_method(
+ callable, name=name, property=not reify, reify=reify)
- intr = self.introspectable('request properties', name,
- self.object_description(callable),
- 'request property')
- intr['callable'] = callable
- intr['reify'] = reify
- self.action(('request properties', name), register,
- introspectables=(intr,))
+@implementer(IRequestExtensions)
+class _RequestExtensions(object):
+ def __init__(self):
+ self.descriptors = {}
+ self.methods = {}
-def _set_request_properties(event):
+def _set_request_extensions(event):
request = event.request
- plist = request.registry.queryUtility(IRequestProperties)
- request._set_properties(plist)
+ exts = request.registry.queryUtility(IRequestExtensions)
+ for name, fn in iteritems_(exts.methods):
+ method = fn.__get__(request, request.__class__)
+ setattr(request, name, method)
+ request._set_properties(exts.descriptors)
diff --git a/pyramid/config/predicates.py b/pyramid/config/predicates.py
new file mode 100644
index 000000000..9e0ee28c1
--- /dev/null
+++ b/pyramid/config/predicates.py
@@ -0,0 +1,228 @@
+import re
+
+from pyramid.compat import is_nonstr_iter
+
+from pyramid.exceptions import ConfigurationError
+
+from pyramid.traversal import (
+ find_interface,
+ traversal_path,
+ )
+
+from pyramid.urldispatch import _compile_route
+
+from pyramid.util import object_description
+
+from .util import as_sorted_tuple
+
+class XHRPredicate(object):
+ def __init__(self, val, config):
+ self.val = bool(val)
+
+ def text(self):
+ return 'xhr = %s' % self.val
+
+ phash = text
+
+ def __call__(self, context, request):
+ return bool(request.is_xhr) is self.val
+
+class RequestMethodPredicate(object):
+ def __init__(self, val, config):
+ request_method = as_sorted_tuple(val)
+ if 'GET' in request_method and 'HEAD' not in request_method:
+ # GET implies HEAD too
+ request_method = as_sorted_tuple(request_method + ('HEAD',))
+ self.val = request_method
+
+ def text(self):
+ return 'request_method = %s' % (','.join(self.val))
+
+ phash = text
+
+ def __call__(self, context, request):
+ return request.method in self.val
+
+class PathInfoPredicate(object):
+ def __init__(self, val, config):
+ self.orig = val
+ try:
+ val = re.compile(val)
+ except re.error as why:
+ raise ConfigurationError(why.args[0])
+ self.val = val
+
+ def text(self):
+ return 'path_info = %s' % (self.orig,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ return self.val.match(request.upath_info) is not None
+
+class RequestParamPredicate(object):
+ def __init__(self, val, config):
+ name = val
+ v = None
+ if '=' in name:
+ name, v = name.split('=', 1)
+ name, v = name.strip(), v.strip()
+ if v is None:
+ self._text = 'request_param %s' % (name,)
+ else:
+ self._text = 'request_param %s = %s' % (name, v)
+ self.name = name
+ self.val = v
+
+ def text(self):
+ return self._text
+
+ phash = text
+
+ def __call__(self, context, request):
+ if self.val is None:
+ return self.name in request.params
+ return request.params.get(self.name) == self.val
+
+
+class HeaderPredicate(object):
+ def __init__(self, val, config):
+ name = val
+ v = None
+ if ':' in name:
+ name, v = name.split(':', 1)
+ try:
+ v = re.compile(v)
+ except re.error as why:
+ raise ConfigurationError(why.args[0])
+ if v is None:
+ self._text = 'header %s' % (name,)
+ else:
+ self._text = 'header %s = %s' % (name, v)
+ self.name = name
+ self.val = v
+
+ def text(self):
+ return self._text
+
+ phash = text
+
+ def __call__(self, context, request):
+ if self.val is None:
+ return self.name in request.headers
+ val = request.headers.get(self.name)
+ if val is None:
+ return False
+ return self.val.match(val) is not None
+
+class AcceptPredicate(object):
+ def __init__(self, val, config):
+ self.val = val
+
+ def text(self):
+ return 'accept = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ return self.val in request.accept
+
+class ContainmentPredicate(object):
+ def __init__(self, val, config):
+ self.val = config.maybe_dotted(val)
+
+ def text(self):
+ return 'containment = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ ctx = getattr(request, 'context', context)
+ return find_interface(ctx, self.val) is not None
+
+class RequestTypePredicate(object):
+ def __init__(self, val, config):
+ self.val = val
+
+ def text(self):
+ return 'request_type = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ return self.val.providedBy(request)
+
+class MatchParamPredicate(object):
+ def __init__(self, val, config):
+ if not is_nonstr_iter(val):
+ val = (val,)
+ val = sorted(val)
+ self.val = val
+ reqs = [ p.split('=', 1) for p in val ]
+ self.reqs = [ (x.strip(), y.strip()) for x, y in reqs ]
+
+ def text(self):
+ return 'match_param %s' % ','.join(
+ ['%s=%s' % (x,y) for x, y in self.reqs]
+ )
+
+ phash = text
+
+ def __call__(self, context, request):
+ for k, v in self.reqs:
+ if request.matchdict.get(k) != v:
+ return False
+ return True
+
+class CustomPredicate(object):
+ def __init__(self, func, config):
+ self.func = func
+
+ def text(self):
+ return getattr(
+ self.func,
+ '__text__',
+ 'custom predicate: %s' % object_description(self.func)
+ )
+
+ def phash(self):
+ # using hash() here rather than id() is intentional: we
+ # want to allow custom predicates that are part of
+ # frameworks to be able to define custom __hash__
+ # functions for custom predicates, so that the hash output
+ # of predicate instances which are "logically the same"
+ # may compare equal.
+ return 'custom:%r' % hash(self.func)
+
+ def __call__(self, context, request):
+ return self.func(context, request)
+
+
+class TraversePredicate(object):
+ # Can only be used as a *route* "predicate"; it adds 'traverse' to the
+ # matchdict if it's specified in the routing args. This causes the
+ # ResourceTreeTraverser to use the resolved traverse pattern as the
+ # traversal path.
+ def __init__(self, val, config):
+ _, self.tgenerate = _compile_route(val)
+ self.val = val
+
+ def text(self):
+ return 'traverse matchdict pseudo-predicate'
+
+ def phash(self):
+ # This isn't actually a predicate, it's just a infodict modifier that
+ # injects ``traverse`` into the matchdict. As a result, we don't
+ # need to update the hash.
+ return ''
+
+ def __call__(self, context, request):
+ if 'traverse' in context:
+ return True
+ m = context['match']
+ tvalue = self.tgenerate(m) # tvalue will be urlquoted string
+ m['traverse'] = traversal_path(tvalue)
+ # This isn't actually a predicate, it's just a infodict modifier that
+ # injects ``traverse`` into the matchdict. As a result, we just
+ # return True.
+ return True
diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py
index ea39b6805..18fe39e45 100644
--- a/pyramid/config/routes.py
+++ b/pyramid/config/routes.py
@@ -1,22 +1,27 @@
import warnings
from pyramid.interfaces import (
+ IPredicateList,
IRequest,
IRouteRequest,
IRoutesMapper,
+ PHASE1_CONFIG,
PHASE2_CONFIG,
)
from pyramid.exceptions import ConfigurationError
+from pyramid.registry import predvalseq
from pyramid.request import route_request_iface
from pyramid.urldispatch import RoutesMapper
from pyramid.config.util import (
action_method,
- make_predicates,
as_sorted_tuple,
+ PredicateList,
)
+import pyramid.config.predicates
+
class RoutesConfiguratorMixin(object):
@action_method
def add_route(self,
@@ -28,7 +33,7 @@ class RoutesConfiguratorMixin(object):
factory=None,
for_=None,
header=None,
- xhr=False,
+ xhr=None,
accept=None,
path_info=None,
request_method=None,
@@ -44,7 +49,7 @@ class RoutesConfiguratorMixin(object):
path=None,
pregenerator=None,
static=False,
- ):
+ **predicates):
""" Add a :term:`route configuration` to the current
configuration state, as well as possibly a :term:`view
configuration` to be used to specify a :term:`view callable`
@@ -254,6 +259,15 @@ class RoutesConfiguratorMixin(object):
:ref:`custom_route_predicates` for more information about
``info``.
+ predicates
+
+ Pass a key/value pair here to use a third-party predicate
+ registered via
+ :meth:`pyramid.config.Configurator.add_view_predicate`. More than
+ one key/value pair can be used at the same time. See
+ :ref:`registering_thirdparty_predicates` for more information about
+ third-party predicates. This argument is new as of Pyramid 1.4.
+
View-Related Arguments
.. warning::
@@ -351,17 +365,6 @@ class RoutesConfiguratorMixin(object):
if request_method is not None:
request_method = as_sorted_tuple(request_method)
- ignored, predicates, ignored = make_predicates(
- xhr=xhr,
- request_method=request_method,
- path_info=path_info,
- request_param=request_param,
- header=header,
- accept=accept,
- traverse=traverse,
- custom=custom_predicates
- )
-
factory = self.maybe_dotted(factory)
if pattern is None:
pattern = path
@@ -417,8 +420,24 @@ class RoutesConfiguratorMixin(object):
request_iface, IRouteRequest, name=name)
def register_connect():
+ pvals = predicates.copy()
+ pvals.update(
+ dict(
+ xhr=xhr,
+ request_method=request_method,
+ path_info=path_info,
+ request_param=request_param,
+ header=header,
+ accept=accept,
+ traverse=traverse,
+ custom=predvalseq(custom_predicates),
+ )
+ )
+
+ predlist = self.route_predlist
+ _, preds, _ = predlist.make(self, **pvals)
route = mapper.connect(
- name, pattern, factory, predicates=predicates,
+ name, pattern, factory, predicates=preds,
pregenerator=pregenerator, static=static
)
intr['object'] = route
@@ -447,6 +466,66 @@ class RoutesConfiguratorMixin(object):
attr=view_attr,
)
+ @property
+ def route_predlist(self):
+ predlist = self.registry.queryUtility(IPredicateList, name='route')
+ if predlist is None:
+ predlist = PredicateList()
+ self.registry.registerUtility(predlist, IPredicateList,
+ name='route')
+ return predlist
+
+ @action_method
+ def add_route_predicate(self, name, factory, weighs_more_than=None,
+ weighs_less_than=None):
+ """ Adds a route predicate factory. The view predicate can later be
+ named as a keyword argument to
+ :meth:`pyramid.config.Configurator.add_route`.
+
+ ``name`` should be the name of the predicate. It must be a valid
+ Python identifier (it will be used as a keyword argument to
+ ``add_view``).
+
+ ``factory`` should be a :term:`predicate factory`.
+
+ See :ref:`registering_thirdparty_predicates` for more information.
+
+ .. note::
+
+ This method is new as of Pyramid 1.4.
+ """
+ discriminator = ('route predicate', name)
+ intr = self.introspectable(
+ 'route predicates',
+ discriminator,
+ 'route predicate named %s' % name,
+ 'route predicate')
+ intr['name'] = name
+ intr['factory'] = factory
+ intr['weighs_more_than'] = weighs_more_than
+ intr['weighs_less_than'] = weighs_less_than
+ def register():
+ predlist = self.route_predlist
+ predlist.add(name, factory, weighs_more_than=weighs_more_than,
+ weighs_less_than=weighs_less_than)
+ # must be registered before routes connected
+ self.action(discriminator, register, introspectables=(intr,),
+ order=PHASE1_CONFIG)
+
+ def add_default_route_predicates(self):
+ p = pyramid.config.predicates
+ for (name, factory) in (
+ ('xhr', p.XHRPredicate),
+ ('request_method', p.RequestMethodPredicate),
+ ('path_info', p.PathInfoPredicate),
+ ('request_param', p.RequestParamPredicate),
+ ('header', p.HeaderPredicate),
+ ('accept', p.AcceptPredicate),
+ ('custom', p.CustomPredicate),
+ ('traverse', p.TraversePredicate),
+ ):
+ self.add_route_predicate(name, factory)
+
def get_routes_mapper(self):
""" Return the :term:`routes mapper` object associated with
this configurator's :term:`registry`."""
diff --git a/pyramid/config/security.py b/pyramid/config/security.py
index e8ef1518d..567999cc4 100644
--- a/pyramid/config/security.py
+++ b/pyramid/config/security.py
@@ -137,3 +137,23 @@ class SecurityConfiguratorMixin(object):
introspectables=(intr, perm_intr,))
+ def add_permission(self, permission_name):
+ """
+ A configurator directive which registers a free-standing
+ permission without associating it with a view callable. This can be
+ used so that the permission shows up in the introspectable data under
+ the ``permissions`` category (permissions mentioned via ``add_view``
+ already end up in there). For example::
+
+ config = Configurator()
+ config.add_permission('view')
+ """
+ intr = self.introspectable(
+ 'permissions',
+ permission_name,
+ permission_name,
+ 'permission'
+ )
+ intr['value'] = permission_name
+ self.action(None, introspectables=(intr,))
+
diff --git a/pyramid/config/tweens.py b/pyramid/config/tweens.py
index 1a83f0de9..1bc6dc95c 100644
--- a/pyramid/config/tweens.py
+++ b/pyramid/config/tweens.py
@@ -16,7 +16,10 @@ from pyramid.tweens import (
EXCVIEW,
)
-from pyramid.config.util import action_method
+from pyramid.config.util import (
+ action_method,
+ TopologicalSorter,
+ )
class TweensConfiguratorMixin(object):
def add_tween(self, tween_factory, under=None, over=None):
@@ -177,119 +180,24 @@ class TweensConfiguratorMixin(object):
introspectables.append(intr)
self.action(discriminator, register, introspectables=introspectables)
-class CyclicDependencyError(Exception):
- def __init__(self, cycles):
- self.cycles = cycles
-
- def __str__(self):
- L = []
- cycles = self.cycles
- for cycle in cycles:
- dependent = cycle
- dependees = cycles[cycle]
- L.append('%r sorts over %r' % (dependent, dependees))
- msg = 'Implicit tween ordering cycle:' + '; '.join(L)
- return msg
-
@implementer(ITweens)
class Tweens(object):
def __init__(self):
+ self.sorter = TopologicalSorter(
+ default_before=None,
+ default_after=INGRESS,
+ first=INGRESS,
+ last=MAIN)
self.explicit = []
- self.names = []
- self.req_over = set()
- self.req_under = set()
- self.factories = {}
- self.order = []
def add_explicit(self, name, factory):
self.explicit.append((name, factory))
def add_implicit(self, name, factory, under=None, over=None):
- self.names.append(name)
- self.factories[name] = factory
- if under is None and over is None:
- under = INGRESS
- if under is not None:
- if not is_nonstr_iter(under):
- under = (under,)
- self.order += [(u, name) for u in under]
- self.req_under.add(name)
- if over is not None:
- if not is_nonstr_iter(over):
- over = (over,)
- self.order += [(name, o) for o in over]
- self.req_over.add(name)
+ self.sorter.add(name, factory, after=under, before=over)
def implicit(self):
- order = [(INGRESS, MAIN)]
- roots = []
- graph = {}
- names = [INGRESS, MAIN]
- names.extend(self.names)
-
- for a, b in self.order:
- order.append((a, b))
-
- def add_node(node):
- if not node in graph:
- roots.append(node)
- graph[node] = [0] # 0 = number of arcs coming into this node
-
- def add_arc(fromnode, tonode):
- graph[fromnode].append(tonode)
- graph[tonode][0] += 1
- if tonode in roots:
- roots.remove(tonode)
-
- for name in names:
- add_node(name)
-
- has_over, has_under = set(), set()
- for a, b in order:
- if a in names and b in names: # deal with missing dependencies
- add_arc(a, b)
- has_over.add(a)
- has_under.add(b)
-
- if not self.req_over.issubset(has_over):
- raise ConfigurationError(
- 'Detected tweens with no satisfied over dependencies: %s'
- % (', '.join(sorted(self.req_over - has_over)))
- )
- if not self.req_under.issubset(has_under):
- raise ConfigurationError(
- 'Detected tweens with no satisfied under dependencies: %s'
- % (', '.join(sorted(self.req_under - has_under)))
- )
-
- sorted_names = []
-
- while roots:
- root = roots.pop(0)
- sorted_names.append(root)
- children = graph[root][1:]
- for child in children:
- arcs = graph[child][0]
- arcs -= 1
- graph[child][0] = arcs
- if arcs == 0:
- roots.insert(0, child)
- del graph[root]
-
- if graph:
- # loop in input
- cycledeps = {}
- for k, v in graph.items():
- cycledeps[k] = v[1:]
- raise CyclicDependencyError(cycledeps)
-
- result = []
-
- for name in sorted_names:
- if name in self.names:
- result.append((name, self.factories[name]))
-
- return result
+ return self.sorter.sorted()
def __call__(self, handler, registry):
if self.explicit:
diff --git a/pyramid/config/util.py b/pyramid/config/util.py
index b8d0f2319..cabcab649 100644
--- a/pyramid/config/util.py
+++ b/pyramid/config/util.py
@@ -1,4 +1,3 @@
-import re
import traceback
from zope.interface import implementer
@@ -12,10 +11,7 @@ from pyramid.compat import (
from pyramid.exceptions import ConfigurationError
-from pyramid.traversal import (
- find_interface,
- traversal_path,
- )
+from pyramid.registry import predvalseq
from hashlib import md5
@@ -64,232 +60,239 @@ def action_method(wrapped):
wrapper.__docobj__ = wrapped # for sphinx
return wrapper
-def make_predicates(xhr=None, request_method=None, path_info=None,
- request_param=None, match_param=None, header=None,
- accept=None, containment=None, request_type=None,
- traverse=None, custom=()):
-
- # PREDICATES
- # ----------
- #
- # Given an argument list, a predicate list is computed.
- # Predicates are added to a predicate list in (presumed)
- # computation expense order. All predicates associated with a
- # view or route must evaluate true for the view or route to
- # "match" during a request. Elsewhere in the code, we evaluate
- # predicates using a generator expression. The fastest predicate
- # should be evaluated first, then the next fastest, and so on, as
- # if one returns false, the remainder of the predicates won't need
- # to be evaluated.
- #
- # While we compute predicates, we also compute a predicate hash
- # (aka phash) that can be used by a caller to identify identical
- # predicate lists.
- #
- # ORDERING
- # --------
- #
- # A "order" is computed for the predicate list. An order is
- # a scoring.
- #
- # Each predicate is associated with a weight value, which is a
- # multiple of 2. The weight of a predicate symbolizes the
- # relative potential "importance" of the predicate to all other
- # predicates. A larger weight indicates greater importance.
- #
- # All weights for a given predicate list are bitwise ORed together
- # to create a "score"; this score is then subtracted from
- # MAX_ORDER and divided by an integer representing the number of
- # predicates+1 to determine the order.
- #
- # The order represents the ordering in which a "multiview" ( a
- # collection of views that share the same context/request/name
- # triad but differ in other ways via predicates) will attempt to
- # call its set of views. Views with lower orders will be tried
- # first. The intent is to a) ensure that views with more
- # predicates are always evaluated before views with fewer
- # predicates and b) to ensure a stable call ordering of views that
- # share the same number of predicates. Views which do not have
- # any predicates get an order of MAX_ORDER, meaning that they will
- # be tried very last.
-
- predicates = []
- weights = []
- h = md5()
-
- if xhr:
- def xhr_predicate(context, request):
- return request.is_xhr
- xhr_predicate.__text__ = "xhr = True"
- weights.append(1 << 1)
- predicates.append(xhr_predicate)
- h.update(bytes_('xhr:%r' % bool(xhr)))
-
- if request_method is not None:
- if not is_nonstr_iter(request_method):
- request_method = (request_method,)
- request_method = sorted(request_method)
- def request_method_predicate(context, request):
- return request.method in request_method
- text = "request method = %r" % request_method
- request_method_predicate.__text__ = text
- weights.append(1 << 2)
- predicates.append(request_method_predicate)
- for m in request_method:
- h.update(bytes_('request_method:%r' % m))
-
- if path_info is not None:
- try:
- path_info_val = re.compile(path_info)
- except re.error as why:
- raise ConfigurationError(why.args[0])
- def path_info_predicate(context, request):
- return path_info_val.match(request.upath_info) is not None
- text = "path_info = %s"
- path_info_predicate.__text__ = text % path_info
- weights.append(1 << 3)
- predicates.append(path_info_predicate)
- h.update(bytes_('path_info:%r' % path_info))
-
- if request_param is not None:
- request_param_val = None
- if '=' in request_param:
- request_param, request_param_val = request_param.split('=', 1)
- if request_param_val is None:
- text = "request_param %s" % request_param
- else:
- text = "request_param %s = %s" % (request_param, request_param_val)
- def request_param_predicate(context, request):
- if request_param_val is None:
- return request_param in request.params
- return request.params.get(request_param) == request_param_val
- request_param_predicate.__text__ = text
- weights.append(1 << 4)
- predicates.append(request_param_predicate)
- h.update(
- bytes_('request_param:%r=%r' % (request_param, request_param_val)))
-
- if header is not None:
- header_name = header
- header_val = None
- if ':' in header:
- header_name, header_val = header.split(':', 1)
- try:
- header_val = re.compile(header_val)
- except re.error as why:
- raise ConfigurationError(why.args[0])
- if header_val is None:
- text = "header %s" % header_name
- else:
- text = "header %s = %s" % (header_name, header_val)
- def header_predicate(context, request):
- if header_val is None:
- return header_name in request.headers
- val = request.headers.get(header_name)
- if val is None:
- return False
- return header_val.match(val) is not None
- header_predicate.__text__ = text
- weights.append(1 << 5)
- predicates.append(header_predicate)
- h.update(bytes_('header:%r=%r' % (header_name, header_val)))
-
- if accept is not None:
- def accept_predicate(context, request):
- return accept in request.accept
- accept_predicate.__text__ = "accept = %s" % accept
- weights.append(1 << 6)
- predicates.append(accept_predicate)
- h.update(bytes_('accept:%r' % accept))
-
- if containment is not None:
- def containment_predicate(context, request):
- ctx = getattr(request, 'context', context)
- return find_interface(ctx, containment) is not None
- containment_predicate.__text__ = "containment = %s" % containment
- weights.append(1 << 7)
- predicates.append(containment_predicate)
- h.update(bytes_('containment:%r' % hash(containment)))
-
- if request_type is not None:
- def request_type_predicate(context, request):
- return request_type.providedBy(request)
- text = "request_type = %s"
- request_type_predicate.__text__ = text % request_type
- weights.append(1 << 8)
- predicates.append(request_type_predicate)
- h.update(bytes_('request_type:%r' % hash(request_type)))
-
- if match_param is not None:
- if not is_nonstr_iter(match_param):
- match_param = (match_param,)
- match_param = sorted(match_param)
- text = "match_param %s" % repr(match_param)
- reqs = [p.split('=', 1) for p in match_param]
- def match_param_predicate(context, request):
- for k, v in reqs:
- if request.matchdict.get(k) != v:
- return False
- return True
- match_param_predicate.__text__ = text
- weights.append(1 << 9)
- predicates.append(match_param_predicate)
- for p in match_param:
- h.update(bytes_('match_param:%r' % p))
-
- if custom:
- for num, predicate in enumerate(custom):
- if getattr(predicate, '__text__', None) is None:
- text = '<unknown custom predicate>'
- try:
- predicate.__text__ = text
- except AttributeError:
- # if this happens the predicate is probably a classmethod
- if hasattr(predicate, '__func__'):
- predicate.__func__.__text__ = text
- else: # pragma: no cover ; 2.5 doesn't have __func__
- predicate.im_func.__text__ = text
- predicates.append(predicate)
- # using hash() here rather than id() is intentional: we
- # want to allow custom predicates that are part of
- # frameworks to be able to define custom __hash__
- # functions for custom predicates, so that the hash output
- # of predicate instances which are "logically the same"
- # may compare equal.
- h.update(bytes_('custom%s:%r' % (num, hash(predicate))))
- weights.append(1 << 10)
-
- if traverse is not None:
- # ``traverse`` can only be used as a *route* "predicate"; it
- # adds 'traverse' to the matchdict if it's specified in the
- # routing args. This causes the ResourceTreeTraverser to use
- # the resolved traverse pattern as the traversal path.
- from pyramid.urldispatch import _compile_route
- _, tgenerate = _compile_route(traverse)
- def traverse_predicate(context, request):
- if 'traverse' in context:
- return True
- m = context['match']
- tvalue = tgenerate(m) # tvalue will be urlquoted string
- m['traverse'] = traversal_path(tvalue) # will be seq of unicode
- return True
- # This isn't actually a predicate, it's just a infodict
- # modifier that injects ``traverse`` into the matchdict. As a
- # result, the ``traverse_predicate`` function above always
- # returns True, and we don't need to update the hash or attach
- # a weight to it
- predicates.append(traverse_predicate)
-
- score = 0
- for bit in weights:
- score = score | bit
- order = (MAX_ORDER - score) / (len(predicates) + 1)
- phash = h.hexdigest()
- return order, predicates, phash
-
def as_sorted_tuple(val):
if not is_nonstr_iter(val):
val = (val,)
val = tuple(sorted(val))
return val
+# under = after
+# over = before
+
+class Singleton(object):
+ def __init__(self, repr):
+ self.repr = repr
+
+ def __repr__(self):
+ return self.repr
+
+FIRST = Singleton('FIRST')
+LAST = Singleton('LAST')
+
+class TopologicalSorter(object):
+ def __init__(
+ self,
+ default_before=LAST,
+ default_after=None,
+ first=FIRST,
+ last=LAST,
+ ):
+ self.names = []
+ self.req_before = set()
+ self.req_after = set()
+ self.name2before = {}
+ self.name2after = {}
+ self.name2val = {}
+ self.order = []
+ self.default_before = default_before
+ self.default_after = default_after
+ self.first = first
+ self.last = last
+
+ def remove(self, name):
+ self.names.remove(name)
+ del self.name2val[name]
+ after = self.name2after.pop(name, [])
+ if after:
+ self.req_after.remove(name)
+ for u in after:
+ self.order.remove((u, name))
+ before = self.name2before.pop(name, [])
+ if before:
+ self.req_before.remove(name)
+ for u in before:
+ self.order.remove((name, u))
+
+ def add(self, name, val, after=None, before=None):
+ if name in self.names:
+ self.remove(name)
+ self.names.append(name)
+ self.name2val[name] = val
+ if after is None and before is None:
+ before = self.default_before
+ after = self.default_after
+ if after is not None:
+ if not is_nonstr_iter(after):
+ after = (after,)
+ self.name2after[name] = after
+ self.order += [(u, name) for u in after]
+ self.req_after.add(name)
+ if before is not None:
+ if not is_nonstr_iter(before):
+ before = (before,)
+ self.name2before[name] = before
+ self.order += [(name, o) for o in before]
+ self.req_before.add(name)
+
+ def sorted(self):
+ order = [(self.first, self.last)]
+ roots = []
+ graph = {}
+ names = [self.first, self.last]
+ names.extend(self.names)
+
+ for a, b in self.order:
+ order.append((a, b))
+
+ def add_node(node):
+ if not node in graph:
+ roots.append(node)
+ graph[node] = [0] # 0 = number of arcs coming into this node
+
+ def add_arc(fromnode, tonode):
+ graph[fromnode].append(tonode)
+ graph[tonode][0] += 1
+ if tonode in roots:
+ roots.remove(tonode)
+
+ for name in names:
+ add_node(name)
+
+ has_before, has_after = set(), set()
+ for a, b in order:
+ if a in names and b in names: # deal with missing dependencies
+ add_arc(a, b)
+ has_before.add(a)
+ has_after.add(b)
+
+ if not self.req_before.issubset(has_before):
+ raise ConfigurationError(
+ 'Unsatisfied before dependencies: %s'
+ % (', '.join(sorted(self.req_before - has_before)))
+ )
+ if not self.req_after.issubset(has_after):
+ raise ConfigurationError(
+ 'Unsatisfied after dependencies: %s'
+ % (', '.join(sorted(self.req_after - has_after)))
+ )
+
+ sorted_names = []
+
+ while roots:
+ root = roots.pop(0)
+ sorted_names.append(root)
+ children = graph[root][1:]
+ for child in children:
+ arcs = graph[child][0]
+ arcs -= 1
+ graph[child][0] = arcs
+ if arcs == 0:
+ roots.insert(0, child)
+ del graph[root]
+
+ if graph:
+ # loop in input
+ cycledeps = {}
+ for k, v in graph.items():
+ cycledeps[k] = v[1:]
+ raise CyclicDependencyError(cycledeps)
+
+ result = []
+
+ for name in sorted_names:
+ if name in self.names:
+ result.append((name, self.name2val[name]))
+
+ return result
+
+class CyclicDependencyError(Exception):
+ def __init__(self, cycles):
+ self.cycles = cycles
+
+ def __str__(self):
+ L = []
+ cycles = self.cycles
+ for cycle in cycles:
+ dependent = cycle
+ dependees = cycles[cycle]
+ L.append('%r sorts before %r' % (dependent, dependees))
+ msg = 'Implicit ordering cycle:' + '; '.join(L)
+ return msg
+
+class PredicateList(object):
+
+ def __init__(self):
+ self.sorter = TopologicalSorter()
+ self.last_added = None
+
+ def add(self, name, factory, weighs_more_than=None, weighs_less_than=None):
+ # Predicates should be added to a predicate list in (presumed)
+ # computation expense order.
+ ## if weighs_more_than is None and weighs_less_than is None:
+ ## weighs_more_than = self.last_added or FIRST
+ ## weighs_less_than = LAST
+ self.last_added = name
+ self.sorter.add(name, factory, after=weighs_more_than,
+ before=weighs_less_than)
+
+ def make(self, config, **kw):
+ # Given a configurator and a list of keywords, a predicate list is
+ # computed. Elsewhere in the code, we evaluate predicates using a
+ # generator expression. All predicates associated with a view or
+ # route must evaluate true for the view or route to "match" during a
+ # request. The fastest predicate should be evaluated first, then the
+ # next fastest, and so on, as if one returns false, the remainder of
+ # the predicates won't need to be evaluated.
+ #
+ # While we compute predicates, we also compute a predicate hash (aka
+ # phash) that can be used by a caller to identify identical predicate
+ # lists.
+ ordered = self.sorter.sorted()
+ phash = md5()
+ weights = []
+ preds = []
+ for n, (name, predicate_factory) in enumerate(ordered):
+ vals = kw.pop(name, None)
+ if vals is None: # XXX should this be a sentinel other than None?
+ continue
+ if not isinstance(vals, predvalseq):
+ vals = (vals,)
+ for val in vals:
+ pred = predicate_factory(val, config)
+ hashes = pred.phash()
+ if not is_nonstr_iter(hashes):
+ hashes = [hashes]
+ for h in hashes:
+ phash.update(bytes_(h))
+ weights.append(1 << n+1)
+ preds.append(pred)
+ if kw:
+ raise ConfigurationError('Unknown predicate values: %r' % (kw,))
+ # A "order" is computed for the predicate list. An order is
+ # a scoring.
+ #
+ # Each predicate is associated with a weight value. The weight of a
+ # predicate symbolizes the relative potential "importance" of the
+ # predicate to all other predicates. A larger weight indicates
+ # greater importance.
+ #
+ # All weights for a given predicate list are bitwise ORed together
+ # to create a "score"; this score is then subtracted from
+ # MAX_ORDER and divided by an integer representing the number of
+ # predicates+1 to determine the order.
+ #
+ # For views, the order represents the ordering in which a "multiview"
+ # ( a collection of views that share the same context/request/name
+ # triad but differ in other ways via predicates) will attempt to call
+ # its set of views. Views with lower orders will be tried first.
+ # The intent is to a) ensure that views with more predicates are
+ # always evaluated before views with fewer predicates and b) to
+ # ensure a stable call ordering of views that share the same number
+ # of predicates. Views which do not have any predicates get an order
+ # of MAX_ORDER, meaning that they will be tried very last.
+ score = 0
+ for bit in weights:
+ score = score | bit
+ order = (MAX_ORDER - score) / (len(preds) + 1)
+ return order, preds, phash.hexdigest()
+
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 9e9b5321b..1c4e20dd6 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -20,6 +20,7 @@ from pyramid.interfaces import (
IException,
IExceptionViewClassifier,
IMultiView,
+ IPredicateList,
IRendererFactory,
IRequest,
IResponse,
@@ -54,6 +55,11 @@ from pyramid.httpexceptions import (
HTTPNotFound,
)
+from pyramid.registry import (
+ predvalseq,
+ Deferred,
+ )
+
from pyramid.security import NO_PERMISSION_REQUIRED
from pyramid.static import static_view
from pyramid.threadlocal import get_current_registry
@@ -63,14 +69,17 @@ from pyramid.view import (
AppendSlashNotFoundViewFactory,
)
-from pyramid.util import object_description
+from pyramid.util import (
+ object_description,
+ )
+
+import pyramid.config.predicates
from pyramid.config.util import (
DEFAULT_PHASH,
MAX_ORDER,
action_method,
- as_sorted_tuple,
- make_predicates,
+ PredicateList,
)
urljoin = urlparse.urljoin
@@ -272,20 +281,22 @@ class ViewDeriver(object):
@wraps_view
def predicated_view(self, view):
- predicates = self.kw.get('predicates', ())
- if not predicates:
+ preds = self.kw.get('predicates', ())
+ if not preds:
return view
def predicate_wrapper(context, request):
- if all((predicate(context, request) for predicate in predicates)):
- return view(context, request)
- view_name = getattr(view, '__name__', view)
- raise PredicateMismatch(
- 'predicate mismatch for view %s' % view_name)
+ for predicate in preds:
+ if not predicate(context, request):
+ view_name = getattr(view, '__name__', view)
+ raise PredicateMismatch(
+ 'predicate mismatch for view %s (%s)' % (
+ view_name, predicate.text()))
+ return view(context, request)
def checker(context, request):
return all((predicate(context, request) for predicate in
- predicates))
+ preds))
predicate_wrapper.__predicated__ = checker
- predicate_wrapper.__predicates__ = predicates
+ predicate_wrapper.__predicates__ = preds
return predicate_wrapper
@wraps_view
@@ -629,13 +640,31 @@ def viewdefaults(wrapped):
class ViewsConfiguratorMixin(object):
@viewdefaults
@action_method
- def add_view(self, view=None, name="", for_=None, permission=None,
- request_type=None, route_name=None, request_method=None,
- request_param=None, containment=None, attr=None,
- renderer=None, wrapper=None, xhr=False, accept=None,
- header=None, path_info=None, custom_predicates=(),
- context=None, decorator=None, mapper=None, http_cache=None,
- match_param=None):
+ def add_view(
+ self,
+ view=None,
+ name="",
+ for_=None,
+ permission=None,
+ request_type=None,
+ route_name=None,
+ request_method=None,
+ request_param=None,
+ containment=None,
+ attr=None,
+ renderer=None,
+ wrapper=None,
+ xhr=None,
+ accept=None,
+ header=None,
+ path_info=None,
+ custom_predicates=(),
+ context=None,
+ decorator=None,
+ mapper=None,
+ http_cache=None,
+ match_param=None,
+ **predicates):
""" Add a :term:`view configuration` to the current
configuration state. Arguments to ``add_view`` are broken
down below into *predicate* arguments and *non-predicate*
@@ -658,24 +687,27 @@ class ViewsConfiguratorMixin(object):
permission
- The name of a :term:`permission` that the user must possess
- in order to invoke the :term:`view callable`. See
- :ref:`view_security_section` for more information about view
- security and permissions. If ``permission`` is omitted, a
- *default* permission may be used for this view registration
- if one was named as the
+ A :term:`permission` that the user must possess in order to invoke
+ the :term:`view callable`. See :ref:`view_security_section` for
+ more information about view security and permissions. This is
+ often a string like ``view`` or ``edit``.
+
+ If ``permission`` is omitted, a *default* permission may be used
+ for this view registration if one was named as the
:class:`pyramid.config.Configurator` constructor's
``default_permission`` argument, or if
- :meth:`pyramid.config.Configurator.set_default_permission`
- was used prior to this view registration. Pass the string
- :data:`pyramid.security.NO_PERMISSION_REQUIRED` as the
- permission argument to explicitly indicate that the view should
- always be executable by entirely anonymous users, regardless of
- the default permission, bypassing any :term:`authorization
- policy` that may be in effect.
+ :meth:`pyramid.config.Configurator.set_default_permission` was used
+ prior to this view registration. Pass the value
+ :data:`pyramid.security.NO_PERMISSION_REQUIRED` as the permission
+ argument to explicitly indicate that the view should always be
+ executable by entirely anonymous users, regardless of the default
+ permission, bypassing any :term:`authorization policy` that may be
+ in effect.
attr
+ This knob is most useful when the view definition is a class.
+
The view machinery defaults to using the ``__call__`` method
of the :term:`view callable` (or the function itself, if the
view callable is a function) to obtain a response. The
@@ -684,8 +716,7 @@ class ViewsConfiguratorMixin(object):
class, and the class has a method named ``index`` and you
wanted to use this method instead of the class' ``__call__``
method to return the response, you'd say ``attr="index"`` in the
- view configuration for the view. This is
- most useful when the view definition is a class.
+ view configuration for the view.
renderer
@@ -969,9 +1000,16 @@ class ViewsConfiguratorMixin(object):
Each custom predicate callable should accept two arguments:
``context`` and ``request`` and should return either
``True`` or ``False`` after doing arbitrary evaluation of
- the context and/or the request. If all callables return
- ``True``, the associated view callable will be considered
- viable for a given request.
+ the context and/or the request.
+
+ predicates
+
+ Pass a key/value pair here to use a third-party predicate
+ registered via
+ :meth:`pyramid.config.Configurator.add_view_predicate`. More than
+ one key/value pair can be used at the same time. See
+ :ref:`registering_thirdparty_predicates` for more information about
+ third-party predicates. This argument is new as of Pyramid 1.4.
"""
view = self.maybe_dotted(view)
@@ -995,18 +1033,6 @@ class ViewsConfiguratorMixin(object):
raise ConfigurationError(
'request_type must be an interface, not %s' % request_type)
- if request_method is not None:
- request_method = as_sorted_tuple(request_method)
- if 'GET' in request_method and 'HEAD' not in request_method:
- # GET implies HEAD too
- request_method = as_sorted_tuple(request_method + ('HEAD',))
-
- order, predicates, phash = make_predicates(xhr=xhr,
- request_method=request_method, path_info=path_info,
- request_param=request_param, header=header, accept=accept,
- containment=containment, request_type=request_type,
- match_param=match_param, custom=custom_predicates)
-
if context is None:
context = for_
@@ -1022,17 +1048,38 @@ class ViewsConfiguratorMixin(object):
registry = self.registry)
introspectables = []
- discriminator = [
- 'view', context, name, request_type, IView, containment,
- request_param, request_method, route_name, attr,
- xhr, accept, header, path_info, match_param]
- discriminator.extend(sorted([hash(x) for x in custom_predicates]))
- discriminator = tuple(discriminator)
+ pvals = predicates.copy()
+ pvals.update(
+ dict(
+ xhr=xhr,
+ request_method=request_method,
+ path_info=path_info,
+ request_param=request_param,
+ header=header,
+ accept=accept,
+ containment=containment,
+ request_type=request_type,
+ match_param=match_param,
+ custom=predvalseq(custom_predicates),
+ )
+ )
+
+ def discrim_func():
+ # We need to defer the discriminator until we know what the phash
+ # is. It can't be computed any sooner because thirdparty
+ # predicates may not yet exist when add_view is called.
+ order, preds, phash = predlist.make(self, **pvals)
+ view_intr.update({'phash':phash, 'order':order, 'predicates':preds})
+ return ('view', context, name, route_name, phash)
+
+ discriminator = Deferred(discrim_func)
+
if inspect.isclass(view) and attr:
view_desc = 'method %r of %s' % (
attr, self.object_description(view))
else:
view_desc = self.object_description(view)
+
view_intr = self.introspectable('views',
discriminator,
view_desc,
@@ -1055,9 +1102,15 @@ class ViewsConfiguratorMixin(object):
decorator=decorator,
)
)
+ view_intr.update(**predicates)
introspectables.append(view_intr)
+ predlist = self.view_predlist
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,
@@ -1082,21 +1135,28 @@ class ViewsConfiguratorMixin(object):
# (reg'd in phase 1)
permission = self.registry.queryUtility(IDefaultPermission)
+ # added by discrim_func above during conflict resolving
+ preds = view_intr['predicates']
+ order = view_intr['order']
+ phash = view_intr['phash']
+
# __no_permission_required__ handled by _secure_view
- deriver = ViewDeriver(registry=self.registry,
- permission=permission,
- predicates=predicates,
- attr=attr,
- renderer=renderer,
- wrapper_viewname=wrapper,
- viewname=name,
- accept=accept,
- order=order,
- phash=phash,
- package=self.package,
- mapper=mapper,
- decorator=decorator,
- http_cache=http_cache)
+ deriver = ViewDeriver(
+ registry=self.registry,
+ permission=permission,
+ predicates=preds,
+ attr=attr,
+ renderer=renderer,
+ wrapper_viewname=wrapper,
+ viewname=name,
+ accept=accept,
+ order=order,
+ phash=phash,
+ package=self.package,
+ mapper=mapper,
+ decorator=decorator,
+ http_cache=http_cache,
+ )
derived_view = deriver(view)
derived_view.__discriminator__ = lambda *arg: discriminator
# __discriminator__ is used by superdynamic systems
@@ -1201,19 +1261,25 @@ class ViewsConfiguratorMixin(object):
IMultiView, name=name)
if mapper:
- mapper_intr = self.introspectable('view mappers',
- discriminator,
- 'view mapper for %s' % view_desc,
- 'view mapper')
+ mapper_intr = self.introspectable(
+ 'view mappers',
+ discriminator,
+ 'view mapper for %s' % view_desc,
+ 'view mapper'
+ )
mapper_intr['mapper'] = mapper
mapper_intr.relate('views', discriminator)
introspectables.append(mapper_intr)
if route_name:
view_intr.relate('routes', route_name) # see add_route
if renderer is not None and renderer.name and '.' in renderer.name:
- # it's a template
- tmpl_intr = self.introspectable('templates', discriminator,
- renderer.name, 'template')
+ # the renderer is a template
+ tmpl_intr = self.introspectable(
+ 'templates',
+ discriminator,
+ renderer.name,
+ 'template'
+ )
tmpl_intr.relate('views', discriminator)
tmpl_intr['name'] = renderer.name
tmpl_intr['type'] = renderer.type
@@ -1221,13 +1287,79 @@ class ViewsConfiguratorMixin(object):
tmpl_intr.relate('renderer factories', renderer.type)
introspectables.append(tmpl_intr)
if permission is not None:
- perm_intr = self.introspectable('permissions', permission,
- permission, 'permission')
+ # if a permission exists, register a permission introspectable
+ perm_intr = self.introspectable(
+ 'permissions',
+ permission,
+ permission,
+ 'permission'
+ )
perm_intr['value'] = permission
perm_intr.relate('views', discriminator)
introspectables.append(perm_intr)
self.action(discriminator, register, introspectables=introspectables)
+ @property
+ def view_predlist(self):
+ predlist = self.registry.queryUtility(IPredicateList, name='view')
+ if predlist is None:
+ predlist = PredicateList()
+ self.registry.registerUtility(predlist, IPredicateList, name='view')
+ return predlist
+
+ @action_method
+ def add_view_predicate(self, name, factory, weighs_more_than=None,
+ weighs_less_than=None):
+ """ Adds a view predicate factory. The associated view predicate can
+ later be named as a keyword argument to
+ :meth:`pyramid.config.Configurator.add_view` in the
+ ``predicates`` anonyous keyword argument dictionary.
+
+ ``name`` should be the name of the predicate. It must be a valid
+ Python identifier (it will be used as a keyword argument to
+ ``add_view`` by others).
+
+ ``factory`` should be a :term:`predicate factory`.
+
+ See :ref:`registering_thirdparty_predicates` for more information.
+
+ .. note::
+
+ This method is new as of Pyramid 1.4.
+ """
+ discriminator = ('view predicate', name)
+ intr = self.introspectable(
+ 'view predicates',
+ discriminator,
+ 'view predicate named %s' % name,
+ 'view predicate')
+ intr['name'] = name
+ intr['factory'] = factory
+ intr['weighs_more_than'] = weighs_more_than
+ intr['weighs_less_than'] = weighs_less_than
+ def register():
+ predlist = self.view_predlist
+ predlist.add(name, factory, weighs_more_than=weighs_more_than,
+ weighs_less_than=weighs_less_than)
+ self.action(discriminator, register, introspectables=(intr,),
+ order=PHASE1_CONFIG) # must be registered before views added
+
+ def add_default_view_predicates(self):
+ p = pyramid.config.predicates
+ for (name, factory) in (
+ ('xhr', p.XHRPredicate),
+ ('request_method', p.RequestMethodPredicate),
+ ('path_info', p.PathInfoPredicate),
+ ('request_param', p.RequestParamPredicate),
+ ('header', p.HeaderPredicate),
+ ('accept', p.AcceptPredicate),
+ ('containment', p.ContainmentPredicate),
+ ('request_type', p.RequestTypePredicate),
+ ('match_param', p.MatchParamPredicate),
+ ('custom', p.CustomPredicate),
+ ):
+ self.add_view_predicate(name, factory)
+
def derive_view(self, view, attr=None, renderer=None):
"""
Create a :term:`view callable` using the function, instance,
@@ -1345,11 +1477,26 @@ class ViewsConfiguratorMixin(object):
@action_method
def add_forbidden_view(
- self, view=None, attr=None, renderer=None, wrapper=None,
- route_name=None, request_type=None, request_method=None,
- request_param=None, containment=None, xhr=None, accept=None,
- header=None, path_info=None, custom_predicates=(), decorator=None,
- mapper=None, match_param=None):
+ self,
+ view=None,
+ attr=None,
+ renderer=None,
+ wrapper=None,
+ route_name=None,
+ request_type=None,
+ request_method=None,
+ request_param=None,
+ containment=None,
+ xhr=None,
+ accept=None,
+ header=None,
+ path_info=None,
+ custom_predicates=(),
+ decorator=None,
+ mapper=None,
+ match_param=None,
+ **predicates
+ ):
""" Add a forbidden view to the current configuration state. The
view will be called when Pyramid or application code raises a
:exc:`pyramid.httpexceptions.HTTPForbidden` exception and the set of
@@ -1366,12 +1513,23 @@ class ViewsConfiguratorMixin(object):
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
- view will be invoked.
+ view will be invoked. Unlike
+ :meth:`pyramid.config.Configurator.add_view`, this method will raise
+ an exception if passed ``name``, ``permission``, ``context``,
+ ``for_``, or ``http_cache`` keyword arguments. These argument values
+ make no sense in the context of a forbidden view.
.. note::
This method is new as of Pyramid 1.3.
"""
+ for arg in ('name', 'permission', 'context', 'for_', 'http_cache'):
+ if arg in predicates:
+ raise ConfigurationError(
+ '%s may not be used as an argument to add_forbidden_view'
+ % arg
+ )
+
settings = dict(
view=view,
context=HTTPForbidden,
@@ -1393,17 +1551,34 @@ class ViewsConfiguratorMixin(object):
attr=attr,
renderer=renderer,
)
+ settings.update(predicates)
return self.add_view(**settings)
set_forbidden_view = add_forbidden_view # deprecated sorta-bw-compat alias
@action_method
def add_notfound_view(
- self, view=None, attr=None, renderer=None, wrapper=None,
- route_name=None, request_type=None, request_method=None,
- request_param=None, containment=None, xhr=None, accept=None,
- header=None, path_info=None, custom_predicates=(), decorator=None,
- mapper=None, match_param=None, append_slash=False):
+ self,
+ view=None,
+ attr=None,
+ renderer=None,
+ wrapper=None,
+ route_name=None,
+ request_type=None,
+ request_method=None,
+ request_param=None,
+ containment=None,
+ xhr=None,
+ accept=None,
+ header=None,
+ path_info=None,
+ custom_predicates=(),
+ decorator=None,
+ mapper=None,
+ match_param=None,
+ append_slash=False,
+ **predicates
+ ):
""" Add a default notfound view to the current configuration state.
The view will be called when Pyramid or application code raises an
:exc:`pyramid.httpexceptions.HTTPForbidden` exception (e.g. when a
@@ -1419,7 +1594,11 @@ class ViewsConfiguratorMixin(object):
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
- view will be invoked.
+ view will be invoked. Unlike
+ :meth:`pyramid.config.Configurator.add_view`, this method will raise
+ an exception if passed ``name``, ``permission``, ``context``,
+ ``for_``, or ``http_cache`` keyword arguments. These argument values
+ make no sense in the context of a notfound view.
If ``append_slash`` is ``True``, when this notfound view is invoked,
and the current path info does not end in a slash, the notfound logic
@@ -1433,6 +1612,13 @@ class ViewsConfiguratorMixin(object):
This method is new as of Pyramid 1.3.
"""
+ for arg in ('name', 'permission', 'context', 'for_', 'http_cache'):
+ if arg in predicates:
+ raise ConfigurationError(
+ '%s may not be used as an argument to add_notfound_view'
+ % arg
+ )
+
settings = dict(
view=view,
context=HTTPNotFound,
@@ -1452,6 +1638,7 @@ class ViewsConfiguratorMixin(object):
route_name=route_name,
permission=NO_PERMISSION_REQUIRED,
)
+ settings.update(predicates)
if append_slash:
view = self._derive_view(view, attr=attr, renderer=renderer)
view = AppendSlashNotFoundViewFactory(view)
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index 1445ee394..042b4487b 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -513,9 +513,13 @@ class IRequestHandler(Interface):
IRequest.combined = IRequest # for exception view lookups
-class IRequestProperties(Interface):
- """ Marker interface for storing a list of request properties which
- will be added to the request object."""
+class IRequestExtensions(Interface):
+ """ Marker interface for storing request extensions (properties and
+ methods) which will be added to the request object."""
+ descriptors = Attribute(
+ """A list of descriptors that will be added to each request.""")
+ methods = Attribute(
+ """A list of methods to be added to each request.""")
class IRouteRequest(Interface):
""" *internal only* interface used as in a utility lookup to find
@@ -1111,6 +1115,9 @@ class IJSONAdapter(Interface):
into a JSON-serializable primitive.
"""
+class IPredicateList(Interface):
+ """ Interface representing a predicate list """
+
# 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.
diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py
index 208e54bf5..2b09e8d45 100644
--- a/pyramid/mako_templating.py
+++ b/pyramid/mako_templating.py
@@ -1,4 +1,6 @@
import os
+import posixpath
+import re
import sys
import threading
@@ -36,16 +38,28 @@ class PkgResourceTemplateLookup(TemplateLookup):
isabs = os.path.isabs(uri)
if (not isabs) and (':' in uri):
return uri
+ if not(isabs) and ('$' in uri):
+ return uri.replace('$', ':')
+ if relativeto is not None:
+ relativeto = relativeto.replace('$', ':')
+ if not(':' in uri) and (':' in relativeto):
+ if uri.startswith('/'):
+ return uri
+ pkg, relto = relativeto.split(':')
+ _uri = posixpath.join(posixpath.dirname(relto), uri)
+ return '{0}:{1}'.format(pkg, _uri)
+ if not(':' in uri) and not(':' in relativeto):
+ return posixpath.join(posixpath.dirname(relativeto), uri)
return TemplateLookup.adjust_uri(self, uri, relativeto)
def get_template(self, uri):
"""Fetch a template from the cache, or check the filesystem
for it
-
+
In addition to the basic filesystem lookup, this subclass will
use pkg_resource to load a file using the asset
specification syntax.
-
+
"""
isabs = os.path.isabs(uri)
if (not isabs) and (':' in uri):
@@ -69,14 +83,20 @@ class PkgResourceTemplateLookup(TemplateLookup):
return TemplateLookup.get_template(self, uri)
-registry_lock = threading.Lock()
+registry_lock = threading.Lock()
class MakoRendererFactoryHelper(object):
def __init__(self, settings_prefix=None):
self.settings_prefix = settings_prefix
def __call__(self, info):
- path = info.name
+ p = re.compile(
+ r'(?P<asset>[\w_.:/]+)'
+ r'(?:\#(?P<defname>[\w_]+))?'
+ r'(\.(?P<ext>.*))'
+ )
+ asset, defname, ext = p.match(info.name).group('asset', 'defname', 'ext')
+ path = '%s.%s' % (asset, ext)
registry = info.registry
settings = info.settings
settings_prefix = self.settings_prefix
@@ -136,12 +156,12 @@ class MakoRendererFactoryHelper(object):
registry_lock.acquire()
try:
- registry.registerUtility(lookup, IMakoLookup,
+ registry.registerUtility(lookup, IMakoLookup,
name=settings_prefix)
finally:
registry_lock.release()
- return MakoLookupTemplateRenderer(path, lookup)
+ return MakoLookupTemplateRenderer(path, defname, lookup)
renderer_factory = MakoRendererFactoryHelper('mako.')
@@ -156,10 +176,18 @@ class MakoRenderingException(Exception):
@implementer(ITemplateRenderer)
class MakoLookupTemplateRenderer(object):
- def __init__(self, path, lookup):
+ """ Render a :term:`Mako` template using the template
+ implied by the ``path`` argument.The ``path`` argument may be a
+ package-relative path, an absolute path, or a :term:`asset
+ specification`. If a defname is defined, in the form of
+ package:path/to/template#defname.mako, a function named ``defname``
+ inside the template will then be rendered.
+ """
+ def __init__(self, path, defname, lookup):
self.path = path
+ self.defname = defname
self.lookup = lookup
-
+
def implementation(self):
return self.lookup.get_template(self.path)
@@ -167,16 +195,19 @@ class MakoLookupTemplateRenderer(object):
context = system.pop('context', None)
if context is not None:
system['_context'] = context
- def_name = None
- if isinstance(value, tuple):
- def_name, value = value
+ if self.defname is None:
+ if isinstance(value, tuple):
+ self.defname, value = value
+ else:
+ if isinstance(value, tuple):
+ _, value = value
try:
system.update(value)
except (TypeError, ValueError):
raise ValueError('renderer was passed non-dictionary as value')
template = self.implementation()
- if def_name is not None:
- template = template.get_def(def_name)
+ if self.defname is not None:
+ template = template.get_def(self.defname)
try:
result = template.render_unicode(**system)
except:
diff --git a/pyramid/registry.py b/pyramid/registry.py
index f0f9c83ea..606251a8d 100644
--- a/pyramid/registry.py
+++ b/pyramid/registry.py
@@ -191,14 +191,20 @@ class Introspectable(dict):
def unrelate(self, category_name, discriminator):
self._relations.append((False, category_name, discriminator))
+ def _assert_resolved(self):
+ assert undefer(self.discriminator) is self.discriminator
+
@property
def discriminator_hash(self):
+ self._assert_resolved()
return hash(self.discriminator)
def __hash__(self):
+ self._assert_resolved()
return hash((self.category_name,) + (self.discriminator,))
def __repr__(self):
+ self._assert_resolved()
return '<%s category %r, discriminator %r>' % (self.__class__.__name__,
self.category_name,
self.discriminator)
@@ -209,9 +215,11 @@ class Introspectable(dict):
__bool__ = __nonzero__ # py3
def register(self, introspector, action_info):
+ self.discriminator = undefer(self.discriminator)
self.action_info = action_info
introspector.add(self)
for relate, category_name, discriminator in self._relations:
+ discriminator = undefer(discriminator)
if relate:
method = introspector.relate
else:
@@ -221,4 +229,29 @@ class Introspectable(dict):
(category_name, discriminator)
)
+class Deferred(object):
+ """ Can be used by a third-party configuration extender to wrap a
+ :term:`discriminator` during configuration if an immediately hashable
+ discriminator cannot be computed because it relies on unresolved values.
+ The function should accept no arguments and should return a hashable
+ discriminator."""
+ def __init__(self, func):
+ self.func = func
+
+ def resolve(self):
+ return self.func()
+
+def undefer(v):
+ """ Function which accepts an object and returns it unless it is a
+ :class:`pyramid.registry.Deferred` instance. If it is an instance of
+ that class, its ``resolve`` method is called, and the result of the
+ method is returned."""
+ if isinstance(v, Deferred):
+ v = v.resolve()
+ return v
+
+class predvalseq(tuple):
+ """ A subtype of tuple used to represent a sequence of predicate values """
+ pass
+
global_registry = Registry('global')
diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py
index 29ec9e72a..f64107d2b 100644
--- a/pyramid/scripts/proutes.py
+++ b/pyramid/scripts/proutes.py
@@ -15,10 +15,10 @@ class PRoutesCommand(object):
route, the pattern of the route, and the view callable which will be
invoked when the route is matched.
- This command accepts one positional argument named "config_uri". It
+ This command accepts one positional argument named 'config_uri'. It
specifies the PasteDeploy config file to use for the interactive
- shell. The format is "inifile#name". If the name is left off, "main"
- will be assumed. Example: "proutes myapp.ini".
+ shell. The format is 'inifile#name'. If the name is left off, 'main'
+ will be assumed. Example: 'proutes myapp.ini'.
"""
bootstrap = (bootstrap,)
diff --git a/pyramid/scripts/pviews.py b/pyramid/scripts/pviews.py
index 72a9800c3..a9db59dc1 100644
--- a/pyramid/scripts/pviews.py
+++ b/pyramid/scripts/pviews.py
@@ -17,11 +17,11 @@ class PViewsCommand(object):
each route+predicate set, print each view that might match and its
predicates.
- This command accepts two positional arguments: "config_uri" specifies the
+ This command accepts two positional arguments: 'config_uri' specifies the
PasteDeploy config file to use for the interactive shell. The format is
- "inifile#name". If the name is left off, "main" will be assumed. "url"
+ 'inifile#name'. If the name is left off, 'main' will be assumed. 'url'
specifies the path info portion of a URL that will be used to find
- matching views. Example: "proutes myapp.ini#main /url"
+ matching views. Example: 'proutes myapp.ini#main /url'
"""
stdout = sys.stdout
@@ -223,7 +223,7 @@ class PViewsCommand(object):
self.out("%srequired permission = %s" % (indent, permission))
predicates = getattr(view_wrapper, '__predicates__', None)
if predicates is not None:
- predicate_text = ', '.join([p.__text__ for p in predicates])
+ predicate_text = ', '.join([p.text() for p in predicates])
self.out("%sview predicates (%s)" % (indent, predicate_text))
def run(self):
diff --git a/pyramid/testing.py b/pyramid/testing.py
index 40e90cda6..89eec84b0 100644
--- a/pyramid/testing.py
+++ b/pyramid/testing.py
@@ -372,6 +372,7 @@ def registerRoute(pattern, name, factory=None):
"""
reg = get_current_registry()
config = Configurator(registry=reg)
+ config.setup_registry()
result = config.add_route(name, pattern, factory=factory)
config.commit()
return result
@@ -824,6 +825,8 @@ def setUp(registry=None, request=None, hook_zca=True, autocommit=True,
# ``render_template`` and friends went behind the back of
# any existing renderer factory lookup system.
config.add_renderer(name, renderer)
+ config.add_default_view_predicates()
+ config.add_default_route_predicates()
config.commit()
global have_zca
try:
diff --git a/pyramid/tests/fixtures/components.mak b/pyramid/tests/fixtures/components.mak
new file mode 100644
index 000000000..cc886805c
--- /dev/null
+++ b/pyramid/tests/fixtures/components.mak
@@ -0,0 +1,3 @@
+<%def name="comp()">
+World!
+</%def> \ No newline at end of file
diff --git a/pyramid/tests/fixtures/hellocompo.mak b/pyramid/tests/fixtures/hellocompo.mak
new file mode 100644
index 000000000..142676a11
--- /dev/null
+++ b/pyramid/tests/fixtures/hellocompo.mak
@@ -0,0 +1,3 @@
+<%namespace name="comp" file="pyramid.tests:fixtures/components.mak"/>
+Namespace
+Hello ${comp.comp()} \ No newline at end of file
diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py
index 1dfeda34c..a95326772 100644
--- a/pyramid/tests/test_config/test_factories.py
+++ b/pyramid/tests/test_config/test_factories.py
@@ -68,39 +68,40 @@ class TestFactoriesMixin(unittest.TestCase):
dummyfactory)
def test_set_request_property_with_callable(self):
- from pyramid.interfaces import IRequestProperties
+ from pyramid.interfaces import IRequestExtensions
config = self._makeOne(autocommit=True)
callable = lambda x: None
config.set_request_property(callable, name='foo')
- plist = config.registry.getUtility(IRequestProperties)
- self.assertEqual(set(p[0] for p in plist), set(['foo']))
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.descriptors)
def test_set_request_property_with_unnamed_callable(self):
- from pyramid.interfaces import IRequestProperties
+ from pyramid.interfaces import IRequestExtensions
config = self._makeOne(autocommit=True)
def foo(self): pass
config.set_request_property(foo, reify=True)
- plist = config.registry.getUtility(IRequestProperties)
- self.assertEqual(set(p[0] for p in plist), set(['foo']))
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.descriptors)
def test_set_request_property_with_property(self):
- from pyramid.interfaces import IRequestProperties
+ from pyramid.interfaces import IRequestExtensions
config = self._makeOne(autocommit=True)
callable = property(lambda x: None)
config.set_request_property(callable, name='foo')
- plist = config.registry.getUtility(IRequestProperties)
- self.assertEqual(set(p[0] for p in plist), set(['foo']))
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.descriptors)
def test_set_multiple_request_properties(self):
- from pyramid.interfaces import IRequestProperties
+ from pyramid.interfaces import IRequestExtensions
config = self._makeOne()
def foo(self): pass
bar = property(lambda x: None)
config.set_request_property(foo, reify=True)
config.set_request_property(bar, name='bar')
config.commit()
- plist = config.registry.getUtility(IRequestProperties)
- self.assertEqual(set(p[0] for p in plist), set(['foo', 'bar']))
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.descriptors)
+ self.assertTrue('bar' in exts.descriptors)
def test_set_multiple_request_properties_conflict(self):
from pyramid.exceptions import ConfigurationConflictError
@@ -124,19 +125,76 @@ class TestFactoriesMixin(unittest.TestCase):
request = DummyRequest(config.registry)
event = Event()
config.registry.notify(event)
- plist = event.request.plist
- self.assertEqual(set(p[0] for p in plist), set(['foo', 'bar']))
+ exts = event.request.extensions
+ self.assertTrue('foo' in exts[0])
+ self.assertTrue('bar' in exts[1])
+
+ def test_set_request_method_subscriber(self):
+ from zope.interface import implementer
+ from pyramid.interfaces import INewRequest
+ config = self._makeOne(autocommit=True)
+ def foo(r): return 'bar'
+ config.set_request_method(foo, name='foo')
+ @implementer(INewRequest)
+ class Event(object):
+ request = DummyRequest(config.registry)
+ event = Event()
+ config.registry.notify(event)
+ self.assertEqual('bar', event.request.foo())
+
+ def test_set_request_method_with_callable(self):
+ from pyramid.interfaces import IRequestExtensions
+ config = self._makeOne(autocommit=True)
+ callable = lambda x: None
+ config.set_request_method(callable, name='foo')
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.methods)
+
+ def test_set_request_method_with_unnamed_callable(self):
+ from pyramid.interfaces import IRequestExtensions
+ config = self._makeOne(autocommit=True)
+ def foo(self): pass
+ config.set_request_method(foo)
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.methods)
+
+ def test_set_multiple_request_methods_conflict(self):
+ from pyramid.exceptions import ConfigurationConflictError
+ config = self._makeOne()
+ def foo(self): pass
+ def bar(self): pass
+ config.set_request_method(foo, name='bar')
+ config.set_request_method(bar, name='bar')
+ self.assertRaises(ConfigurationConflictError, config.commit)
+
+ def test_set_request_method_with_None_callable(self):
+ from pyramid.interfaces import IRequestExtensions
+ config = self._makeOne(autocommit=True)
+ config.set_request_method(name='foo')
+ exts = config.registry.queryUtility(IRequestExtensions)
+ self.assertTrue(exts is None)
+
+ def test_set_request_method_with_None_callable_conflict(self):
+ from pyramid.exceptions import ConfigurationConflictError
+ config = self._makeOne()
+ def bar(self): pass
+ config.set_request_method(name='foo')
+ config.set_request_method(bar, name='foo')
+ self.assertRaises(ConfigurationConflictError, config.commit)
+
+ def test_set_request_method_with_None_callable_and_no_name(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(AttributeError, config.set_request_method)
-
class DummyRequest(object):
- plist = None
+ extensions = None
def __init__(self, registry):
self.registry = registry
def _set_properties(self, properties):
- if self.plist is None:
- self.plist = []
- self.plist.extend(properties)
+ if self.extensions is None:
+ self.extensions = []
+ self.extensions.extend(properties)
diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py
index 37c3de275..abe22400b 100644
--- a/pyramid/tests/test_config/test_init.py
+++ b/pyramid/tests/test_config/test_init.py
@@ -349,7 +349,7 @@ class ConfiguratorTests(unittest.TestCase):
config.setup_registry()
self.assertEqual(reg.has_listeners, True)
- def test_setup_registry_registers_default_exceptionresponse_view(self):
+ def test_setup_registry_registers_default_exceptionresponse_views(self):
from webob.exc import WSGIHTTPException
from pyramid.interfaces import IExceptionResponse
from pyramid.view import default_exceptionresponse_view
@@ -357,6 +357,7 @@ class ConfiguratorTests(unittest.TestCase):
config = self._makeOne(reg)
views = []
config.add_view = lambda *arg, **kw: views.append((arg, kw))
+ config.add_default_view_predicates = lambda *arg: None
config._add_tween = lambda *arg, **kw: False
config.setup_registry()
self.assertEqual(views[0], ((default_exceptionresponse_view,),
@@ -364,6 +365,16 @@ class ConfiguratorTests(unittest.TestCase):
self.assertEqual(views[1], ((default_exceptionresponse_view,),
{'context':WSGIHTTPException}))
+ def test_setup_registry_registers_default_view_predicates(self):
+ reg = DummyRegistry()
+ config = self._makeOne(reg)
+ vp_called = []
+ config.add_view = lambda *arg, **kw: None
+ config.add_default_view_predicates = lambda *arg: vp_called.append(True)
+ config._add_tween = lambda *arg, **kw: False
+ config.setup_registry()
+ self.assertTrue(vp_called)
+
def test_setup_registry_registers_default_webob_iresponse_adapter(self):
from webob import Response
from pyramid.interfaces import IResponse
@@ -1385,13 +1396,9 @@ class TestConfiguratorDeprecatedFeatures(unittest.TestCase):
try:
config.commit()
except ConfigurationConflictError as why:
- c1, c2, c3, c4, c5, c6 = _conflictFunctions(why)
+ c1, c2 = _conflictFunctions(why)
self.assertEqual(c1, 'test_conflict_route_with_view')
self.assertEqual(c2, 'test_conflict_route_with_view')
- self.assertEqual(c3, 'test_conflict_route_with_view')
- self.assertEqual(c4, 'test_conflict_route_with_view')
- self.assertEqual(c5, 'test_conflict_route_with_view')
- self.assertEqual(c6, 'test_conflict_route_with_view')
else: # pragma: no cover
raise AssertionError
@@ -1682,6 +1689,7 @@ class Test_resolveConflicts(unittest.TestCase):
(3, f, (3,), {}, ('y',)),
(None, f, (5,), {}, ('y',)),
])
+ result = list(result)
self.assertEqual(
result,
[{'info': None,
@@ -1743,6 +1751,7 @@ class Test_resolveConflicts(unittest.TestCase):
expand_action(3, f, (3,), {}, ('y',)),
expand_action(None, f, (5,), {}, ('y',)),
])
+ result = list(result)
self.assertEqual(
result,
[{'info': None,
@@ -1794,32 +1803,31 @@ class Test_resolveConflicts(unittest.TestCase):
def test_it_conflict(self):
from pyramid.tests.test_config import dummyfactory as f
- self.assertRaises(
- ConfigurationConflictError,
- self._callFUT, [
- (None, f),
- (1, f, (2,), {}, ('x',), 'eek'),
- (1, f, (3,), {}, ('y',), 'ack'),
- (4, f, (4,), {}, ('y',)),
- (3, f, (3,), {}, ('y',)),
- (None, f, (5,), {}, ('y',)),
- ]
- )
+ result = self._callFUT([
+ (None, f),
+ (1, f, (2,), {}, ('x',), 'eek'), # will conflict
+ (1, f, (3,), {}, ('y',), 'ack'), # will conflict
+ (4, f, (4,), {}, ('y',)),
+ (3, f, (3,), {}, ('y',)),
+ (None, f, (5,), {}, ('y',)),
+ ])
+ self.assertRaises(ConfigurationConflictError, list, result)
def test_it_with_actions_grouped_by_order(self):
from pyramid.tests.test_config import dummyfactory as f
from pyramid.config import expand_action
result = self._callFUT([
- expand_action(None, f),
- expand_action(1, f, (1,), {}, (), 'third', 10),
+ expand_action(None, f), # X
+ expand_action(1, f, (1,), {}, (), 'third', 10), # X
expand_action(1, f, (2,), {}, ('x',), 'fourth', 10),
expand_action(1, f, (3,), {}, ('y',), 'fifth', 10),
- expand_action(2, f, (1,), {}, (), 'sixth', 10),
- expand_action(3, f, (1,), {}, (), 'seventh', 10),
- expand_action(5, f, (4,), {}, ('y',), 'eighth', 99999),
- expand_action(4, f, (3,), {}, (), 'first', 5),
+ expand_action(2, f, (1,), {}, (), 'sixth', 10), # X
+ expand_action(3, f, (1,), {}, (), 'seventh', 10), # X
+ expand_action(5, f, (4,), {}, ('y',), 'eighth', 99999), # X
+ expand_action(4, f, (3,), {}, (), 'first', 5), # X
expand_action(4, f, (5,), {}, ('y',), 'second', 5),
])
+ result = list(result)
self.assertEqual(len(result), 6)
# resolved actions should be grouped by (order, i)
self.assertEqual(
@@ -1940,10 +1948,11 @@ class DummyEvent:
pass
class DummyRegistry(object):
- def __init__(self, adaptation=None):
+ def __init__(self, adaptation=None, util=None):
self.utilities = []
self.adapters = []
self.adaptation = adaptation
+ self.util = util
def subscribers(self, events, name):
self.events = events
return events
@@ -1953,6 +1962,8 @@ class DummyRegistry(object):
self.adapters.append((arg, kw))
def queryAdapter(self, *arg, **kw):
return self.adaptation
+ def queryUtility(self, *arg, **kw):
+ return self.util
from pyramid.interfaces import IResponse
@implementer(IResponse)
diff --git a/pyramid/tests/test_config/test_predicates.py b/pyramid/tests/test_config/test_predicates.py
new file mode 100644
index 000000000..e33a31458
--- /dev/null
+++ b/pyramid/tests/test_config/test_predicates.py
@@ -0,0 +1,268 @@
+import unittest
+
+from pyramid.compat import text_
+
+class TestXHRPredicate(unittest.TestCase):
+ def _makeOne(self, val):
+ from pyramid.config.predicates import XHRPredicate
+ return XHRPredicate(val, None)
+
+ def test___call___true(self):
+ inst = self._makeOne(True)
+ request = Dummy()
+ request.is_xhr = True
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___false(self):
+ inst = self._makeOne(True)
+ request = Dummy()
+ request.is_xhr = False
+ result = inst(None, request)
+ self.assertFalse(result)
+
+ def test_text(self):
+ inst = self._makeOne(True)
+ self.assertEqual(inst.text(), 'xhr = True')
+
+ def test_phash(self):
+ inst = self._makeOne(True)
+ self.assertEqual(inst.phash(), 'xhr = True')
+
+class TestRequestMethodPredicate(unittest.TestCase):
+ def _makeOne(self, val):
+ from pyramid.config.predicates import RequestMethodPredicate
+ return RequestMethodPredicate(val, None)
+
+ def test_ctor_get_but_no_head(self):
+ inst = self._makeOne('GET')
+ self.assertEqual(inst.val, ('GET', 'HEAD'))
+
+ def test___call___true_single(self):
+ inst = self._makeOne('GET')
+ request = Dummy()
+ request.method = 'GET'
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___true_multi(self):
+ inst = self._makeOne(('GET','HEAD'))
+ request = Dummy()
+ request.method = 'GET'
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___false(self):
+ inst = self._makeOne(('GET','HEAD'))
+ request = Dummy()
+ request.method = 'POST'
+ result = inst(None, request)
+ self.assertFalse(result)
+
+ def test_text(self):
+ inst = self._makeOne(('HEAD','GET'))
+ self.assertEqual(inst.text(), 'request_method = GET,HEAD')
+
+ def test_phash(self):
+ inst = self._makeOne(('HEAD','GET'))
+ self.assertEqual(inst.phash(), 'request_method = GET,HEAD')
+
+class TestPathInfoPredicate(unittest.TestCase):
+ def _makeOne(self, val):
+ from pyramid.config.predicates import PathInfoPredicate
+ return PathInfoPredicate(val, None)
+
+ def test_ctor_compilefail(self):
+ from pyramid.exceptions import ConfigurationError
+ self.assertRaises(ConfigurationError, self._makeOne, '\\')
+
+ def test___call___true(self):
+ inst = self._makeOne(r'/\d{2}')
+ request = Dummy()
+ request.upath_info = text_('/12')
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___false(self):
+ inst = self._makeOne(r'/\d{2}')
+ request = Dummy()
+ request.upath_info = text_('/n12')
+ result = inst(None, request)
+ self.assertFalse(result)
+
+ def test_text(self):
+ inst = self._makeOne('/')
+ self.assertEqual(inst.text(), 'path_info = /')
+
+ def test_phash(self):
+ inst = self._makeOne('/')
+ self.assertEqual(inst.phash(), 'path_info = /')
+
+class TestRequestParamPredicate(unittest.TestCase):
+ def _makeOne(self, val):
+ from pyramid.config.predicates import RequestParamPredicate
+ return RequestParamPredicate(val, None)
+
+ def test___call___true_exists(self):
+ inst = self._makeOne('abc')
+ request = Dummy()
+ request.params = {'abc':1}
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___true_withval(self):
+ inst = self._makeOne('abc=1')
+ request = Dummy()
+ request.params = {'abc':'1'}
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___false(self):
+ inst = self._makeOne('abc')
+ request = Dummy()
+ request.params = {}
+ result = inst(None, request)
+ self.assertFalse(result)
+
+ def test_text_exists(self):
+ inst = self._makeOne('abc')
+ self.assertEqual(inst.text(), 'request_param abc')
+
+ def test_text_withval(self):
+ inst = self._makeOne('abc= 1')
+ self.assertEqual(inst.text(), 'request_param abc = 1')
+
+ def test_phash_exists(self):
+ inst = self._makeOne('abc')
+ self.assertEqual(inst.phash(), 'request_param abc')
+
+ def test_phash_withval(self):
+ inst = self._makeOne('abc= 1')
+ self.assertEqual(inst.phash(), "request_param abc = 1")
+
+class TestMatchParamPredicate(unittest.TestCase):
+ def _makeOne(self, val):
+ from pyramid.config.predicates import MatchParamPredicate
+ return MatchParamPredicate(val, None)
+
+ def test___call___true_single(self):
+ inst = self._makeOne('abc=1')
+ request = Dummy()
+ request.matchdict = {'abc':'1'}
+ result = inst(None, request)
+ self.assertTrue(result)
+
+
+ def test___call___true_multi(self):
+ inst = self._makeOne(('abc=1', 'def=2'))
+ request = Dummy()
+ request.matchdict = {'abc':'1', 'def':'2'}
+ result = inst(None, request)
+ self.assertTrue(result)
+
+ def test___call___false(self):
+ inst = self._makeOne('abc=1')
+ request = Dummy()
+ request.matchdict = {}
+ result = inst(None, request)
+ self.assertFalse(result)
+
+ def test_text(self):
+ inst = self._makeOne(('def= 1', 'abc =2'))
+ self.assertEqual(inst.text(), 'match_param abc=2,def=1')
+
+ def test_phash(self):
+ inst = self._makeOne(('def= 1', 'abc =2'))
+ self.assertEqual(inst.phash(), 'match_param abc=2,def=1')
+
+class TestCustomPredicate(unittest.TestCase):
+ def _makeOne(self, val):
+ from pyramid.config.predicates import CustomPredicate
+ return CustomPredicate(val, None)
+
+ def test___call___true(self):
+ def func(context, request):
+ self.assertEqual(context, None)
+ self.assertEqual(request, None)
+ return True
+ inst = self._makeOne(func)
+ result = inst(None, None)
+ self.assertTrue(result)
+
+ def test___call___false(self):
+ def func(context, request):
+ self.assertEqual(context, None)
+ self.assertEqual(request, None)
+ return False
+ inst = self._makeOne(func)
+ result = inst(None, None)
+ self.assertFalse(result)
+
+ def test_text_func_has___text__(self):
+ pred = predicate()
+ pred.__text__ = 'text'
+ inst = self._makeOne(pred)
+ self.assertEqual(inst.text(), 'text')
+
+ def test_text_func_repr(self):
+ pred = predicate()
+ inst = self._makeOne(pred)
+ self.assertEqual(inst.text(), 'custom predicate: object predicate')
+
+ def test_phash(self):
+ pred = predicate()
+ inst = self._makeOne(pred)
+ self.assertEqual(inst.phash(), 'custom:1')
+
+class TestTraversePredicate(unittest.TestCase):
+ def _makeOne(self, val):
+ from pyramid.config.predicates import TraversePredicate
+ return TraversePredicate(val, None)
+
+ def test___call__traverse_has_remainder_already(self):
+ inst = self._makeOne('/1/:a/:b')
+ info = {'traverse':'abc'}
+ request = Dummy()
+ result = inst(info, request)
+ self.assertEqual(result, True)
+ self.assertEqual(info, {'traverse':'abc'})
+
+ def test___call__traverse_matches(self):
+ inst = self._makeOne('/1/:a/:b')
+ info = {'match':{'a':'a', 'b':'b'}}
+ request = Dummy()
+ result = inst(info, request)
+ self.assertEqual(result, True)
+ self.assertEqual(info, {'match':
+ {'a':'a', 'b':'b', 'traverse':('1', 'a', 'b')}})
+
+ def test___call__traverse_matches_with_highorder_chars(self):
+ inst = self._makeOne(text_(b'/La Pe\xc3\xb1a/{x}', 'utf-8'))
+ info = {'match':{'x':text_(b'Qu\xc3\xa9bec', 'utf-8')}}
+ request = Dummy()
+ result = inst(info, request)
+ self.assertEqual(result, True)
+ self.assertEqual(
+ info['match']['traverse'],
+ (text_(b'La Pe\xc3\xb1a', 'utf-8'),
+ text_(b'Qu\xc3\xa9bec', 'utf-8'))
+ )
+
+ def test_text(self):
+ inst = self._makeOne('/abc')
+ self.assertEqual(inst.text(), 'traverse matchdict pseudo-predicate')
+
+ def test_phash(self):
+ inst = self._makeOne('/abc')
+ self.assertEqual(inst.phash(), '')
+
+class predicate(object):
+ def __repr__(self):
+ return 'predicate'
+ def __hash__(self):
+ return 1
+
+class Dummy(object):
+ def __init__(self, **kw):
+ self.__dict__.update(**kw)
+
diff --git a/pyramid/tests/test_config/test_routes.py b/pyramid/tests/test_config/test_routes.py
index bb47d2d7e..6fb5189f6 100644
--- a/pyramid/tests/test_config/test_routes.py
+++ b/pyramid/tests/test_config/test_routes.py
@@ -158,7 +158,7 @@ class RoutesConfiguratorMixinTests(unittest.TestCase):
def pred2(context, request): pass
config.add_route('name', 'path', custom_predicates=(pred1, pred2))
route = self._assertRoute(config, 'name', 'path', 2)
- self.assertEqual(route.predicates, [pred1, pred2])
+ self.assertEqual(len(route.predicates), 2)
def test_add_route_with_header(self):
config = self._makeOne(autocommit=True)
diff --git a/pyramid/tests/test_config/test_security.py b/pyramid/tests/test_config/test_security.py
index d05d1d471..817f6ce02 100644
--- a/pyramid/tests/test_config/test_security.py
+++ b/pyramid/tests/test_config/test_security.py
@@ -89,3 +89,12 @@ class ConfiguratorSecurityMethodsTests(unittest.TestCase):
self.assertEqual(config.registry.getUtility(IDefaultPermission),
'view')
+ def test_add_permission(self):
+ config = self._makeOne(autocommit=True)
+ config.add_permission('perm')
+ cat = config.registry.introspector.get_category('permissions')
+ self.assertEqual(len(cat), 1)
+ D = cat[0]
+ intr = D['introspectable']
+ self.assertEqual(intr['value'], 'perm')
+
diff --git a/pyramid/tests/test_config/test_tweens.py b/pyramid/tests/test_config/test_tweens.py
index 0d96bf601..8853b6899 100644
--- a/pyramid/tests/test_config/test_tweens.py
+++ b/pyramid/tests/test_config/test_tweens.py
@@ -179,28 +179,12 @@ class TestTweens(unittest.TestCase):
('name2', 'factory2')])
def test_add_implicit(self):
- from pyramid.tweens import INGRESS
tweens = self._makeOne()
tweens.add_implicit('name', 'factory')
- self.assertEqual(tweens.names, ['name'])
- self.assertEqual(tweens.factories,
- {'name':'factory'})
- self.assertEqual(tweens.order, [(INGRESS, 'name')])
tweens.add_implicit('name2', 'factory2')
- self.assertEqual(tweens.names, ['name', 'name2'])
- self.assertEqual(tweens.factories,
- {'name':'factory', 'name2':'factory2'})
- self.assertEqual(tweens.order,
- [(INGRESS, 'name'), (INGRESS, 'name2')])
- tweens.add_implicit('name3', 'factory3', over='name2')
- self.assertEqual(tweens.names,
- ['name', 'name2', 'name3'])
- self.assertEqual(tweens.factories,
- {'name':'factory', 'name2':'factory2',
- 'name3':'factory3'})
- self.assertEqual(tweens.order,
- [(INGRESS, 'name'), (INGRESS, 'name2'),
- ('name3', 'name2')])
+ self.assertEqual(tweens.sorter.sorted(),
+ [('name2', 'factory2'),
+ ('name', 'factory')])
def test___call___explicit(self):
tweens = self._makeOne()
@@ -212,18 +196,13 @@ class TestTweens(unittest.TestCase):
self.assertEqual(tweens(None, None), '123')
def test___call___implicit(self):
- from pyramid.tweens import INGRESS
tweens = self._makeOne()
def factory1(handler, registry):
return handler
def factory2(handler, registry):
return '123'
- tweens.names = ['name', 'name2']
- tweens.alias_to_name = {'name':'name', 'name2':'name2'}
- tweens.name_to_alias = {'name':'name', 'name2':'name2'}
- tweens.req_under = set(['name', 'name2'])
- tweens.order = [(INGRESS, 'name'), (INGRESS, 'name2')]
- tweens.factories = {'name':factory1, 'name2':factory2}
+ tweens.add_implicit('name2', factory2)
+ tweens.add_implicit('name1', factory1)
self.assertEqual(tweens(None, None), '123')
def test_implicit_ordering_1(self):
@@ -413,7 +392,7 @@ class TestTweens(unittest.TestCase):
self.assertRaises(ConfigurationError, tweens.implicit)
def test_implicit_ordering_conflict_direct(self):
- from pyramid.config.tweens import CyclicDependencyError
+ from pyramid.config.util import CyclicDependencyError
tweens = self._makeOne()
add = tweens.add_implicit
add('browserid', 'browserid_factory')
@@ -421,7 +400,7 @@ class TestTweens(unittest.TestCase):
self.assertRaises(CyclicDependencyError, tweens.implicit)
def test_implicit_ordering_conflict_indirect(self):
- from pyramid.config.tweens import CyclicDependencyError
+ from pyramid.config.util import CyclicDependencyError
tweens = self._makeOne()
add = tweens.add_implicit
add('browserid', 'browserid_factory')
@@ -429,14 +408,3 @@ class TestTweens(unittest.TestCase):
add('dbt', 'dbt_factory', under='browserid', over='auth')
self.assertRaises(CyclicDependencyError, tweens.implicit)
-class TestCyclicDependencyError(unittest.TestCase):
- def _makeOne(self, cycles):
- from pyramid.config.tweens import CyclicDependencyError
- return CyclicDependencyError(cycles)
-
- def test___str__(self):
- exc = self._makeOne({'a':['c', 'd'], 'c':['a']})
- result = str(exc)
- self.assertTrue("'a' sorts over ['c', 'd']" in result)
- self.assertTrue("'c' sorts over ['a']" in result)
-
diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py
index 1ad1fb3c1..13cb27526 100644
--- a/pyramid/tests/test_config/test_util.py
+++ b/pyramid/tests/test_config/test_util.py
@@ -1,10 +1,32 @@
import unittest
from pyramid.compat import text_
-class Test__make_predicates(unittest.TestCase):
+class TestPredicateList(unittest.TestCase):
+
+ def _makeOne(self):
+ from pyramid.config.util import PredicateList
+ from pyramid.config import predicates
+ inst = PredicateList()
+ for name, factory in (
+ ('xhr', predicates.XHRPredicate),
+ ('request_method', predicates.RequestMethodPredicate),
+ ('path_info', predicates.PathInfoPredicate),
+ ('request_param', predicates.RequestParamPredicate),
+ ('header', predicates.HeaderPredicate),
+ ('accept', predicates.AcceptPredicate),
+ ('containment', predicates.ContainmentPredicate),
+ ('request_type', predicates.RequestTypePredicate),
+ ('match_param', predicates.MatchParamPredicate),
+ ('custom', predicates.CustomPredicate),
+ ('traverse', predicates.TraversePredicate),
+ ):
+ inst.add(name, factory)
+ return inst
+
def _callFUT(self, **kw):
- from pyramid.config.util import make_predicates
- return make_predicates(**kw)
+ inst = self._makeOne()
+ config = DummyConfigurator()
+ return inst.make(config, **kw)
def test_ordering_xhr_and_request_method_trump_only_containment(self):
order1, _, _ = self._callFUT(xhr=True, request_method='GET')
@@ -12,6 +34,7 @@ class Test__make_predicates(unittest.TestCase):
self.assertTrue(order1 < order2)
def test_ordering_number_of_predicates(self):
+ from pyramid.config.util import predvalseq
order1, _, _ = self._callFUT(
xhr='xhr',
request_method='request_method',
@@ -22,7 +45,7 @@ class Test__make_predicates(unittest.TestCase):
accept='accept',
containment='containment',
request_type='request_type',
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
order2, _, _ = self._callFUT(
xhr='xhr',
@@ -34,7 +57,7 @@ class Test__make_predicates(unittest.TestCase):
accept='accept',
containment='containment',
request_type='request_type',
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
order3, _, _ = self._callFUT(
xhr='xhr',
@@ -114,6 +137,7 @@ class Test__make_predicates(unittest.TestCase):
self.assertTrue(order12 > order10)
def test_ordering_importance_of_predicates(self):
+ from pyramid.config.util import predvalseq
order1, _, _ = self._callFUT(
xhr='xhr',
)
@@ -142,7 +166,7 @@ class Test__make_predicates(unittest.TestCase):
match_param='foo=bar',
)
order10, _, _ = self._callFUT(
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
self.assertTrue(order1 > order2)
self.assertTrue(order2 > order3)
@@ -155,12 +179,13 @@ class Test__make_predicates(unittest.TestCase):
self.assertTrue(order9 > order10)
def test_ordering_importance_and_number(self):
+ from pyramid.config.util import predvalseq
order1, _, _ = self._callFUT(
xhr='xhr',
request_method='request_method',
)
order2, _, _ = self._callFUT(
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
self.assertTrue(order1 < order2)
@@ -170,7 +195,7 @@ class Test__make_predicates(unittest.TestCase):
)
order2, _, _ = self._callFUT(
request_method='request_method',
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
self.assertTrue(order1 > order2)
@@ -181,7 +206,7 @@ class Test__make_predicates(unittest.TestCase):
)
order2, _, _ = self._callFUT(
request_method='request_method',
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
self.assertTrue(order1 < order2)
@@ -193,18 +218,19 @@ class Test__make_predicates(unittest.TestCase):
order2, _, _ = self._callFUT(
xhr='xhr',
request_method='request_method',
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
self.assertTrue(order1 > order2)
def test_different_custom_predicates_with_same_hash(self):
+ from pyramid.config.util import predvalseq
class PredicateWithHash(object):
def __hash__(self):
return 1
a = PredicateWithHash()
b = PredicateWithHash()
- _, _, a_phash = self._callFUT(custom=(a,))
- _, _, b_phash = self._callFUT(custom=(b,))
+ _, _, a_phash = self._callFUT(custom=predvalseq([a]))
+ _, _, b_phash = self._callFUT(custom=predvalseq([b]))
self.assertEqual(a_phash, b_phash)
def test_traverse_has_remainder_already(self):
@@ -244,12 +270,14 @@ class Test__make_predicates(unittest.TestCase):
)
def test_custom_predicates_can_affect_traversal(self):
+ from pyramid.config.util import predvalseq
def custom(info, request):
m = info['match']
m['dummy'] = 'foo'
return True
- _, predicates, _ = self._callFUT(custom=(custom,),
- traverse='/1/:dummy/:a')
+ _, predicates, _ = self._callFUT(
+ custom=predvalseq([custom]),
+ traverse='/1/:dummy/:a')
self.assertEqual(len(predicates), 2)
info = {'match':{'a':'a'}}
request = DummyRequest()
@@ -259,6 +287,7 @@ class Test__make_predicates(unittest.TestCase):
'traverse':('1', 'foo', 'a')}})
def test_predicate_text_is_correct(self):
+ from pyramid.config.util import predvalseq
_, predicates, _ = self._callFUT(
xhr='xhr',
request_method='request_method',
@@ -268,23 +297,27 @@ class Test__make_predicates(unittest.TestCase):
accept='accept',
containment='containment',
request_type='request_type',
- custom=(DummyCustomPredicate(),
+ custom=predvalseq(
+ [
+ DummyCustomPredicate(),
DummyCustomPredicate.classmethod_predicate,
- DummyCustomPredicate.classmethod_predicate_no_text),
+ DummyCustomPredicate.classmethod_predicate_no_text,
+ ]
+ ),
match_param='foo=bar')
- self.assertEqual(predicates[0].__text__, 'xhr = True')
- self.assertEqual(predicates[1].__text__,
- "request method = ['request_method']")
- self.assertEqual(predicates[2].__text__, 'path_info = path_info')
- self.assertEqual(predicates[3].__text__, 'request_param param')
- self.assertEqual(predicates[4].__text__, 'header header')
- self.assertEqual(predicates[5].__text__, 'accept = accept')
- self.assertEqual(predicates[6].__text__, 'containment = containment')
- self.assertEqual(predicates[7].__text__, 'request_type = request_type')
- self.assertEqual(predicates[8].__text__, "match_param ['foo=bar']")
- self.assertEqual(predicates[9].__text__, 'custom predicate')
- self.assertEqual(predicates[10].__text__, 'classmethod predicate')
- self.assertEqual(predicates[11].__text__, '<unknown custom predicate>')
+ self.assertEqual(predicates[0].text(), 'xhr = True')
+ self.assertEqual(predicates[1].text(),
+ "request_method = request_method")
+ self.assertEqual(predicates[2].text(), 'path_info = path_info')
+ self.assertEqual(predicates[3].text(), 'request_param param')
+ self.assertEqual(predicates[4].text(), 'header header')
+ self.assertEqual(predicates[5].text(), 'accept = accept')
+ self.assertEqual(predicates[6].text(), 'containment = containment')
+ self.assertEqual(predicates[7].text(), 'request_type = request_type')
+ self.assertEqual(predicates[8].text(), "match_param foo=bar")
+ self.assertEqual(predicates[9].text(), 'custom predicate')
+ self.assertEqual(predicates[10].text(), 'classmethod predicate')
+ self.assertTrue(predicates[11].text().startswith('custom predicate'))
def test_match_param_from_string(self):
_, predicates, _ = self._callFUT(match_param='foo=bar')
@@ -328,15 +361,10 @@ class Test__make_predicates(unittest.TestCase):
hash2, _, __= self._callFUT(request_method='GET')
self.assertEqual(hash1, hash2)
- def test_match_param_hashable(self):
- # https://github.com/Pylons/pyramid/issues/425
- import pyramid.testing
- def view(request): pass
- config = pyramid.testing.setUp(autocommit=False)
- config.add_route('foo', '/foo/{a}/{b}')
- config.add_view(view, route_name='foo', match_param='a=bar')
- config.add_view(view, route_name='foo', match_param=('a=bar', 'b=baz'))
- config.commit()
+ def test_unknown_predicate(self):
+ from pyramid.exceptions import ConfigurationError
+ self.assertRaises(ConfigurationError, self._callFUT, unknown=1)
+
class TestActionInfo(unittest.TestCase):
def _getTargetClass(self):
@@ -368,6 +396,274 @@ class TestActionInfo(unittest.TestCase):
self.assertEqual(str(inst),
"Line 0 of file filename:\n linerepr ")
+class TestTopologicalSorter(unittest.TestCase):
+ def _makeOne(self, *arg, **kw):
+ from pyramid.config.util import TopologicalSorter
+ return TopologicalSorter(*arg, **kw)
+
+ def test_remove(self):
+ inst = self._makeOne()
+ inst.names.append('name')
+ inst.name2val['name'] = 1
+ inst.req_after.add('name')
+ inst.req_before.add('name')
+ inst.name2after['name'] = ('bob',)
+ inst.name2before['name'] = ('fred',)
+ inst.order.append(('bob', 'name'))
+ inst.order.append(('name', 'fred'))
+ inst.remove('name')
+ self.assertFalse(inst.names)
+ self.assertFalse(inst.req_before)
+ self.assertFalse(inst.req_after)
+ self.assertFalse(inst.name2before)
+ self.assertFalse(inst.name2after)
+ self.assertFalse(inst.name2val)
+ self.assertFalse(inst.order)
+
+ def test_add(self):
+ from pyramid.config.util import LAST
+ sorter = self._makeOne()
+ sorter.add('name', 'factory')
+ self.assertEqual(sorter.names, ['name'])
+ self.assertEqual(sorter.name2val,
+ {'name':'factory'})
+ self.assertEqual(sorter.order, [('name', LAST)])
+ sorter.add('name2', 'factory2')
+ self.assertEqual(sorter.names, ['name', 'name2'])
+ self.assertEqual(sorter.name2val,
+ {'name':'factory', 'name2':'factory2'})
+ self.assertEqual(sorter.order,
+ [('name', LAST), ('name2', LAST)])
+ sorter.add('name3', 'factory3', before='name2')
+ self.assertEqual(sorter.names,
+ ['name', 'name2', 'name3'])
+ self.assertEqual(sorter.name2val,
+ {'name':'factory', 'name2':'factory2',
+ 'name3':'factory3'})
+ self.assertEqual(sorter.order,
+ [('name', LAST), ('name2', LAST),
+ ('name3', 'name2')])
+
+ def test_sorted_ordering_1(self):
+ sorter = self._makeOne()
+ sorter.add('name1', 'factory1')
+ sorter.add('name2', 'factory2')
+ self.assertEqual(sorter.sorted(),
+ [
+ ('name1', 'factory1'),
+ ('name2', 'factory2'),
+ ])
+
+ def test_sorted_ordering_2(self):
+ from pyramid.config.util import FIRST
+ sorter = self._makeOne()
+ sorter.add('name1', 'factory1')
+ sorter.add('name2', 'factory2', after=FIRST)
+ self.assertEqual(sorter.sorted(),
+ [
+ ('name2', 'factory2'),
+ ('name1', 'factory1'),
+ ])
+
+ def test_sorted_ordering_3(self):
+ from pyramid.config.util import FIRST
+ sorter = self._makeOne()
+ add = sorter.add
+ add('auth', 'auth_factory', after='browserid')
+ add('dbt', 'dbt_factory')
+ add('retry', 'retry_factory', before='txnmgr', after='exceptionview')
+ add('browserid', 'browserid_factory')
+ add('txnmgr', 'txnmgr_factory', after='exceptionview')
+ add('exceptionview', 'excview_factory', after=FIRST)
+ self.assertEqual(sorter.sorted(),
+ [
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ('txnmgr', 'txnmgr_factory'),
+ ('dbt', 'dbt_factory'),
+ ('browserid', 'browserid_factory'),
+ ('auth', 'auth_factory'),
+ ])
+
+ def test_sorted_ordering_4(self):
+ from pyramid.config.util import FIRST
+ sorter = self._makeOne()
+ add = sorter.add
+ add('exceptionview', 'excview_factory', after=FIRST)
+ add('auth', 'auth_factory', after='browserid')
+ add('retry', 'retry_factory', before='txnmgr', after='exceptionview')
+ add('browserid', 'browserid_factory')
+ add('txnmgr', 'txnmgr_factory', after='exceptionview')
+ add('dbt', 'dbt_factory')
+ self.assertEqual(sorter.sorted(),
+ [
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ('txnmgr', 'txnmgr_factory'),
+ ('browserid', 'browserid_factory'),
+ ('auth', 'auth_factory'),
+ ('dbt', 'dbt_factory'),
+ ])
+
+ def test_sorted_ordering_5(self):
+ from pyramid.config.util import LAST, FIRST
+ sorter = self._makeOne()
+ add = sorter.add
+ add('exceptionview', 'excview_factory')
+ add('auth', 'auth_factory', after=FIRST)
+ add('retry', 'retry_factory', before='txnmgr', after='exceptionview')
+ add('browserid', 'browserid_factory', after=FIRST)
+ add('txnmgr', 'txnmgr_factory', after='exceptionview', before=LAST)
+ add('dbt', 'dbt_factory')
+ self.assertEqual(sorter.sorted(),
+ [
+ ('browserid', 'browserid_factory'),
+ ('auth', 'auth_factory'),
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ('txnmgr', 'txnmgr_factory'),
+ ('dbt', 'dbt_factory'),
+ ])
+
+ def test_sorted_ordering_missing_before_partial(self):
+ from pyramid.exceptions import ConfigurationError
+ sorter = self._makeOne()
+ add = sorter.add
+ add('dbt', 'dbt_factory')
+ add('auth', 'auth_factory', after='browserid')
+ add('retry', 'retry_factory', before='txnmgr', after='exceptionview')
+ add('browserid', 'browserid_factory')
+ self.assertRaises(ConfigurationError, sorter.sorted)
+
+ def test_sorted_ordering_missing_after_partial(self):
+ from pyramid.exceptions import ConfigurationError
+ sorter = self._makeOne()
+ add = sorter.add
+ add('dbt', 'dbt_factory')
+ add('auth', 'auth_factory', after='txnmgr')
+ add('retry', 'retry_factory', before='dbt', after='exceptionview')
+ add('browserid', 'browserid_factory')
+ self.assertRaises(ConfigurationError, sorter.sorted)
+
+ def test_sorted_ordering_missing_before_and_after_partials(self):
+ from pyramid.exceptions import ConfigurationError
+ sorter = self._makeOne()
+ add = sorter.add
+ add('dbt', 'dbt_factory')
+ add('auth', 'auth_factory', after='browserid')
+ add('retry', 'retry_factory', before='foo', after='txnmgr')
+ add('browserid', 'browserid_factory')
+ self.assertRaises(ConfigurationError, sorter.sorted)
+
+ def test_sorted_ordering_missing_before_partial_with_fallback(self):
+ from pyramid.config.util import LAST
+ sorter = self._makeOne()
+ add = sorter.add
+ add('exceptionview', 'excview_factory', before=LAST)
+ add('auth', 'auth_factory', after='browserid')
+ add('retry', 'retry_factory', before=('txnmgr', LAST),
+ after='exceptionview')
+ add('browserid', 'browserid_factory')
+ add('dbt', 'dbt_factory')
+ self.assertEqual(sorter.sorted(),
+ [
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ('browserid', 'browserid_factory'),
+ ('auth', 'auth_factory'),
+ ('dbt', 'dbt_factory'),
+ ])
+
+ def test_sorted_ordering_missing_after_partial_with_fallback(self):
+ from pyramid.config.util import FIRST
+ sorter = self._makeOne()
+ add = sorter.add
+ add('exceptionview', 'excview_factory', after=FIRST)
+ add('auth', 'auth_factory', after=('txnmgr','browserid'))
+ add('retry', 'retry_factory', after='exceptionview')
+ add('browserid', 'browserid_factory')
+ add('dbt', 'dbt_factory')
+ self.assertEqual(sorter.sorted(),
+ [
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ('browserid', 'browserid_factory'),
+ ('auth', 'auth_factory'),
+ ('dbt', 'dbt_factory'),
+ ])
+
+ def test_sorted_ordering_with_partial_fallbacks(self):
+ from pyramid.config.util import LAST
+ sorter = self._makeOne()
+ add = sorter.add
+ add('exceptionview', 'excview_factory', before=('wontbethere', LAST))
+ add('retry', 'retry_factory', after='exceptionview')
+ add('browserid', 'browserid_factory', before=('wont2', 'exceptionview'))
+ self.assertEqual(sorter.sorted(),
+ [
+ ('browserid', 'browserid_factory'),
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ])
+
+ def test_sorted_ordering_with_multiple_matching_fallbacks(self):
+ from pyramid.config.util import LAST
+ sorter = self._makeOne()
+ add = sorter.add
+ add('exceptionview', 'excview_factory', before=LAST)
+ add('retry', 'retry_factory', after='exceptionview')
+ add('browserid', 'browserid_factory', before=('retry', 'exceptionview'))
+ self.assertEqual(sorter.sorted(),
+ [
+ ('browserid', 'browserid_factory'),
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ])
+
+ def test_sorted_ordering_with_missing_fallbacks(self):
+ from pyramid.exceptions import ConfigurationError
+ from pyramid.config.util import LAST
+ sorter = self._makeOne()
+ add = sorter.add
+ add('exceptionview', 'excview_factory', before=LAST)
+ add('retry', 'retry_factory', after='exceptionview')
+ add('browserid', 'browserid_factory', before=('txnmgr', 'auth'))
+ self.assertRaises(ConfigurationError, sorter.sorted)
+
+ def test_sorted_ordering_conflict_direct(self):
+ from pyramid.config.util import CyclicDependencyError
+ sorter = self._makeOne()
+ add = sorter.add
+ add('browserid', 'browserid_factory')
+ add('auth', 'auth_factory', before='browserid', after='browserid')
+ self.assertRaises(CyclicDependencyError, sorter.sorted)
+
+ def test_sorted_ordering_conflict_indirect(self):
+ from pyramid.config.util import CyclicDependencyError
+ sorter = self._makeOne()
+ add = sorter.add
+ add('browserid', 'browserid_factory')
+ add('auth', 'auth_factory', before='browserid')
+ add('dbt', 'dbt_factory', after='browserid', before='auth')
+ self.assertRaises(CyclicDependencyError, sorter.sorted)
+
+class TestSingleton(unittest.TestCase):
+ def test_repr(self):
+ from pyramid.config.util import Singleton
+ r = repr(Singleton('ABC'))
+ self.assertEqual(r, 'ABC')
+
+class TestCyclicDependencyError(unittest.TestCase):
+ def _makeOne(self, cycles):
+ from pyramid.config.util import CyclicDependencyError
+ return CyclicDependencyError(cycles)
+
+ def test___str__(self):
+ exc = self._makeOne({'a':['c', 'd'], 'c':['a']})
+ result = str(exc)
+ self.assertTrue("'a' sorts before ['c', 'd']" in result)
+ self.assertTrue("'c' sorts before ['a']" in result)
+
class DummyCustomPredicate(object):
def __init__(self):
self.__text__ = 'custom predicate'
@@ -392,3 +688,7 @@ class DummyRequest:
self.params = {}
self.cookies = {}
+class DummyConfigurator(object):
+ def maybe_dotted(self, thing):
+ return thing
+
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index 9b46f83c9..38f60d79b 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -118,7 +118,8 @@ class TestViewsConfigurationMixin(unittest.TestCase):
self.assertEqual(wrapper.__module__, view.__module__)
self.assertEqual(wrapper.__name__, view.__name__)
self.assertEqual(wrapper.__doc__, view.__doc__)
- self.assertEqual(wrapper.__discriminator__(None, None)[0], 'view')
+ self.assertEqual(wrapper.__discriminator__(None, None).resolve()[0],
+ 'view')
def test_add_view_view_callable_dottedname(self):
from pyramid.renderers import null_renderer
@@ -400,7 +401,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
from pyramid.interfaces import IViewClassifier
from pyramid.interfaces import IMultiView
phash = md5()
- phash.update(b'xhr:True')
+ phash.update(b'xhr = True')
view = lambda *arg: 'NOT OK'
view.__phash__ = phash.hexdigest()
config = self._makeOne(autocommit=True)
@@ -424,7 +425,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
from pyramid.interfaces import IExceptionViewClassifier
from pyramid.interfaces import IMultiView
phash = md5()
- phash.update(b'xhr:True')
+ phash.update(b'xhr = True')
view = lambda *arg: 'NOT OK'
view.__phash__ = phash.hexdigest()
config = self._makeOne(autocommit=True)
@@ -970,8 +971,10 @@ class TestViewsConfigurationMixin(unittest.TestCase):
wrapper = self._getViewCallable(config)
self.assertTrue(IMultiView.providedBy(wrapper))
request = self._makeRequest(config)
- self.assertEqual(wrapper.__discriminator__(foo, request)[5], IFoo)
- self.assertEqual(wrapper.__discriminator__(bar, request)[5], IBar)
+ self.assertNotEqual(
+ wrapper.__discriminator__(foo, request),
+ wrapper.__discriminator__(bar, request),
+ )
def test_add_view_with_template_renderer(self):
from pyramid.tests import test_config
@@ -1217,8 +1220,8 @@ class TestViewsConfigurationMixin(unittest.TestCase):
def test_add_view_with_header_badregex(self):
view = lambda *arg: 'OK'
config = self._makeOne()
- self.assertRaises(ConfigurationError,
- config.add_view, view=view, header='Host:a\\')
+ config.add_view(view, header='Host:a\\')
+ self.assertRaises(ConfigurationError, config.commit)
def test_add_view_with_header_noval_match(self):
from pyramid.renderers import null_renderer
@@ -1323,8 +1326,8 @@ class TestViewsConfigurationMixin(unittest.TestCase):
def test_add_view_with_path_info_badregex(self):
view = lambda *arg: 'OK'
config = self._makeOne()
- self.assertRaises(ConfigurationError,
- config.add_view, view=view, path_info='\\')
+ config.add_view(view, path_info='\\')
+ self.assertRaises(ConfigurationError, config.commit)
def test_add_view_with_path_info_match(self):
from pyramid.renderers import null_renderer
@@ -1698,6 +1701,38 @@ class TestViewsConfigurationMixin(unittest.TestCase):
result = view(None, request)
self.assertEqual(result, 'OK')
+ def test_add_forbidden_view_allows_other_predicates(self):
+ from pyramid.renderers import null_renderer
+ config = self._makeOne(autocommit=True)
+ # doesnt blow up
+ config.add_view_predicate('dummy', DummyPredicate)
+ config.add_forbidden_view(renderer=null_renderer, dummy='abc')
+
+ def test_add_forbidden_view_disallows_name(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_forbidden_view, name='foo')
+
+ def test_add_forbidden_view_disallows_permission(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_forbidden_view, permission='foo')
+
+ def test_add_forbidden_view_disallows_context(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_forbidden_view, context='foo')
+
+ def test_add_forbidden_view_disallows_for_(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_forbidden_view, for_='foo')
+
+ def test_add_forbidden_view_disallows_http_cache(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_forbidden_view, http_cache='foo')
+
def test_add_notfound_view(self):
from pyramid.renderers import null_renderer
from zope.interface import implementedBy
@@ -1713,6 +1748,38 @@ class TestViewsConfigurationMixin(unittest.TestCase):
result = view(None, request)
self.assertEqual(result, (None, request))
+ def test_add_notfound_view_allows_other_predicates(self):
+ from pyramid.renderers import null_renderer
+ config = self._makeOne(autocommit=True)
+ # doesnt blow up
+ config.add_view_predicate('dummy', DummyPredicate)
+ config.add_notfound_view(renderer=null_renderer, dummy='abc')
+
+ def test_add_notfound_view_disallows_name(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_notfound_view, name='foo')
+
+ def test_add_notfound_view_disallows_permission(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_notfound_view, permission='foo')
+
+ def test_add_notfound_view_disallows_context(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_notfound_view, context='foo')
+
+ def test_add_notfound_view_disallows_for_(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_notfound_view, for_='foo')
+
+ def test_add_notfound_view_disallows_http_cache(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(ConfigurationError,
+ config.add_notfound_view, http_cache='foo')
+
def test_add_notfound_view_append_slash(self):
from pyramid.response import Response
from pyramid.renderers import null_renderer
@@ -2905,6 +2972,7 @@ class TestViewDeriver(unittest.TestCase):
view = lambda *arg: response
def predicate1(context, request):
return False
+ predicate1.text = lambda *arg: 'text'
deriver = self._makeOne(predicates=[predicate1])
result = deriver(view)
request = self._makeRequest()
@@ -2912,7 +2980,8 @@ class TestViewDeriver(unittest.TestCase):
try:
result(None, None)
except PredicateMismatch as e:
- self.assertEqual(e.detail, 'predicate mismatch for view <lambda>')
+ self.assertEqual(e.detail,
+ 'predicate mismatch for view <lambda> (text)')
else: # pragma: no cover
raise AssertionError
@@ -2921,6 +2990,7 @@ class TestViewDeriver(unittest.TestCase):
def myview(request): pass
def predicate1(context, request):
return False
+ predicate1.text = lambda *arg: 'text'
deriver = self._makeOne(predicates=[predicate1])
result = deriver(myview)
request = self._makeRequest()
@@ -2928,7 +2998,29 @@ class TestViewDeriver(unittest.TestCase):
try:
result(None, None)
except PredicateMismatch as e:
- self.assertEqual(e.detail, 'predicate mismatch for view myview')
+ self.assertEqual(e.detail,
+ 'predicate mismatch for view myview (text)')
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_predicate_mismatch_exception_has_text_in_detail(self):
+ from pyramid.exceptions import PredicateMismatch
+ def myview(request): pass
+ def predicate1(context, request):
+ return True
+ predicate1.text = lambda *arg: 'pred1'
+ def predicate2(context, request):
+ return False
+ predicate2.text = lambda *arg: 'pred2'
+ deriver = self._makeOne(predicates=[predicate1, predicate2])
+ result = deriver(myview)
+ request = self._makeRequest()
+ request.method = 'POST'
+ try:
+ result(None, None)
+ except PredicateMismatch as e:
+ self.assertEqual(e.detail,
+ 'predicate mismatch for view myview (pred2)')
else: # pragma: no cover
raise AssertionError
@@ -2974,9 +3066,11 @@ class TestViewDeriver(unittest.TestCase):
def predicate1(context, request):
predicates.append(True)
return True
+ predicate1.text = lambda *arg: 'text'
def predicate2(context, request):
predicates.append(True)
return False
+ predicate2.text = lambda *arg: 'text'
deriver = self._makeOne(predicates=[predicate1, predicate2])
result = deriver(view)
request = self._makeRequest()
@@ -3943,3 +4037,13 @@ class DummyViewDefaultsClass(object):
pass
def __call__(self):
return 'OK'
+
+class DummyPredicate(object):
+ def __init__(self, val, config):
+ self.val = val
+
+ def text(self):
+ return 'dummy'
+
+ phash = text
+
diff --git a/pyramid/tests/test_events.py b/pyramid/tests/test_events.py
index f35083c02..3e9c959d9 100644
--- a/pyramid/tests/test_events.py
+++ b/pyramid/tests/test_events.py
@@ -9,13 +9,13 @@ class NewRequestEventTests(unittest.TestCase):
def _makeOne(self, request):
return self._getTargetClass()(request)
- def test_class_implements(self):
+ def test_class_conforms_to_INewRequest(self):
from pyramid.interfaces import INewRequest
from zope.interface.verify import verifyClass
klass = self._getTargetClass()
verifyClass(INewRequest, klass)
- def test_instance_implements(self):
+ def test_instance_conforms_to_INewRequest(self):
from pyramid.interfaces import INewRequest
from zope.interface.verify import verifyObject
request = DummyRequest()
@@ -35,13 +35,13 @@ class NewResponseEventTests(unittest.TestCase):
def _makeOne(self, request, response):
return self._getTargetClass()(request, response)
- def test_class_implements(self):
+ def test_class_conforms_to_INewResponse(self):
from pyramid.interfaces import INewResponse
from zope.interface.verify import verifyClass
klass = self._getTargetClass()
verifyClass(INewResponse, klass)
- def test_instance_implements(self):
+ def test_instance_conforms_to_INewResponse(self):
from pyramid.interfaces import INewResponse
from zope.interface.verify import verifyObject
request = DummyRequest()
@@ -57,68 +57,72 @@ class NewResponseEventTests(unittest.TestCase):
self.assertEqual(inst.response, response)
class ApplicationCreatedEventTests(unittest.TestCase):
- def test_alias_object_implements(self):
- from pyramid.events import WSGIApplicationCreatedEvent
- event = WSGIApplicationCreatedEvent(object())
- from pyramid.interfaces import IWSGIApplicationCreatedEvent
- from pyramid.interfaces import IApplicationCreated
- from zope.interface.verify import verifyObject
- verifyObject(IWSGIApplicationCreatedEvent, event)
- verifyObject(IApplicationCreated, event)
+ def _getTargetClass(self):
+ from pyramid.events import ApplicationCreated
+ return ApplicationCreated
- def test_alias_class_implements(self):
- from pyramid.events import WSGIApplicationCreatedEvent
- from pyramid.interfaces import IWSGIApplicationCreatedEvent
+ def _makeOne(self, context=object()):
+ return self._getTargetClass()(context)
+
+ def test_class_conforms_to_IApplicationCreated(self):
from pyramid.interfaces import IApplicationCreated
from zope.interface.verify import verifyClass
- verifyClass(IWSGIApplicationCreatedEvent, WSGIApplicationCreatedEvent)
- verifyClass(IApplicationCreated, WSGIApplicationCreatedEvent)
+ verifyClass(IApplicationCreated, self._getTargetClass())
- def test_object_implements(self):
- from pyramid.events import ApplicationCreated
- event = ApplicationCreated(object())
+ def test_object_conforms_to_IApplicationCreated(self):
from pyramid.interfaces import IApplicationCreated
from zope.interface.verify import verifyObject
- verifyObject(IApplicationCreated, event)
+ verifyObject(IApplicationCreated, self._makeOne())
- def test_class_implements(self):
- from pyramid.events import ApplicationCreated
- from pyramid.interfaces import IApplicationCreated
+class WSGIApplicationCreatedEventTests(ApplicationCreatedEventTests):
+ def _getTargetClass(self):
+ from pyramid.events import WSGIApplicationCreatedEvent
+ return WSGIApplicationCreatedEvent
+
+ def test_class_conforms_to_IWSGIApplicationCreatedEvent(self):
+ from pyramid.interfaces import IWSGIApplicationCreatedEvent
from zope.interface.verify import verifyClass
- verifyClass(IApplicationCreated, ApplicationCreated)
+ verifyClass(IWSGIApplicationCreatedEvent, self._getTargetClass())
+
+ def test_object_conforms_to_IWSGIApplicationCreatedEvent(self):
+ from pyramid.interfaces import IWSGIApplicationCreatedEvent
+ from zope.interface.verify import verifyObject
+ verifyObject(IWSGIApplicationCreatedEvent, self._makeOne())
class ContextFoundEventTests(unittest.TestCase):
- def test_alias_class_implements(self):
+ def _getTargetClass(self):
+ from pyramid.events import ContextFound
+ return ContextFound
+
+ def _makeOne(self, request=None):
+ if request is None:
+ request = DummyRequest()
+ return self._getTargetClass()(request)
+
+ def test_class_conforms_to_IContextFound(self):
from zope.interface.verify import verifyClass
- from pyramid.events import AfterTraversal
- from pyramid.interfaces import IAfterTraversal
from pyramid.interfaces import IContextFound
- verifyClass(IAfterTraversal, AfterTraversal)
- verifyClass(IContextFound, AfterTraversal)
+ verifyClass(IContextFound, self._getTargetClass())
- def test_alias_instance_implements(self):
+ def test_instance_conforms_to_IContextFound(self):
from zope.interface.verify import verifyObject
- from pyramid.events import AfterTraversal
- from pyramid.interfaces import IAfterTraversal
from pyramid.interfaces import IContextFound
- request = DummyRequest()
- inst = AfterTraversal(request)
- verifyObject(IAfterTraversal, inst)
- verifyObject(IContextFound, inst)
+ verifyObject(IContextFound, self._makeOne())
- def test_class_implements(self):
+class AfterTraversalEventTests(ContextFoundEventTests):
+ def _getTargetClass(self):
+ from pyramid.events import AfterTraversal
+ return AfterTraversal
+
+ def test_class_conforms_to_IAfterTraversal(self):
from zope.interface.verify import verifyClass
- from pyramid.events import ContextFound
- from pyramid.interfaces import IContextFound
- verifyClass(IContextFound, ContextFound)
+ from pyramid.interfaces import IAfterTraversal
+ verifyClass(IAfterTraversal, self._getTargetClass())
- def test_instance_implements(self):
+ def test_instance_conforms_to_IAfterTraversal(self):
from zope.interface.verify import verifyObject
- from pyramid.events import ContextFound
- from pyramid.interfaces import IContextFound
- request = DummyRequest()
- inst = ContextFound(request)
- verifyObject(IContextFound, inst)
+ from pyramid.interfaces import IAfterTraversal
+ verifyObject(IAfterTraversal, self._makeOne())
class TestSubscriber(unittest.TestCase):
def setUp(self):
diff --git a/pyramid/tests/test_mako_templating.py b/pyramid/tests/test_mako_templating.py
index fbb04273b..97b2c679b 100644
--- a/pyramid/tests/test_mako_templating.py
+++ b/pyramid/tests/test_mako_templating.py
@@ -135,7 +135,7 @@ class Test_renderer_factory(Base, unittest.TestCase):
self._callFUT(info)
lookup = self._getLookup()
self.assertEqual(lookup.template_args['input_encoding'], 'utf-16')
-
+
def test_with_error_handler(self):
settings = {'mako.directories':self.templates_dir,
'mako.error_handler':'pyramid.tests'}
@@ -315,7 +315,7 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase):
def test_instance_implements_ITemplate(self):
from zope.interface.verify import verifyObject
from pyramid.interfaces import ITemplateRenderer
- verifyObject(ITemplateRenderer, self._makeOne(None, None))
+ verifyObject(ITemplateRenderer, self._makeOne(None, None, None))
def test_class_implements_ITemplate(self):
from zope.interface.verify import verifyClass
@@ -324,7 +324,7 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase):
def test_call(self):
lookup = DummyLookup()
- instance = self._makeOne('path', lookup)
+ instance = self._makeOne('path', None, lookup)
result = instance({}, {'system':1})
self.assertTrue(isinstance(result, text_type))
self.assertEqual(result, text_('result'))
@@ -332,7 +332,7 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase):
def test_call_with_system_context(self):
# lame
lookup = DummyLookup()
- instance = self._makeOne('path', lookup)
+ instance = self._makeOne('path', None, lookup)
result = instance({}, {'context':1})
self.assertTrue(isinstance(result, text_type))
self.assertEqual(result, text_('result'))
@@ -340,21 +340,36 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase):
def test_call_with_tuple_value(self):
lookup = DummyLookup()
- instance = self._makeOne('path', lookup)
+ instance = self._makeOne('path', None, lookup)
result = instance(('fub', {}), {'context':1})
self.assertEqual(lookup.deffed, 'fub')
self.assertEqual(result, text_('result'))
self.assertEqual(lookup.values, {'_context':1})
+ def test_call_with_defname(self):
+ lookup = DummyLookup()
+ instance = self._makeOne('path', 'defname', lookup)
+ result = instance({}, {'system':1})
+ self.assertTrue(isinstance(result, text_type))
+ self.assertEqual(result, text_('result'))
+
+ def test_call_with_defname_with_tuple_value(self):
+ lookup = DummyLookup()
+ instance = self._makeOne('path', 'defname', lookup)
+ result = instance(('defname', {}), {'context':1})
+ self.assertEqual(lookup.deffed, 'defname')
+ self.assertEqual(result, text_('result'))
+ self.assertEqual(lookup.values, {'_context':1})
+
def test_call_with_nondict_value(self):
lookup = DummyLookup()
- instance = self._makeOne('path', lookup)
+ instance = self._makeOne('path', None, lookup)
self.assertRaises(ValueError, instance, None, {})
def test_call_render_raises(self):
from pyramid.mako_templating import MakoRenderingException
lookup = DummyLookup(exc=NotImplementedError)
- instance = self._makeOne('path', lookup)
+ instance = self._makeOne('path', None, lookup)
try:
instance({}, {})
except MakoRenderingException as e:
@@ -364,11 +379,11 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase):
def test_implementation(self):
lookup = DummyLookup()
- instance = self._makeOne('path', lookup)
+ instance = self._makeOne('path', None, lookup)
result = instance.implementation().render_unicode()
self.assertTrue(isinstance(result, text_type))
self.assertEqual(result, text_('result'))
-
+
class TestIntegration(unittest.TestCase):
def setUp(self):
import pyramid.mako_templating
@@ -391,7 +406,7 @@ class TestIntegration(unittest.TestCase):
self.config.add_settings({'reload_templates': True})
result = render('helloworld.mak', {'a':1}).replace('\r','')
self.assertEqual(result, text_('\nHello föö\n', 'utf-8'))
-
+
def test_render_inheritance(self):
from pyramid.renderers import render
result = render('helloinherit.mak', {}).replace('\r','')
@@ -402,6 +417,11 @@ class TestIntegration(unittest.TestCase):
result = render('hello_inherit_pkg.mak', {}).replace('\r','')
self.assertEqual(result, text_('Layout\nHello World!\n'))
+ def test_render_namespace(self):
+ from pyramid.renderers import render
+ result = render('hellocompo.mak', {}).replace('\r','')
+ self.assertEqual(result, text_('\nNamespace\nHello \nWorld!\n'))
+
def test_render_to_response(self):
from pyramid.renderers import render_to_response
result = render_to_response('helloworld.mak', {'a':1})
@@ -414,7 +434,7 @@ class TestIntegration(unittest.TestCase):
{'a':1})
self.assertEqual(result.ubody.replace('\r', ''),
text_('\nHello föö\n', 'utf-8'))
-
+
def test_render_with_abs_path(self):
from pyramid.renderers import render
result = render('/helloworld.mak', {'a':1}).replace('\r','')
@@ -426,7 +446,7 @@ class TestIntegration(unittest.TestCase):
self.assertEqual(
result.implementation().render_unicode().replace('\r',''),
text_('\nHello föö\n', 'utf-8'))
-
+
def test_template_not_found(self):
from pyramid.renderers import render
from mako.exceptions import TemplateLookupException
@@ -459,12 +479,42 @@ class TestPkgResourceTemplateLookup(unittest.TestCase):
result = inst.adjust_uri('a:b', None)
self.assertEqual(result, 'a:b')
+ def test_adjust_uri_asset_spec_with_modified_asset_spec(self):
+ inst = self._makeOne()
+ result = inst.adjust_uri('a$b', None)
+ self.assertEqual(result, 'a:b')
+
+ def test_adjust_uri_not_asset_spec_with_relativeto_asset_spec(self):
+ inst = self._makeOne()
+ result = inst.adjust_uri('c', 'a:b')
+ self.assertEqual(result, 'a:c')
+
+ def test_adjust_uri_not_asset_spec_with_relativeto_modified_asset_spec(self):
+ inst = self._makeOne()
+ result = inst.adjust_uri('c', 'a$b')
+ self.assertEqual(result, 'a:c')
+
+ def test_adjust_uri_not_asset_spec_with_relativeto_not_asset_spec(self):
+ inst = self._makeOne()
+ result = inst.adjust_uri('b', '../a')
+ self.assertEqual(result, '../b')
+
+ def test_adjust_uri_not_asset_spec_abs_with_relativeto_asset_spec(self):
+ inst = self._makeOne()
+ result = inst.adjust_uri('/c', 'a:b')
+ self.assertEqual(result, '/c')
+
+ def test_adjust_uri_asset_spec_with_relativeto_not_asset_spec_abs(self):
+ inst = self._makeOne()
+ result = inst.adjust_uri('a:b', '/c')
+ self.assertEqual(result, 'a:b')
+
def test_get_template_not_asset_spec(self):
fixturedir = self.get_fixturedir()
inst = self._makeOne(directories=[fixturedir])
result = inst.get_template('helloworld.mak')
self.assertFalse(result is None)
-
+
def test_get_template_asset_spec_with_filesystem_checks(self):
inst = self._makeOne(filesystem_checks=True)
result = inst.get_template('pyramid.tests:fixtures/helloworld.mak')
@@ -478,7 +528,7 @@ class TestPkgResourceTemplateLookup(unittest.TestCase):
self.assertFalse(result is None)
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
-
+
def test_get_template_asset_spec_missing(self):
from mako.exceptions import TopLevelLookupException
fixturedir = self.get_fixturedir()
@@ -490,7 +540,7 @@ class TestMakoRenderingException(unittest.TestCase):
def _makeOne(self, text):
from pyramid.mako_templating import MakoRenderingException
return MakoRenderingException(text)
-
+
def test_repr_and_str(self):
exc = self._makeOne('text')
self.assertEqual(str(exc), 'text')
@@ -499,7 +549,7 @@ class TestMakoRenderingException(unittest.TestCase):
class DummyLookup(object):
def __init__(self, exc=None):
self.exc = exc
-
+
def get_template(self, path):
self.path = path
return self
@@ -513,8 +563,8 @@ class DummyLookup(object):
raise self.exc
self.values = values
return text_('result')
-
+
class DummyRendererInfo(object):
def __init__(self, kw):
self.__dict__.update(kw)
-
+
diff --git a/pyramid/tests/test_path.py b/pyramid/tests/test_path.py
index 42b38d785..ccc56fb0d 100644
--- a/pyramid/tests/test_path.py
+++ b/pyramid/tests/test_path.py
@@ -259,17 +259,15 @@ class TestPkgResourcesAssetDescriptor(unittest.TestCase):
def _makeOne(self, pkg='pyramid.tests', path='test_asset.py'):
return self._getTargetClass()(pkg, path)
- def test_class_implements(self):
+ def test_class_conforms_to_IAssetDescriptor(self):
from pyramid.interfaces import IAssetDescriptor
from zope.interface.verify import verifyClass
- klass = self._getTargetClass()
- verifyClass(IAssetDescriptor, klass)
+ verifyClass(IAssetDescriptor, self._getTargetClass())
- def test_instance_implements(self):
+ def test_instance_conforms_to_IAssetDescriptor(self):
from pyramid.interfaces import IAssetDescriptor
from zope.interface.verify import verifyObject
- inst = self._makeOne()
- verifyObject(IAssetDescriptor, inst)
+ verifyObject(IAssetDescriptor, self._makeOne())
def test_absspec(self):
inst = self._makeOne()
@@ -316,17 +314,15 @@ class TestFSAssetDescriptor(unittest.TestCase):
def _makeOne(self, path=os.path.join(here, 'test_asset.py')):
return self._getTargetClass()(path)
- def test_class_implements(self):
+ def test_class_conforms_to_IAssetDescriptor(self):
from pyramid.interfaces import IAssetDescriptor
from zope.interface.verify import verifyClass
- klass = self._getTargetClass()
- verifyClass(IAssetDescriptor, klass)
+ verifyClass(IAssetDescriptor, self._getTargetClass())
- def test_instance_implements(self):
+ def test_instance_conforms_to_IAssetDescriptor(self):
from pyramid.interfaces import IAssetDescriptor
from zope.interface.verify import verifyObject
- inst = self._makeOne()
- verifyObject(IAssetDescriptor, inst)
+ verifyObject(IAssetDescriptor, self._makeOne())
def test_absspec(self):
inst = self._makeOne()
diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py
index 6d5131013..a95d614f9 100644
--- a/pyramid/tests/test_request.py
+++ b/pyramid/tests/test_request.py
@@ -18,13 +18,15 @@ class TestRequest(unittest.TestCase):
def tearDown(self):
testing.tearDown()
- def _makeOne(self, environ):
- return self._getTargetClass()(environ)
-
def _getTargetClass(self):
from pyramid.request import Request
return Request
+ def _makeOne(self, environ=None):
+ if environ is None:
+ environ = {}
+ return self._getTargetClass()(environ)
+
def _registerResourceURL(self):
from pyramid.interfaces import IResourceURL
from zope.interface import Interface
@@ -36,6 +38,17 @@ class TestRequest(unittest.TestCase):
DummyResourceURL, (Interface, Interface),
IResourceURL)
+ def test_class_conforms_to_IRequest(self):
+ from zope.interface.verify import verifyClass
+ from pyramid.interfaces import IRequest
+ verifyClass(IRequest, self._getTargetClass())
+ klass = self._getTargetClass()
+
+ def test_instance_conforms_to_IRequest(self):
+ from zope.interface.verify import verifyObject
+ from pyramid.interfaces import IRequest
+ verifyObject(IRequest, self._makeOne())
+
def test_charset_defaults_to_utf8(self):
r = self._makeOne({'PATH_INFO':'/'})
self.assertEqual(r.charset, 'UTF-8')
@@ -61,25 +74,15 @@ class TestRequest(unittest.TestCase):
request.charset = None
self.assertEqual(request.GET['la'], text_(b'La Pe\xf1a'))
- def test_class_implements(self):
- from pyramid.interfaces import IRequest
- klass = self._getTargetClass()
- self.assertTrue(IRequest.implementedBy(klass))
-
- def test_instance_provides(self):
- from pyramid.interfaces import IRequest
- inst = self._makeOne({})
- self.assertTrue(IRequest.providedBy(inst))
-
def test_tmpl_context(self):
from pyramid.request import TemplateContext
- inst = self._makeOne({})
+ inst = self._makeOne()
result = inst.tmpl_context
self.assertEqual(result.__class__, TemplateContext)
def test_session_configured(self):
from pyramid.interfaces import ISessionFactory
- inst = self._makeOne({})
+ inst = self._makeOne()
def factory(request):
return 'orangejuice'
self.config.registry.registerUtility(factory, ISessionFactory)
@@ -88,12 +91,12 @@ class TestRequest(unittest.TestCase):
self.assertEqual(inst.__dict__['session'], 'orangejuice')
def test_session_not_configured(self):
- inst = self._makeOne({})
+ inst = self._makeOne()
inst.registry = self.config.registry
self.assertRaises(AttributeError, getattr, inst, 'session')
def test_setattr_and_getattr_dotnotation(self):
- inst = self._makeOne({})
+ inst = self._makeOne()
inst.foo = 1
self.assertEqual(inst.foo, 1)
@@ -105,7 +108,7 @@ class TestRequest(unittest.TestCase):
self.assertEqual(environ, {}) # make sure we're not using adhoc attrs
def test_add_response_callback(self):
- inst = self._makeOne({})
+ inst = self._makeOne()
self.assertEqual(inst.response_callbacks, ())
def callback(request, response):
""" """
@@ -115,7 +118,7 @@ class TestRequest(unittest.TestCase):
self.assertEqual(inst.response_callbacks, [callback, callback])
def test__process_response_callbacks(self):
- inst = self._makeOne({})
+ inst = self._makeOne()
def callback1(request, response):
request.called1 = True
response.called1 = True
@@ -132,7 +135,7 @@ class TestRequest(unittest.TestCase):
self.assertEqual(inst.response_callbacks, [])
def test_add_finished_callback(self):
- inst = self._makeOne({})
+ inst = self._makeOne()
self.assertEqual(inst.finished_callbacks, ())
def callback(request):
""" """
@@ -142,7 +145,7 @@ class TestRequest(unittest.TestCase):
self.assertEqual(inst.finished_callbacks, [callback, callback])
def test__process_finished_callbacks(self):
- inst = self._makeOne({})
+ inst = self._makeOne()
def callback1(request):
request.called1 = True
def callback2(request):
@@ -219,13 +222,13 @@ class TestRequest(unittest.TestCase):
('pyramid.tests:static/foo.css', request, {}) )
def test_is_response_false(self):
- request = self._makeOne({})
+ request = self._makeOne()
request.registry = self.config.registry
self.assertEqual(request.is_response('abc'), False)
def test_is_response_false_adapter_is_not_self(self):
from pyramid.interfaces import IResponse
- request = self._makeOne({})
+ request = self._makeOne()
request.registry = self.config.registry
def adapter(ob):
return object()
@@ -237,7 +240,7 @@ class TestRequest(unittest.TestCase):
def test_is_response_adapter_true(self):
from pyramid.interfaces import IResponse
- request = self._makeOne({})
+ request = self._makeOne()
request.registry = self.config.registry
class Foo(object):
pass
@@ -277,7 +280,7 @@ class TestRequest(unittest.TestCase):
self.assertRaises(ValueError, getattr, request, 'json_body')
def test_set_property(self):
- request = self._makeOne({})
+ request = self._makeOne()
opts = [2, 1]
def connect(obj):
return opts.pop()
@@ -286,7 +289,7 @@ class TestRequest(unittest.TestCase):
self.assertEqual(2, request.db)
def test_set_property_reify(self):
- request = self._makeOne({})
+ request = self._makeOne()
opts = [2, 1]
def connect(obj):
return opts.pop()
diff --git a/pyramid/tests/test_scaffolds/test_copydir.py b/pyramid/tests/test_scaffolds/test_copydir.py
index 68cefbe6e..d757b837c 100644
--- a/pyramid/tests/test_scaffolds/test_copydir.py
+++ b/pyramid/tests/test_scaffolds/test_copydir.py
@@ -176,6 +176,14 @@ class Test_makedirs(unittest.TestCase):
self._callFUT(target, 2, None)
shutil.rmtree(tmpdir)
+ def test_makedirs_no_parent_dir(self):
+ import shutil
+ import tempfile
+ tmpdir = tempfile.mkdtemp()
+ target = os.path.join(tmpdir, 'nonexistent_subdir', 'non2')
+ self._callFUT(target, 2, None)
+ shutil.rmtree(tmpdir)
+
class Test_support_functions(unittest.TestCase):
def _call_html_quote(self, *arg, **kw):
diff --git a/pyramid/tests/test_scripts/test_pviews.py b/pyramid/tests/test_scripts/test_pviews.py
index 680d48cee..6a919c31b 100644
--- a/pyramid/tests/test_scripts/test_pviews.py
+++ b/pyramid/tests/test_scripts/test_pviews.py
@@ -309,7 +309,7 @@ class TestPViewsCommand(unittest.TestCase):
L = []
command.out = L.append
def predicate(): pass
- predicate.__text__ = "predicate = x"
+ predicate.text = lambda *arg: "predicate = x"
view = dummy.DummyView(context='context', view_name='a')
view.__predicates__ = [predicate]
command._find_view = lambda arg1, arg2: view
@@ -448,7 +448,7 @@ class TestPViewsCommand(unittest.TestCase):
L = []
command.out = L.append
def predicate(): pass
- predicate.__text__ = "predicate = x"
+ predicate.text = lambda *arg: "predicate = x"
view = dummy.DummyView(context='context')
view.__name__ = 'view'
view.__view_attr__ = 'call'
diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py
index 5b0073b81..a9e50442f 100644
--- a/pyramid/tests/test_testing.py
+++ b/pyramid/tests/test_testing.py
@@ -253,6 +253,7 @@ class Test_registerSubscriber(TestBase):
class Test_registerRoute(TestBase):
def test_registerRoute(self):
+ from pyramid.config import Configurator
from pyramid.request import Request
from pyramid.interfaces import IRoutesMapper
from pyramid.testing import registerRoute
@@ -261,6 +262,8 @@ class Test_registerRoute(TestBase):
self.assertEqual(len(mapper.routelist), 1)
request = Request.blank('/')
request.registry = self.registry
+ config = Configurator(registry=self.registry)
+ config.setup_registry()
self.assertEqual(request.route_url('home', pagename='abc'),
'http://localhost/abc')
diff --git a/pyramid/tests/test_url.py b/pyramid/tests/test_url.py
index 50deb63f3..a7a565356 100644
--- a/pyramid/tests/test_url.py
+++ b/pyramid/tests/test_url.py
@@ -2,10 +2,8 @@ import os
import unittest
import warnings
-from pyramid.testing import (
- setUp,
- tearDown,
- )
+from pyramid import testing
+
from pyramid.compat import (
text_,
native_,
@@ -14,10 +12,10 @@ from pyramid.compat import (
class TestURLMethodsMixin(unittest.TestCase):
def setUp(self):
- self.config = setUp()
+ self.config = testing.setUp()
def tearDown(self):
- tearDown()
+ testing.tearDown()
def _makeOne(self, environ=None):
from pyramid.url import URLMethodsMixin
diff --git a/pyramid/tests/test_urldispatch.py b/pyramid/tests/test_urldispatch.py
index e15242f75..b2164717e 100644
--- a/pyramid/tests/test_urldispatch.py
+++ b/pyramid/tests/test_urldispatch.py
@@ -311,6 +311,14 @@ class TestCompileRoute(unittest.TestCase):
self.assertEqual(matcher('foo/baz/biz/buz/bar'), None)
self.assertEqual(generator({'baz':1, 'buz':2, 'bar': 'html'}),
'/foo/1/biz/2.html')
+
+ def test_custom_regex_with_colons(self):
+ matcher, generator = self._callFUT('foo/{baz}/biz/{buz:(?:[^/\.]+)}.{bar}')
+ self.assertEqual(matcher('/foo/baz/biz/buz.bar'),
+ {'baz':'baz', 'buz':'buz', 'bar':'bar'})
+ self.assertEqual(matcher('foo/baz/biz/buz/bar'), None)
+ self.assertEqual(generator({'baz':1, 'buz':2, 'bar': 'html'}),
+ '/foo/1/biz/2.html')
def test_mixed_newstyle_oldstyle_pattern_defaults_to_newstyle(self):
# pattern: '\\/foo\\/(?P<baz>abc)\\/biz\\/(?P<buz>[^/]+)\\/bar$'
diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py
index a105adb70..f63e17bd8 100644
--- a/pyramid/tests/test_view.py
+++ b/pyramid/tests/test_view.py
@@ -3,17 +3,14 @@ import sys
from zope.interface import implementer
-from pyramid.testing import (
- setUp,
- tearDown,
- )
+from pyramid import testing
class BaseTest(object):
def setUp(self):
- self.config = setUp()
+ self.config = testing.setUp()
def tearDown(self):
- tearDown()
+ testing.tearDown()
def _registerView(self, reg, app, name):
from pyramid.interfaces import IRequest
@@ -90,7 +87,7 @@ class Test_notfound_view_config(BaseTest, unittest.TestCase):
config = call_venusian(venusian)
settings = config.settings
self.assertEqual(len(settings), 1)
- self.assertEqual(len(settings[0]), 5)
+ self.assertEqual(len(settings[0]), 4)
self.assertEqual(settings[0]['venusian'], venusian)
self.assertEqual(settings[0]['view'], None) # comes from call_venusian
self.assertEqual(settings[0]['attr'], 'view')
@@ -334,10 +331,10 @@ class TestIsResponse(unittest.TestCase):
class TestViewConfigDecorator(unittest.TestCase):
def setUp(self):
- setUp()
+ testing.setUp()
def tearDown(self):
- tearDown()
+ testing.tearDown()
def _getTargetClass(self):
from pyramid.view import view_config
@@ -371,6 +368,10 @@ class TestViewConfigDecorator(unittest.TestCase):
self.assertEqual(decorator.mapper, 'mapper')
self.assertEqual(decorator.decorator, 'decorator')
self.assertEqual(decorator.match_param, 'match_param')
+
+ def test_create_with_other_predicates(self):
+ decorator = self._makeOne(foo=1)
+ self.assertEqual(decorator.foo, 1)
def test_call_function(self):
decorator = self._makeOne()
diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py
index cccff14ba..4182ea665 100644
--- a/pyramid/urldispatch.py
+++ b/pyramid/urldispatch.py
@@ -148,7 +148,9 @@ def _compile_route(route):
name = pat.pop() # unicode
name = name[1:-1]
if ':' in name:
- name, reg = name.split(':')
+ # reg may contain colons as well,
+ # so we must strictly split name into two parts
+ name, reg = name.split(':', 1)
else:
reg = '[^/]+'
gen.append('%%(%s)s' % native_(name)) # native
diff --git a/pyramid/util.py b/pyramid/util.py
index 7d5c97814..dabd84695 100644
--- a/pyramid/util.py
+++ b/pyramid/util.py
@@ -280,3 +280,4 @@ def shortrepr(object, closer):
if len(r) > 100:
r = r[:100] + ' ... %s' % closer
return r
+
diff --git a/pyramid/view.py b/pyramid/view.py
index bb531c326..12a2efde6 100644
--- a/pyramid/view.py
+++ b/pyramid/view.py
@@ -138,15 +138,6 @@ def render_view(context, request, name='', secure=True):
return None
return ''.join(iterable)
-class _default(object):
- def __nonzero__(self):
- return False
- __bool__ = __nonzero__
- def __repr__(self): # pragma: no cover
- return '(default)'
-
-default = _default()
-
class view_config(object):
""" A function, class or method :term:`decorator` which allows a
developer to create view registrations nearer to a :term:`view
@@ -174,12 +165,12 @@ class view_config(object):
backwards compatibility purposes, as the name
:class:`pyramid.view.bfg_view`.
- The following arguments are supported to
+ The following keyword arguments are supported to
:class:`pyramid.view.view_config`: ``context``, ``permission``, ``name``,
``request_type``, ``route_name``, ``request_method``, ``request_param``,
``containment``, ``xhr``, ``accept``, ``header``, ``path_info``,
``custom_predicates``, ``decorator``, ``mapper``, ``http_cache``,
- and ``match_param``.
+ ``match_param``, and ``predicates``.
The meanings of these arguments are the same as the arguments passed to
:meth:`pyramid.config.Configurator.add_view`. If any argument is left
@@ -190,21 +181,11 @@ class view_config(object):
"""
venusian = venusian # for testing injection
- def __init__(self, name=default, request_type=default, for_=default,
- permission=default, route_name=default,
- request_method=default, request_param=default,
- containment=default, attr=default, renderer=default,
- wrapper=default, xhr=default, accept=default,
- header=default, path_info=default,
- custom_predicates=default, context=default,
- decorator=default, mapper=default, http_cache=default,
- match_param=default):
- L = locals()
- if (context is not default) or (for_ is not default):
- L['context'] = context or for_
- for k, v in L.items():
- if k not in ('self', 'L') and v is not default:
- setattr(self, k, v)
+ def __init__(self, **settings):
+ if 'for_' in settings:
+ if settings.get('context') is None:
+ settings['context'] = settings['for_']
+ self.__dict__.update(settings)
def __call__(self, wrapped):
settings = self.__dict__.copy()
@@ -326,7 +307,7 @@ class notfound_view_config(object):
The notfound_view_config constructor accepts most of the same arguments
as the constructor of :class:`pyramid.view.view_config`. It can be used
in the same places, and behaves in largely the same way, except it always
- registers a not found exception view instead of a "normal" view.
+ registers a not found exception view instead of a 'normal' view.
Example:
@@ -360,17 +341,8 @@ class notfound_view_config(object):
venusian = venusian
- def __init__(self, request_type=default, request_method=default,
- route_name=default, request_param=default, attr=default,
- renderer=default, containment=default, wrapper=default,
- xhr=default, accept=default, header=default,
- path_info=default, custom_predicates=default,
- decorator=default, mapper=default, match_param=default,
- append_slash=False):
- L = locals()
- for k, v in L.items():
- if k not in ('self', 'L') and v is not default:
- self.__dict__[k] = v
+ def __init__(self, **settings):
+ self.__dict__.update(settings)
def __call__(self, wrapped):
settings = self.__dict__.copy()
@@ -400,7 +372,7 @@ class forbidden_view_config(object):
The forbidden_view_config constructor accepts most of the same arguments
as the constructor of :class:`pyramid.view.view_config`. It can be used
in the same places, and behaves in largely the same way, except it always
- registers a forbidden exception view instead of a "normal" view.
+ registers a forbidden exception view instead of a 'normal' view.
Example:
@@ -413,9 +385,9 @@ class forbidden_view_config(object):
def notfound(request):
return Response('You are not allowed', status='401 Unauthorized')
- All have the same meaning as :meth:`pyramid.view.view_config` and each
- predicate argument restricts the set of circumstances under which this
- notfound view will be invoked.
+ All arguments passed to this function have the same meaning as
+ :meth:`pyramid.view.view_config` and each predicate argument restricts
+ the set of circumstances under which this notfound view will be invoked.
See :ref:`changing_the_forbidden_view` for detailed usage information.
@@ -426,16 +398,8 @@ class forbidden_view_config(object):
venusian = venusian
- def __init__(self, request_type=default, request_method=default,
- route_name=default, request_param=default, attr=default,
- renderer=default, containment=default, wrapper=default,
- xhr=default, accept=default, header=default,
- path_info=default, custom_predicates=default,
- decorator=default, mapper=default, match_param=default):
- L = locals()
- for k, v in L.items():
- if k not in ('self', 'L') and v is not default:
- self.__dict__[k] = v
+ def __init__(self, **settings):
+ self.__dict__.update(settings)
def __call__(self, wrapped):
settings = self.__dict__.copy()