summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml8
-rw-r--r--CHANGES.txt234
-rw-r--r--CONTRIBUTORS.txt11
-rw-r--r--HACKING.txt25
-rw-r--r--TODO.txt80
-rw-r--r--docs/api.rst3
-rw-r--r--docs/api/chameleon_text.rst31
-rw-r--r--docs/api/chameleon_zpt.rst28
-rw-r--r--docs/api/config.rst6
-rw-r--r--docs/api/decorator.rst9
-rw-r--r--docs/api/registry.rst14
-rw-r--r--docs/api/settings.rst2
-rw-r--r--docs/changes.rst2
-rw-r--r--docs/glossary.rst7
-rw-r--r--docs/index.rst13
-rw-r--r--docs/latexindex.rst2
-rw-r--r--docs/narr/advconfig.rst8
-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.rst230
-rw-r--r--docs/narr/introduction.rst4
-rw-r--r--docs/narr/renderers.rst4
-rw-r--r--docs/narr/sessions.rst13
-rw-r--r--docs/narr/templates.rst81
-rw-r--r--docs/narr/testing.rst2
-rw-r--r--docs/narr/upgrading.rst232
-rw-r--r--docs/narr/views.rst2
-rw-r--r--docs/tutorials/modwsgi/index.rst31
-rw-r--r--docs/tutorials/wiki/definingviews.rst2
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/views.py10
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/views.py10
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/views.py10
-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--docs/tutorials/wiki2/src/authorization/tutorial/views.py16
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views.py16
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/views.py16
-rw-r--r--pyramid/chameleon_text.py109
-rw-r--r--pyramid/chameleon_zpt.py121
-rw-r--r--pyramid/config/__init__.py195
-rw-r--r--pyramid/config/adapters.py115
-rw-r--r--pyramid/config/factories.py132
-rw-r--r--pyramid/config/predicates.py228
-rw-r--r--pyramid/config/routes.py88
-rw-r--r--pyramid/config/security.py20
-rw-r--r--pyramid/config/tweens.py114
-rw-r--r--pyramid/config/util.py468
-rw-r--r--pyramid/config/views.py353
-rw-r--r--pyramid/configuration.py60
-rw-r--r--pyramid/decorator.py28
-rw-r--r--pyramid/events.py49
-rw-r--r--pyramid/interfaces.py13
-rw-r--r--pyramid/mako_templating.py64
-rw-r--r--pyramid/paster.py10
-rw-r--r--pyramid/registry.py33
-rw-r--r--pyramid/renderers.py29
-rw-r--r--pyramid/request.py9
-rw-r--r--pyramid/router.py16
-rw-r--r--pyramid/scripting.py7
-rw-r--r--pyramid/scripts/proutes.py6
-rw-r--r--pyramid/scripts/pserve.py8
-rw-r--r--pyramid/scripts/pviews.py8
-rw-r--r--pyramid/session.py106
-rw-r--r--pyramid/settings.py26
-rw-r--r--pyramid/testing.py379
-rw-r--r--pyramid/tests/fixtures/components.mak3
-rw-r--r--pyramid/tests/fixtures/hellocompo.mak3
-rw-r--r--pyramid/tests/fixtures/withmacro.pt6
-rw-r--r--pyramid/tests/test_chameleon_text.py122
-rw-r--r--pyramid/tests/test_chameleon_zpt.py135
-rw-r--r--pyramid/tests/test_config/__init__.py8
-rw-r--r--pyramid/tests/test_config/test_adapters.py127
-rw-r--r--pyramid/tests/test_config/test_factories.py95
-rw-r--r--pyramid/tests/test_config/test_init.py85
-rw-r--r--pyramid/tests/test_config/test_init.py:TestConfigurator_add_directive0
-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_settings.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.py169
-rw-r--r--pyramid/tests/test_configuration.py31
-rw-r--r--pyramid/tests/test_events.py119
-rw-r--r--pyramid/tests/test_mako_templating.py86
-rw-r--r--pyramid/tests/test_path.py26
-rw-r--r--pyramid/tests/test_renderers.py41
-rw-r--r--pyramid/tests/test_request.py67
-rw-r--r--pyramid/tests/test_router.py36
-rw-r--r--pyramid/tests/test_scaffolds/test_copydir.py8
-rw-r--r--pyramid/tests/test_scripting.py81
-rw-r--r--pyramid/tests/test_scripts/test_pviews.py4
-rw-r--r--pyramid/tests/test_session.py42
-rw-r--r--pyramid/tests/test_settings.py26
-rw-r--r--pyramid/tests/test_testing.py286
-rw-r--r--pyramid/tests/test_traversal.py172
-rw-r--r--pyramid/tests/test_url.py10
-rw-r--r--pyramid/tests/test_urldispatch.py8
-rw-r--r--pyramid/tests/test_util.py14
-rw-r--r--pyramid/tests/test_view.py19
-rw-r--r--pyramid/traversal.py32
-rw-r--r--pyramid/urldispatch.py4
-rw-r--r--pyramid/util.py8
-rw-r--r--pyramid/view.py68
-rw-r--r--setup.py2
-rw-r--r--tox.ini33
109 files changed, 4037 insertions, 2647 deletions
diff --git a/.travis.yml b/.travis.yml
index 2e737af04..ab9c3ff30 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,8 +6,8 @@ python:
- pypy
- 3.2
-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..da2399d9b 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,7 @@
Next release
============
+
Bug Fixes
---------
@@ -9,9 +10,66 @@ 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
+
+- HTTP Accept headers were not being normalized causing potentially
+ conflicting view registrations to go unnoticed. Two views that only
+ differ in the case ('text/html' vs. 'text/HTML') will now raise an error.
+ https://github.com/Pylons/pyramid/pull/620
+
+- Configurator.add_directive now accepts arbitrary callables like partials or
+ objects implementing ``__call__`` which dont have ``__name__`` and
+ ``__doc__`` attributes. See https://github.com/Pylons/pyramid/issues/621
+ and https://github.com/Pylons/pyramid/pull/647.
+
+- Forward-port from 1.3 branch: when registering multiple views with an
+ ``accept`` predicate in a Pyramid application runing under Python 3, you
+ might have received a ``TypeError: unorderable types: function() <
+ function()`` exception.
+
Features
--------
+- Third-party custom view, route, and subscriber predicates can now be added
+ for use by view authors via
+ ``pyramid.config.Configurator.add_view_predicate``,
+ ``pyramid.config.Configurator.add_route_predicate`` and
+ ``pyramid.config.Configurator.add_subscriber_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)
+
+ Similar features exist for ``add_route``, and ``add_subscriber``. See
+ "Adding A Third Party View, Route, or Subscriber 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 +87,183 @@ 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.add_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 in an asset spect. When the def
+ name is present in the asset spec, the system will render the template def
+ within the template and will return the result. An example asset spec is
+ ``package:path/to/template#defname.mako``. This will render the def named
+ ``defname`` inside the ``template.pt`` package instead of rendering the
+ entire template. The old way of returning a tuple from the view is
+ supported for backward compatibility, ('defname', {}).
+
+- The Chameleon ZPT renderer now accepts a macro name in an asset spec. When
+ the macro name is present in the asset spec, the system will render the
+ macro listed as a ``define-macro`` and return the result instead of
+ rendering the entire template. An example asset spec:
+ ``package:path/to/template#macroname.pt``. This will render the macro
+ defined as ``macroname`` within the ``template.pt`` template instead of the
+ entire templae.
+
+- 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')
+
+- The ``UnencryptedCookieSessionFactoryConfig`` now accepts
+ ``signed_serialize`` and ``signed_deserialize`` hooks which may be used
+ to influence how the sessions are marshalled (by default this is done
+ with HMAC+pickle).
+
+- ``pyramid.testing.DummyRequest`` now supports methods supplied by the
+ ``pyramid.util.InstancePropertyMixin`` class such as ``set_property``.
+
+- Request properties and methods added via ``config.set_request_property`` or
+ ``config.add_request_method`` are now available to tweens.
+
+- Request properties and methods added via ``config.set_request_property`` or
+ ``config.add_request_method`` are now available to tweens.
+
+- Request properties and methods added via ``config.set_request_property`` or
+ ``config.add_request_method`` are now available in the request object
+ returned from ``pyramid.paster.bootstrap``.
+
+- ``request.context`` of environment request during ``bootstrap`` is now the
+ root object if a context isn't already set on a provided request.
+
+- The ``pyramid.decorator.reify`` function is now an API, and was added to
+ the API documentation.
+
+- Added the ``pyramid.testing.testConfig`` context manager, which can be used
+ to generate a configurator in a test, e.g. ``with testing.testConfig(...):``.
+
+Deprecations
+------------
+
+- The ``pyramid.config.Configurator.set_request_property`` has been
+ documentation-deprecated. The method remains usable but the more
+ featureful ``pyramid.config.Configurator.add_request_method`` should be
+ used in its place (it has all of the same capabilities but can also extend
+ the request object with methods).
+
+Backwards Incompatibilities
+---------------------------
+
+- The Pyramid router no longer adds the values ``bfg.routes.route`` or
+ ``bfg.routes.matchdict`` to the request's WSGI environment dictionary.
+ These values were docs-deprecated in ``repoze.bfg`` 1.0 (effectively seven
+ minor releases ago). If your code depended on these values, use
+ request.matched_route and request.matchdict instead.
+
+- It is no longer possible to pass an environ dictionary directly to
+ ``pyramid.traversal.ResourceTreeTraverser.__call__`` (aka
+ ``ModelGraphTraverser.__call__``). Instead, you must pass a request
+ object. Passing an environment instead of a request has generated a
+ deprecation warning since Pyramid 1.1.
+
+- Pyramid will no longer work properly if you use the
+ ``webob.request.LegacyRequest`` as a request factory. Instances of the
+ LegacyRequest class have a ``request.path_info`` which return a string.
+ This Pyramid release assumes that ``request.path_info`` will
+ unconditionally be Unicode.
+
+- The functions from ``pyramid.chameleon_zpt`` and ``pyramid.chameleon_text``
+ named ``get_renderer``, ``get_template``, ``render_template``, and
+ ``render_template_to_response`` have been removed. These have issued a
+ deprecation warning upon import since Pyramid 1.0. Use
+ ``pyramid.renderers.get_renderer()``,
+ ``pyramid.renderers.get_renderer().implementation()``,
+ ``pyramid.renderers.render()`` or ``pyramid.renderers.render_to_response``
+ respectively instead of these functions.
+
+- The ``pyramid.configuration`` module was removed. It had been deprecated
+ since Pyramid 1.0 and printed a deprecation warning upon its use. Use
+ ``pyramid.config`` instead.
+
+- The ``pyramid.paster.PyramidTemplate`` API was removed. It had been
+ deprecated since Pyramid 1.1 and issued a warning on import. If your code
+ depended on this, adjust your code to import
+ ``pyramid.scaffolds.PyramidTemplate`` instead.
+
+- The ``pyramid.settings.get_settings()`` API was removed. It had been
+ printing a deprecation warning since Pyramid 1.0. If your code depended on
+ this API, use ``pyramid.threadlocal.get_current_registry().settings``
+ instead or use the ``settings`` attribute of the registry available from
+ the request (``request.registry.settings``).
+
+- These APIs from the ``pyramid.testing`` module were removed. They have
+ been printing deprecation warnings since Pyramid 1.0:
+
+ * ``registerDummySecurityPolicy``, use
+ ``pyramid.config.Configurator.testing_securitypolicy`` instead.
+
+ * ``registerResources`` (aka ``registerModels``, use
+ ``pyramid.config.Configurator.testing_resources`` instead.
+
+ * ``registerEventListener``, use
+ ``pyramid.config.Configurator.testing_add_subscriber`` instead.
+
+ * ``registerTemplateRenderer`` (aka `registerDummyRenderer``), use
+ ``pyramid.config.Configurator.testing_add_template`` instead.
+
+ * ``registerView``, use ``pyramid.config.Configurator.add_view`` instead.
+
+ * ``registerUtility``, use
+ ``pyramid.config.Configurator.registry.registerUtility`` instead.
+
+ * ``registerAdapter``, use
+ ``pyramid.config.Configurator.registry.registerAdapter`` instead.
+
+ * ``registerSubscriber``, use
+ ``pyramid.config.Configurator.add_subscriber`` instead.
+
+ * ``registerRoute``, use
+ ``pyramid.config.Configurator.add_route`` instead.
+
+ * ``registerSettings``, use
+ ``pyramid.config.Configurator.add_settings`` instead.
+
+Documentation
+-------------
+
+- Added an "Upgrading Pyramid" chapter to the narrative documentation. It
+ describes how to cope with deprecations and removals of Pyramid APIs and
+ how to show Pyramid-generated deprecation warnings while running tests and
+ while running a server.
+
+Dependencies
+------------
+
+- Pyramid now requires WebOb 1.2b3+ (the prior Pyramid release only relied on
+ 1.2dev+). This is to ensure that we obtain a version of WebOb that returns
+ ``request.path_info`` as text.
+
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 65ff1b2f2..e2a6ec018 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -173,6 +173,17 @@ Contributors
- Marin Rukavina, 2012/05/03
+- Lorenzo M. Catucci, 2012/06/08
+
- Marc Abramowitz, 2012/06/13
- Brian Sutherland, 2012/06/16
+
+- Jeff Cook, 2012/06/16
+
+- Ian Wilson, 2012/06/17
+
+- Roman Kozlovskyi, 2012/08/11
+
+- Domen Kozar, 2012/09/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/TODO.txt b/TODO.txt
index 4b4f48499..202d1afbb 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -24,17 +24,6 @@ Nice-to-Have
- Modify the urldispatch chapter examples to assume a scan rather than
``add_view``.
-- Context manager for creating a new configurator (replacing
- ``with_package``). E.g.::
-
- with config.partial(package='bar') as c:
- c.add_view(...)
-
- or::
-
- with config.partial(introspection=False) as c:
- c.add_view(..)
-
- Introspection:
* ``default root factory`` category (prevent folks from needing to searh
@@ -106,30 +95,59 @@ Nice-to-Have
- Update App engine chapter with less creaky directions.
+- Idea from Zart:
+
+ diff --git a/pyramid/paster.py b/pyramid/paster.py
+ index b0e4d79..b3bd82a 100644
+ --- a/pyramid/paster.py
+ +++ b/pyramid/paster.py
+ @@ -8,6 +8,7 @@ from paste.deploy import (
+ from pyramid.compat import configparser
+ from logging.config import fileConfig
+ from pyramid.scripting import prepare
+ +from pyramid.config import Configurator
+
+ def get_app(config_uri, name=None, loadapp=loadapp):
+ """ Return the WSGI application named ``name`` in the PasteDeploy
+ @@ -111,3 +112,10 @@ def bootstrap(config_uri, request=None):
+ env['app'] = app
+ return env
+
+ +def make_pyramid_app(global_conf, app=None, **settings):
+ + """Return pyramid application configured with provided settings"""
+ + config = Configurator(package='pyramid', settings=settings)
+ + if app:
+ + config.include(app)
+ + app = config.make_wsgi_app()
+ + return app
+ diff --git a/setup.py b/setup.py
+ index 03ebb42..91e0e21 100644
+ --- a/setup.py
+ +++ b/setup.py
+ @@ -118,6 +118,8 @@ setup(name='pyramid',
+ [paste.server_runner]
+ wsgiref = pyramid.scripts.pserve:wsgiref_server_runner
+ cherrypy = pyramid.scripts.pserve:cherrypy_server_runner
+ + [paster.app_factory]
+ + main = pyramid.paster:make_pyramid_app
+ """
+ )
+
+
Future
------
-- 1.4: Kill off ``bfg.routes`` envvars in router.
-
-- 1.4: Remove ``chameleon_text`` / ``chameleon_zpt`` deprecated functions
- (render_*)
-
-- 1.4: Remove ``pyramid.configuration.ConfigurationError`` (deprecated).
-
-- 1.4: Remove ``pyramid.paster.PyramidTemplate`` (deprecated).
-
-- 1.4: Remove ``pyramid.settings.get_settings`` (deprecated).
-
-- 1.5: Remove all deprecated ``pyramid.testing`` functions.
+- 1.5: remove ``pyramid.view.static`` and ``pyramid.view.is_response``.
- 1.5: turn ``pyramid.settings.Settings`` into a function that returns the
original dict (after ``__getattr__`` deprecation period, it was deprecated
in 1.2).
-- 1.5: Remove ``pyramid.requests.DeprecatedRequestMethodsMixin``.
+- 1.5: Remove ``pyramid.requests.DeprecatedRequestMethodsMixin`` and code in
+ renderers module that looks for _response_content_type, et. al.
- 1.5: Maybe? deprecate set_request_property in favor of pointing people at
- set_request_method.
+ add_request_method, schedule removal for 1.8?
- 1.6: Remove IContextURL and TraversalContextURL.
@@ -152,3 +170,15 @@ Probably Bad Ideas
- http://pythonguy.wordpress.com/2011/06/22/dynamic-variables-revisited/
instead of thread locals
+
+- Context manager for creating a new configurator (replacing
+ ``with_package``). E.g.::
+
+ with config.partial(package='bar') as c:
+ c.add_view(...)
+
+ or::
+
+ with config.partial(introspection=False) as c:
+ c.add_view(..)
+
diff --git a/docs/api.rst b/docs/api.rst
index d510c0d27..9e540b49b 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -10,10 +10,9 @@ documentation is organized alphabetically by module name.
api/authorization
api/authentication
- api/chameleon_text
- api/chameleon_zpt
api/compat
api/config
+ api/decorator
api/events
api/exceptions
api/httpexceptions
diff --git a/docs/api/chameleon_text.rst b/docs/api/chameleon_text.rst
deleted file mode 100644
index 494f5b464..000000000
--- a/docs/api/chameleon_text.rst
+++ /dev/null
@@ -1,31 +0,0 @@
-.. _chameleon_text_module:
-
-:mod:`pyramid.chameleon_text`
-----------------------------------
-
-.. automodule:: pyramid.chameleon_text
-
- .. autofunction:: get_template
-
- .. autofunction:: render_template
-
- .. autofunction:: render_template_to_response
-
-These APIs will will work against template files which contain simple
-``${Genshi}`` - style replacement markers.
-
-The API of :mod:`pyramid.chameleon_text` is identical to that of
-:mod:`pyramid.chameleon_zpt`; only its import location is
-different. If you need to import an API functions from this module as
-well as the :mod:`pyramid.chameleon_zpt` module within the same
-view file, use the ``as`` feature of the Python import statement,
-e.g.:
-
-.. code-block:: python
- :linenos:
-
- from pyramid.chameleon_zpt import render_template as zpt_render
- from pyramid.chameleon_text import render_template as text_render
-
-
-
diff --git a/docs/api/chameleon_zpt.rst b/docs/api/chameleon_zpt.rst
deleted file mode 100644
index df9a36a56..000000000
--- a/docs/api/chameleon_zpt.rst
+++ /dev/null
@@ -1,28 +0,0 @@
-.. _chameleon_zpt_module:
-
-:mod:`pyramid.chameleon_zpt`
--------------------------------
-
-.. automodule:: pyramid.chameleon_zpt
-
- .. autofunction:: get_template
-
- .. autofunction:: render_template
-
- .. autofunction:: render_template_to_response
-
-These APIs will work against files which supply template text which
-matches the :term:`ZPT` specification.
-
-The API of :mod:`pyramid.chameleon_zpt` is identical to that of
-:mod:`pyramid.chameleon_text`; only its import location is
-different. If you need to import an API functions from this module as
-well as the :mod:`pyramid.chameleon_text` module within the same
-view file, use the ``as`` feature of the Python import statement,
-e.g.:
-
-.. code-block:: python
- :linenos:
-
- from pyramid.chameleon_zpt import render_template as zpt_render
- from pyramid.chameleon_text import render_template as text_render
diff --git a/docs/api/config.rst b/docs/api/config.rst
index cd58e74d3..5d2bce23e 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:: add_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/decorator.rst b/docs/api/decorator.rst
new file mode 100644
index 000000000..35d9131df
--- /dev/null
+++ b/docs/api/decorator.rst
@@ -0,0 +1,9 @@
+.. _decorator_module:
+
+:mod:`pyramid.decorator`
+--------------------------
+
+.. automodule:: pyramid.decorator
+
+.. autofunction:: reify
+
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/api/settings.rst b/docs/api/settings.rst
index 6b12c038c..cd802e138 100644
--- a/docs/api/settings.rst
+++ b/docs/api/settings.rst
@@ -5,8 +5,6 @@
.. automodule:: pyramid.settings
- .. autofunction:: get_settings
-
.. autofunction:: asbool
.. autofunction:: aslist
diff --git a/docs/changes.rst b/docs/changes.rst
index 6294123ed..fdeaf1e99 100644
--- a/docs/changes.rst
+++ b/docs/changes.rst
@@ -1,3 +1,5 @@
+.. _changelog:
+
:app:`Pyramid` Change History
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 45a79326f..34cf1b078 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -994,3 +994,10 @@ 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, view, or subscriber predicates to extend the configuration
+ system. See :ref:`registering_thirdparty_predicates` for more
+ information.
+
diff --git a/docs/index.rst b/docs/index.rst
index 31c2fde6b..321fe1fed 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
@@ -94,6 +88,7 @@ Narrative documentation in chapter form explaining how to use
narr/advconfig
narr/extconfig
narr/scaffolding
+ narr/upgrading
narr/threadlocals
narr/zca
diff --git a/docs/latexindex.rst b/docs/latexindex.rst
index 99ec2f54d..604e6e7c6 100644
--- a/docs/latexindex.rst
+++ b/docs/latexindex.rst
@@ -85,8 +85,6 @@ API Reference
api/authorization
api/authentication
- api/chameleon_text
- api/chameleon_zpt
api/config
api/events
api/exceptions
diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst
index 2949dc808..ad22a82c9 100644
--- a/docs/narr/advconfig.rst
+++ b/docs/narr/advconfig.rst
@@ -294,6 +294,7 @@ These are the methods of the configurator which provide conflict detection:
:meth:`~pyramid.config.Configurator.add_view`,
:meth:`~pyramid.config.Configurator.add_route`,
:meth:`~pyramid.config.Configurator.add_renderer`,
+:meth:`~pyramid.config.Configurator.add_request_method`,
:meth:`~pyramid.config.Configurator.set_request_factory`,
:meth:`~pyramid.config.Configurator.set_session_factory`,
:meth:`~pyramid.config.Configurator.set_request_property`,
@@ -413,3 +414,10 @@ constraints: the routes they imply require relative ordering. Such ordering
constraints are not absolved by two-phase configuration. Routes are still
added in configuration execution order.
+More Information
+----------------
+
+For more information, see the article entitled `"A Whirlwind Rour of Advanced
+Configuration Tactics"
+<http://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest/configuration/whirlwind_tour.html>`_
+in the Pyramid Cookbook.
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 a2143b3c5..96fa77a07 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -289,6 +289,36 @@ keys added to the renderer globals dictionary by all
:class:`pyramid.events.BeforeRender` subscribers and renderer globals
factories must be unique.
+The dictionary returned from the view is accessible through the
+:attr:`rendering_val` attribute of a :class:`~pyramid.events.BeforeRender`
+event.
+
+Suppose you return ``{'mykey': 'somevalue', 'mykey2': 'somevalue2'}`` from
+your view callable, like so:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view import view_config
+
+ @view_config(renderer='some_renderer')
+ def myview(request):
+ return {'mykey': 'somevalue', 'mykey2': 'somevalue2'}
+
+:attr:`rendering_val` can be used to access these values from the
+:class:`~pyramid.events.BeforeRender` object:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.events import subscriber
+ from pyramid.events import BeforeRender
+
+ @subscriber(BeforeRender)
+ def read_return(event):
+ # {'mykey': 'somevalue'} is returned from the view
+ print(event.rendering_val['mykey'])
+
See the API documentation for the :class:`~pyramid.events.BeforeRender` event
interface at :class:`pyramid.interfaces.IBeforeRender`.
@@ -1202,3 +1232,203 @@ 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, Route, or Subscriber Predicate
+---------------------------------------------------------
+
+.. note::
+
+ Third-party view, route, and subscriber predicates are a feature new as of
+ Pyramid 1.4.
+
+.. _view_and_route_predicates:
+
+View and Route Predicates
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+View and route predicates used during 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 view or route predicate factory. A view or route
+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.
+
+.. _subscriber_predicates:
+
+Subscriber Predicates
+~~~~~~~~~~~~~~~~~~~~~
+
+Subscriber predicates work almost exactly like view and route predicates.
+They narrow the set of circumstances in which a subscriber will be called.
+There are several minor differences between a subscriber predicate and a
+view/route predicate:
+
+- There are no default subscriber predicates. You must register one to use
+ one.
+
+- The ``__call__`` method of a subscriber predicate accepts a single
+ ``event`` object instead of a ``context`` and a ``request``.
+
+- Not every subscriber predicate can be used with every event type. Some
+ subscriber predicates will assume a certain event type.
+
+Here's an example of a subscriber predicate that can be used in conjunction
+with a subscriber that subscribes to the :class:`pyramid.events.NewReqest`
+event type.
+
+.. code-block:: python
+ :linenos:
+
+ class RequestPathStartsWith(object):
+ def __init__(self, val, config):
+ self.val = val
+
+ def text(self):
+ return 'path_startswith = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, event):
+ return event.request.path.startswith(self.val)
+
+Once you've created a subscriber predicate, it may registered via
+:meth:`pyramid.config.Configurator.add_subscriber_predicate`. For example:
+
+.. code-block:: python
+
+ config.add_subscriber_predicate(
+ 'request_path_startswith', RequestPathStartsWith)
+
+Once a subscriber predicate is registered, you can use it in a call to
+:meth:`pyramid.config.Configurator.add_subscriber` or to
+:class:`pyramid.events.subscriber`. Here's an example of using the
+previously registered ``request_path_startswith`` predicate in a call to
+:meth:`~pyramid.config.Configurator.add_subscriber`:
+
+.. code-block:: python
+ :linenos:
+
+ # define a subscriber in your code
+
+ def yosubscriber(event):
+ event.request.yo = 'YO!'
+
+ # and at configuration time
+
+ config.add_subscriber(yosubscriber, NewRequest,
+ request_path_startswith='/add_yo')
+
+Here's the same subscriber/predicate/event-type combination used via
+:class:`~pyramid.events.subscriber`.
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.events import subscriber
+
+ @subscriber(NewRequest, request_path_startswith='/add_yo')
+ def yosubscriber(event):
+ event.request.yo = 'YO!'
+
+In either of the above configurations, the ``yosubscriber`` callable will
+only be called if the request path starts with ``/add_yo``. Otherwise the
+event subscriber will not be called.
+
+Note that the ``request_path_startswith`` subscriber you defined can be used
+with events that have a ``request`` attribute, but not ones that do not. So,
+for example, the predicate can be used with subscribers registered for
+:class:`pyramid.events.NewRequest` and :class:`pyramid.events.ContextFound`
+events, but it cannot be used with subscribers registered for
+:class:`pyramid.events.ApplicationCreated` because the latter type of event
+has no ``request`` attribute. The point being: unlike route and view
+predicates, not every type of subscriber predicate will necessarily be
+applicable for use in every subscriber registration. It is not the
+responsibility of the predicate author to make every predicate make sense for
+every event type; it is the responsibility of the predicate consumer to use
+predicates that make sense for a particular event type registration.
+
+
+
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/renderers.rst b/docs/narr/renderers.rst
index 57b5bc65b..63287e2cd 100644
--- a/docs/narr/renderers.rst
+++ b/docs/narr/renderers.rst
@@ -306,7 +306,9 @@ See :class:`pyramid.renderers.JSON` and
JSONP Renderer
~~~~~~~~~~~~~~
-.. note:: This feature is new in Pyramid 1.1.
+.. note::
+
+ This feature is new in Pyramid 1.1.
:class:`pyramid.renderers.JSONP` is a `JSONP
<http://en.wikipedia.org/wiki/JSONP>`_ renderer factory helper which
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..ba72ebfbf 100644
--- a/docs/narr/templates.rst
+++ b/docs/narr/templates.rst
@@ -46,20 +46,6 @@ within the body of a view callable like so:
{'foo':1, 'bar':2},
request=request)
-.. warning::
-
- Earlier iterations of this documentation
- (pre-version-1.3) encouraged the application developer to use
- ZPT-specific APIs such as
- :func:`pyramid.chameleon_zpt.render_template_to_response` and
- :func:`pyramid.chameleon_zpt.render_template` to render templates
- directly. This style of rendering still works, but at least for
- purposes of this documentation, those functions are deprecated.
- Application developers are encouraged instead to use the functions
- available in the :mod:`pyramid.renderers` module to perform
- rendering tasks. This set of functions works to render templates
- for all renderer extensions registered with :app:`Pyramid`.
-
The ``sample_view`` :term:`view callable` function above returns a
:term:`response` object which contains the body of the
``templates/foo.pt`` template. In this case, the ``templates``
@@ -79,12 +65,12 @@ prefix on Windows.
.. warning::
Only :term:`Chameleon` templates support defining a renderer for a
- template relative to the location of the module where the view
- callable is defined. Mako templates, and other templating system
- bindings work differently. In particular, Mako templates use a
- "lookup path" as defined by the ``mako.directories`` configuration
- file instead of treating relative paths as relative to the current
- view module. See :ref:`mako_templates`.
+ template relative to the location of the module where the view callable is
+ defined. Mako templates, and other templating system bindings work
+ differently. In particular, Mako templates use a "lookup path" as defined
+ by the ``mako.directories`` configuration file instead of treating
+ relative paths as relative to the current view module. See
+ :ref:`mako_templates`.
The path can alternately be a :term:`asset specification` in the form
``some.dotted.package_name:relative/path``. This makes it possible to
@@ -534,6 +520,33 @@ And ``templates/mytemplate.pt`` might look like so:
</span>
</html>
+
+Using A Chameleon Macro Name Within a Renderer Name
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sommetime you'd like to render a macro inside of a Chameleon ZPT template
+instead of the full Chameleon ZPT template. To render the content of a
+``define-macro`` field inside a Chameleon ZPT template, given a Chameleon
+template file named ``foo.pt`` and a macro named ``bar`` defined within it
+(e.g. ``<div metal:define-macro="bar">...</div>``), 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.pt')
+ def my_view(request):
+ return {'project':'my project'}
+
+The above will render only the ``bar`` macro defined within the ``foo.pt``
+template instead of the entire template.
+
+.. note::
+
+ This feature is new in Pyramid 1.4.
+
.. index::
single: Chameleon text templates
@@ -573,10 +586,6 @@ When the template is rendered, it will show:
Hello, world!
-If you'd rather use templates directly within a view callable (without
-the indirection of using a renderer), see :ref:`chameleon_text_module`
-for the API description.
-
See also :ref:`built_in_renderers` for more general information about
renderers, including Chameleon text renderers.
@@ -714,6 +723,30 @@ 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 A Mako def name Within a Renderer Name
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sommetime you'd like to render a ``def`` inside of a Mako template instead of
+the full Mako template. To render 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'}
+
+The above will render the ``bar`` def from within the ``foo.mak`` template
+instead of the entire template.
+
+.. note::
+
+ This feature is new in Pyramid 1.4.
+
.. index::
single: automatic reloading of templates
single: template automatic reload
diff --git a/docs/narr/testing.rst b/docs/narr/testing.rst
index 89bc1a089..20017064b 100644
--- a/docs/narr/testing.rst
+++ b/docs/narr/testing.rst
@@ -52,7 +52,7 @@ The suggested mechanism for unit and integration testing of a :app:`Pyramid`
application is the Python :mod:`unittest` module. Although this module is
named :mod:`unittest`, it is actually capable of driving both unit and
integration tests. A good :mod:`unittest` tutorial is available within `Dive
-Into Python <http://diveintopython.nfshost.com/unit_testing/index.html>`_ by Mark
+Into Python <http://www.diveintopython.net/unit_testing/index.html>`_ by Mark
Pilgrim.
:app:`Pyramid` provides a number of facilities that make unit, integration,
diff --git a/docs/narr/upgrading.rst b/docs/narr/upgrading.rst
new file mode 100644
index 000000000..92f125c0a
--- /dev/null
+++ b/docs/narr/upgrading.rst
@@ -0,0 +1,232 @@
+Upgrading Pyramid
+=================
+
+When a new version of Pyramid is released, it will sometimes deprecate a
+feature or remove a feature that was deprecated in an older release. When
+features are removed from Pyramid, applications that depend on those features
+will begin to break. This chapter explains how to ensure your Pyramid
+applications keep working when you upgrade the Pyramid version you're using.
+
+.. sidebar:: About Release Numbering
+
+ Conventionally, application version numbering in Python is described as
+ ``major.minor.micro``. If your Pyramid version is "1.2.3", it means
+ you're running a version of Pyramid with the major version "1", the minor
+ version "2" and the micro version "3". A "major" release is one that
+ increments the first-dot number; 2.X.X might follow 1.X.X. A "minor"
+ release is one that increments the second-dot number; 1.3.X might follow
+ 1.2.X. A "micro" release is one that increments the third-dot number;
+ 1.2.3 might follow 1.2.2. In general, micro releases are "bugfix-only",
+ and contain no new features, minor releases contain new features but are
+ largely backwards compatible with older versions, and a major release
+ indicates a large set of backwards incompatibilities.
+
+The Pyramid core team is conservative when it comes to removing features. We
+don't remove features unnecessarily, but we're human, and we make mistakes
+which cause some features to be evolutionary dead ends. Though we are
+willing to support dead-end features for some amount of time, some eventually
+have to be removed when the cost of supporting them outweighs the benefit of
+keeping them around, because each feature in Pyramid represents a certain
+documentation and maintenance burden.
+
+Deprecation and Removal Policy
+------------------------------
+
+When a feature is scheduled for removal from Pyramid or any of its official
+add-ons, the core development team takes these steps:
+
+- Using the feature will begin to generate a `DeprecationWarning`, indicating
+ the version in which the feature became deprecated.
+
+- A note is added to the documentation indicating that the feature is
+ deprecated.
+
+- A note is added to the :ref:`changelog` about the deprecation.
+
+When a deprecated feature is eventually removed:
+
+- The feature is removed.
+
+- A note is added to the :ref:`changelog` about the removal.
+
+Features are never removed in *micro* releases. They are only removed in
+minor and major releases. Deprecated features are kept around for at least
+*three* minor releases from the time the feature became deprecated.
+Therefore, if a feature is added in Pyramid 1.0, but it's deprecated in
+Pyramid 1.1, it will be kept around through all 1.1.X releases, all 1.2.X
+releases and all 1.3.X releases. It will finally be removed in the first
+1.4.X release.
+
+Sometimes features are "docs-deprecated" instead of formally deprecated.
+This means that the feature will be kept around indefinitely, but it will be
+removed from the documentation or a note will be added to the documentation
+telling folks to use some other newer feature. This happens when the cost of
+keeping an old feature around is very minimal and the support and
+documentation burden is very low. For example, we might rename a function
+that is an API without changing the arguments it accepts. In this case,
+we'll often rename the function, and change the docs to point at the new
+function name, but leave around a backwards compatibility alias to the old
+function name so older code doesn't break.
+
+"Docs deprecated" features tend to work "forever", meaning that they won't be
+removed, and they'll never generate a deprecation warning. However, such
+changes are noted in the :ref:`changelog`, so it's possible to know that you
+should change older spellings to newer ones to ensure that people reading
+your code can find the APIs you're using in the Pyramid docs.
+
+Consulting the Change History
+-----------------------------
+
+Your first line of defense against application failures caused by upgrading
+to a newer Pyramid release is always to read the :ref:`changelog`. to find
+the deprecations and removals for each release between the release you're
+currently running and the one you wish to upgrade to. The change history
+notes every deprecation within a ``Deprecation`` section and every removal
+within a ``Backwards Incompatibilies`` section for each release.
+
+The change history often contains instructions for changing your code to
+avoid deprecation warnings and how to change docs-deprecated spellings to
+newer ones. You can follow along with each deprecation explanation in the
+change history, simply doing a grep or other code search to your application,
+using the change log examples to remediate each potential problem.
+
+.. _testing_under_new_release:
+
+Testing Your Application Under a New Pyramid Release
+----------------------------------------------------
+
+Once you've upgraded your application to a new Pyramid release and you've
+remediated as much as possible by using the change history notes, you'll want
+to run your application's tests (see :ref:`running_tests`) in such a way that
+you can see DeprecationWarnings printed to the console when the tests run.
+
+.. code-block:: bash
+
+ $ python -Wd setup.py test -q
+
+The ``-Wd`` argument is an argument that tells Python to print deprecation
+warnings to the console. Note that the ``-Wd`` flag is only required for
+Python 2.7 and better: Python versions 2.6 and older print deprecation
+warnings to the console by default. See `the Python -W flag documentation
+<http://docs.python.org/using/cmdline.html#cmdoption-W>`_ for more
+information.
+
+As your tests run, deprecation warnings will be printed to the console
+explaining the deprecation and providing instructions about how to prevent
+the deprecation warning from being issued. For example:
+
+.. code-block:: text
+
+ $ python -Wd setup.py test -q
+ # .. elided ...
+ running build_ext
+ /home/chrism/projects/pyramid/env27/myproj/myproj/views.py:3:
+ DeprecationWarning: static: The "pyramid.view.static" class is deprecated
+ as of Pyramid 1.1; use the "pyramid.static.static_view" class instead with
+ the "use_subpath" argument set to True.
+ from pyramid.view import static
+ .
+ ----------------------------------------------------------------------
+ Ran 1 test in 0.014s
+
+ OK
+
+In the above case, it's line #3 in the ``myproj.views`` module (``from
+pyramid.view import static``) that is causing the problem:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view import view_config
+
+ from pyramid.view import static
+ myview = static('static', 'static')
+
+The deprecation warning tells me how to fix it, so I can change the code to
+do things the newer way:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view. import view_config
+
+ from pyramid.static import static_view
+ myview = static_view('static', 'static', use_subpath=True)
+
+When I run the tests again, the deprecation warning is no longer printed to
+my console:
+
+.. code-block:: text
+
+ $ python -Wd setup.py test -q
+ # .. elided ...
+ running build_ext
+ .
+ ----------------------------------------------------------------------
+ Ran 1 test in 0.014s
+
+ OK
+
+
+My Application Doesn't Have Any Tests or Has Few Tests
+------------------------------------------------------
+
+If your application has no tests, or has only moderate test coverage, running
+tests won't tell you very much, because the Pyramid codepaths that generate
+deprecation warnings won't be executed.
+
+In this circumstance, you can start your application interactively under a
+server run with the ``PYTHONWARNINGS`` environment variable set to
+``default``. On UNIX, you can do that via:
+
+.. code-block:: bash
+
+ $ PYTHONWARNINGS=default bin/pserve development.ini
+
+On Windows, you need to issue two commands:
+
+.. code-block:: bash
+
+ C:\> set PYTHONWARNINGS=default
+ C:\> Scripts/pserve.exe development.ini
+
+At this point, it's ensured that deprecation warnings will be printed to the
+console whenever a codepath is hit that generates one. You can then click
+around in your application interactively to try to generate them, and
+remediate as explained in :ref:`testing_under_new_release`.
+
+See `the PYTHONWARNINGS environment variable documentation
+<http://docs.python.org/using/cmdline.html#envvar-PYTHONWARNINGS>`_ or `the
+Python -W flag documentation
+<http://docs.python.org/using/cmdline.html#cmdoption-W>`_ for more
+information.
+
+Upgrading to the Very Latest Pyramid Release
+--------------------------------------------
+
+When you upgrade your application to the very most recent Pyramid release,
+it's advisable to upgrade step-wise through each most recent minor release,
+beginning with the one that you know your application currently runs under,
+and ending on the most recent release. For example, if your application is
+running in production on Pyramid 1.2.1, and the most recent Pyramid 1.3
+release is Pyramid 1.3.3, and the most recent Pyramid release is 1.4.4, it's
+advisable to do this:
+
+- Upgrade your environment to the most recent 1.2 release. For example, the
+ most recent 1.2 release might be 1.2.3, so upgrade to it. Then run your
+ application's tests under 1.2.3 as described in
+ :ref:`testing_under_new_release`. Note any deprecation warnings and
+ remediate.
+
+- Upgrade to the most recent 1.3 release, 1.3.3. Run your application's
+ tests, note any deprecation warnings and remediate.
+
+- Upgrade to 1.4.4. Run your application's tests, note any deprecation
+ warnings and remediate.
+
+If you skip testing your application under each minor release (for example if
+you upgrade directly from 1.2.1 to 1.4.4), you might miss a deprecation
+warning and waste more time trying to figure out an error caused by a feature
+removal than it would take to upgrade stepwise through each minor release.
+
+
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..a22f12610 100644
--- a/docs/tutorials/modwsgi/index.rst
+++ b/docs/tutorials/modwsgi/index.rst
@@ -7,12 +7,11 @@ Running a :app:`Pyramid` Application under ``mod_wsgi``
It allows :term:`WSGI` programs to be served using the Apache web
server.
-This guide will outline broad steps that can be used to get a
-:app:`Pyramid` application running under Apache via ``mod_wsgi``.
-This particular tutorial was developed under Apple's Mac OS X platform
-(Snow Leopard, on a 32-bit Mac), but the instructions should be
-largely the same for all systems, delta specific path information for
-commands and files.
+This guide will outline broad steps that can be used to get a :app:`Pyramid`
+application running under Apache via ``mod_wsgi``. This particular tutorial
+was developed under Apple's Mac OS X platform (Snow Leopard, on a 32-bit
+Mac), but the instructions should be largely the same for all systems, delta
+specific path information for commands and files.
.. note:: Unfortunately these instructions almost certainly won't work for
deploying a :app:`Pyramid` application on a Windows system using
@@ -73,9 +72,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,12 +85,15 @@ commands and files.
``application`` is important: mod_wsgi requires finding such an
assignment when it opens the file.
-#. Make the ``pyramid.wsgi`` script executable.
+ The call to ``setup_logging`` initializes the standard library's
+ `logging` module to allow logging within your application.
+ See :ref:`logging_config`.
- .. code-block:: text
-
- $ cd ~/modwsgi/env
- $ chmod 755 pyramid.wsgi
+ There is no need to make the ``pyramid.wsgi`` script executable.
+ However, you'll need to make sure that *two* users have access to change
+ into the ``~/modwsgi/env`` directory: your current user (mine is
+ ``chrism`` and the user that Apache will run as often named ``apache`` or
+ ``httpd``). Make sure both of these users can "cd" into that directory.
#. Edit your Apache configuration and add some stuff. I happened to
create a file named ``/etc/apache2/other/modwsgi.conf`` on my own
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/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views.py
index 21f12b31d..50485d279 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/views.py
@@ -51,17 +51,17 @@ def view_page(context, request):
renderer='templates/edit.pt',
permission='edit')
def add_page(context, request):
- name = request.subpath[0]
+ pagename = request.subpath[0]
if 'form.submitted' in request.params:
body = request.params['body']
page = Page(body)
- page.__name__ = name
+ page.__name__ = pagename
page.__parent__ = context
- context[name] = page
+ context[pagename] = page
return HTTPFound(location = request.resource_url(page))
- save_url = request.resource_url(context, 'add_page', name)
+ save_url = request.resource_url(context, 'add_page', pagename)
page = Page('')
- page.__name__ = name
+ page.__name__ = pagename
page.__parent__ = context
return dict(page = page, save_url = save_url,
diff --git a/docs/tutorials/wiki/src/tests/tutorial/views.py b/docs/tutorials/wiki/src/tests/tutorial/views.py
index 21f12b31d..50485d279 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/views.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/views.py
@@ -51,17 +51,17 @@ def view_page(context, request):
renderer='templates/edit.pt',
permission='edit')
def add_page(context, request):
- name = request.subpath[0]
+ pagename = request.subpath[0]
if 'form.submitted' in request.params:
body = request.params['body']
page = Page(body)
- page.__name__ = name
+ page.__name__ = pagename
page.__parent__ = context
- context[name] = page
+ context[pagename] = page
return HTTPFound(location = request.resource_url(page))
- save_url = request.resource_url(context, 'add_page', name)
+ save_url = request.resource_url(context, 'add_page', pagename)
page = Page('')
- page.__name__ = name
+ page.__name__ = pagename
page.__parent__ = context
return dict(page = page, save_url = save_url,
diff --git a/docs/tutorials/wiki/src/views/tutorial/views.py b/docs/tutorials/wiki/src/views/tutorial/views.py
index 016f5b6bb..b0c15297f 100644
--- a/docs/tutorials/wiki/src/views/tutorial/views.py
+++ b/docs/tutorials/wiki/src/views/tutorial/views.py
@@ -35,17 +35,17 @@ def view_page(context, request):
@view_config(name='add_page', context='.models.Wiki',
renderer='templates/edit.pt')
def add_page(context, request):
- name = request.subpath[0]
+ pagename = request.subpath[0]
if 'form.submitted' in request.params:
body = request.params['body']
page = Page(body)
- page.__name__ = name
+ page.__name__ = pagename
page.__parent__ = context
- context[name] = page
+ context[pagename] = page
return HTTPFound(location = request.resource_url(page))
- save_url = request.resource_url(context, 'add_page', name)
+ save_url = request.resource_url(context, 'add_page', pagename)
page = Page('')
- page.__name__ = name
+ page.__name__ = pagename
page.__parent__ = context
return dict(page = page, save_url = save_url)
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/docs/tutorials/wiki2/src/authorization/tutorial/views.py b/docs/tutorials/wiki2/src/authorization/tutorial/views.py
index c7670b049..0d085b0e2 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views.py
@@ -60,14 +60,14 @@ def view_page(request):
@view_config(route_name='add_page', renderer='templates/edit.pt',
permission='edit')
def add_page(request):
- name = request.matchdict['pagename']
+ pagename = request.matchdict['pagename']
if 'form.submitted' in request.params:
body = request.params['body']
- page = Page(name, body)
+ page = Page(pagename, body)
DBSession.add(page)
return HTTPFound(location = request.route_url('view_page',
- pagename=name))
- save_url = request.route_url('add_page', pagename=name)
+ pagename=pagename))
+ save_url = request.route_url('add_page', pagename=pagename)
page = Page('', '')
return dict(page=page, save_url=save_url,
logged_in=authenticated_userid(request))
@@ -75,16 +75,16 @@ def add_page(request):
@view_config(route_name='edit_page', renderer='templates/edit.pt',
permission='edit')
def edit_page(request):
- name = request.matchdict['pagename']
- page = DBSession.query(Page).filter_by(name=name).one()
+ pagename = request.matchdict['pagename']
+ page = DBSession.query(Page).filter_by(name=pagename).one()
if 'form.submitted' in request.params:
page.data = request.params['body']
DBSession.add(page)
return HTTPFound(location = request.route_url('view_page',
- pagename=name))
+ pagename=pagename))
return dict(
page=page,
- save_url = request.route_url('edit_page', pagename=name),
+ save_url = request.route_url('edit_page', pagename=pagename),
logged_in=authenticated_userid(request),
)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views.py b/docs/tutorials/wiki2/src/tests/tutorial/views.py
index f2a33af1e..42ac0eb7f 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views.py
@@ -61,15 +61,15 @@ def view_page(request):
@view_config(route_name='add_page', renderer='templates/edit.pt',
permission='edit')
def add_page(request):
- name = request.matchdict['pagename']
+ pagename = request.matchdict['pagename']
if 'form.submitted' in request.params:
session = DBSession()
body = request.params['body']
- page = Page(name, body)
+ page = Page(pagename, body)
session.add(page)
return HTTPFound(location = request.route_url('view_page',
- pagename=name))
- save_url = request.route_url('add_page', pagename=name)
+ pagename=pagename))
+ save_url = request.route_url('add_page', pagename=pagename)
page = Page('', '')
return dict(page=page, save_url=save_url,
logged_in=authenticated_userid(request))
@@ -77,17 +77,17 @@ def add_page(request):
@view_config(route_name='edit_page', renderer='templates/edit.pt',
permission='edit')
def edit_page(request):
- name = request.matchdict['pagename']
+ pagename = request.matchdict['pagename']
session = DBSession()
- page = session.query(Page).filter_by(name=name).one()
+ page = session.query(Page).filter_by(name=pagename).one()
if 'form.submitted' in request.params:
page.data = request.params['body']
session.add(page)
return HTTPFound(location = request.route_url('view_page',
- pagename=name))
+ pagename=pagename))
return dict(
page=page,
- save_url = request.route_url('edit_page', pagename=name),
+ save_url = request.route_url('edit_page', pagename=pagename),
logged_in=authenticated_userid(request),
)
diff --git a/docs/tutorials/wiki2/src/views/tutorial/views.py b/docs/tutorials/wiki2/src/views/tutorial/views.py
index c2a94a96b..5a9c75a61 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/views.py
@@ -44,27 +44,27 @@ def view_page(request):
@view_config(route_name='add_page', renderer='templates/edit.pt')
def add_page(request):
- name = request.matchdict['pagename']
+ pagename = request.matchdict['pagename']
if 'form.submitted' in request.params:
body = request.params['body']
- page = Page(name, body)
+ page = Page(pagename, body)
DBSession.add(page)
return HTTPFound(location = request.route_url('view_page',
- pagename=name))
- save_url = request.route_url('add_page', pagename=name)
+ pagename=pagename))
+ save_url = request.route_url('add_page', pagename=pagename)
page = Page('', '')
return dict(page=page, save_url=save_url)
@view_config(route_name='edit_page', renderer='templates/edit.pt')
def edit_page(request):
- name = request.matchdict['pagename']
- page = DBSession.query(Page).filter_by(name=name).one()
+ pagename = request.matchdict['pagename']
+ page = DBSession.query(Page).filter_by(name=pagename).one()
if 'form.submitted' in request.params:
page.data = request.params['body']
DBSession.add(page)
return HTTPFound(location = request.route_url('view_page',
- pagename=name))
+ pagename=pagename))
return dict(
page=page,
- save_url = request.route_url('edit_page', pagename=name),
+ save_url = request.route_url('edit_page', pagename=pagename),
)
diff --git a/pyramid/chameleon_text.py b/pyramid/chameleon_text.py
index 872d3b920..21dc85528 100644
--- a/pyramid/chameleon_text.py
+++ b/pyramid/chameleon_text.py
@@ -1,35 +1,24 @@
import sys
-from zope.deprecation import deprecated
from zope.interface import implementer
-from pyramid.compat import reraise
-
-try:
- from chameleon.zpt.template import PageTextTemplateFile
- # prevent pyflakes complaining about a redefinition below
- PageTextTemplateFile
-except ImportError: # pragma: no cover
- exc_class, exc, tb = sys.exc_info()
- # Chameleon doesn't work on non-CPython platforms
- class PageTextTemplateFile(object):
- def __init__(self, *arg, **kw):
- reraise(ImportError, exc, tb)
+from chameleon.zpt.template import PageTextTemplateFile
from pyramid.interfaces import ITemplateRenderer
from pyramid.decorator import reify
from pyramid import renderers
-from pyramid.path import caller_package
def renderer_factory(info):
return renderers.template_renderer_factory(info, TextTemplateRenderer)
@implementer(ITemplateRenderer)
class TextTemplateRenderer(object):
- def __init__(self, path, lookup):
+ def __init__(self, path, lookup, macro=None):
self.path = path
self.lookup = lookup
+ # text template renderers have no macros, so we ignore the
+ # macro arg
@reify # avoid looking up reload_templates before manager pushed
def template(self):
@@ -52,93 +41,3 @@ class TextTemplateRenderer(object):
result = self.template(**system)
return result
-def get_renderer(path):
- """ Return a callable object which can be used to render a
- :term:`Chameleon` text 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`.
-
- .. warning::
-
- This API is deprecated in :app:`Pyramid` 1.0. Use
- :func:`pyramid.renderers.get_renderer` instead.
- """
- package = caller_package()
- factory = renderers.RendererHelper(path, package=package)
- return factory.get_renderer()
-
-deprecated(
- 'get_renderer',
- '(pyramid.chameleon_text.get_renderer is deprecated '
- 'as of Pyramid 1.0; instead use pyramid.renderers.get_renderer)')
-
-def get_template(path):
- """ Return the underyling object representing a :term:`Chameleon`
- text 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`.
-
- .. warning::
-
- This API is deprecated in :app:`Pyramid` 1.0. Use
- the ``implementation()`` method of a template renderer retrieved via
- :func:`pyramid.renderers.get_renderer` instead.
- """
- package = caller_package()
- factory = renderers.RendererHelper(path, package=package)
- return factory.get_renderer().implementation()
-
-deprecated(
- 'get_template',
- '(pyramid.chameleon_text.get_template is deprecated '
- 'as of Pyramid 1.0; instead use '
- 'pyramid.renderers.get_renderer().implementation())')
-
-def render_template(path, **kw):
- """ Render a :term:`Chameleon` text 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`. The arguments in ``*kw`` are passed as top-level
- names to the template, and so may be used within the template
- itself. Returns a string.
-
- .. warning::
-
- This API is deprecated in :app:`Pyramid` 1.0. Use
- :func:`pyramid.renderers.render` instead.
- """
- package = caller_package()
- request = kw.pop('request', None)
- renderer = renderers.RendererHelper(path, package=package)
- return renderer.render(kw, None, request=request)
-
-deprecated(
- 'render_template',
- '(pyramid.chameleon_text.render_template is deprecated '
- 'as of Pyramid 1.0; instead use pyramid.renderers.render)')
-
-def render_template_to_response(path, **kw):
- """ Render a :term:`Chameleon` text 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`. The arguments in ``*kw`` are passed as top-level
- names to the template, and so may be used within the template
- itself. Returns a :term:`Response` object with the body as the
- template result.
-
- .. warning::
-
- This API is deprecated in :app:`Pyramid` 1.0. Use
- :func:`pyramid.renderers.render_to_response` instead.
- """
- package = caller_package()
- request = kw.pop('request', None)
- renderer = renderers.RendererHelper(path, package=package)
- return renderer.render_to_response(kw, None, request=request)
-
-deprecated(
- 'render_template_to_response',
- '(pyramid.chameleon_text.render_template_to_response is deprecated '
- 'as of Pyramid 1.0; instead use pyramid.renderers.render_to_response)')
-
diff --git a/pyramid/chameleon_zpt.py b/pyramid/chameleon_zpt.py
index aa6f89e07..862e996b9 100644
--- a/pyramid/chameleon_zpt.py
+++ b/pyramid/chameleon_zpt.py
@@ -1,24 +1,11 @@
import sys
-from zope.deprecation import deprecated
from zope.interface import implementer
-from pyramid.compat import reraise
-
-try:
- from chameleon.zpt.template import PageTemplateFile
- PageTemplateFile # prevent pyflakes complaining about a redefinition below
-except ImportError: # pragma: no cover
- exc_class, exc, tb = sys.exc_info()
- # Chameleon doesn't work on non-CPython platforms
- class PageTemplateFile(object):
- def __init__(self, *arg, **kw):
- reraise(ImportError, exc, tb)
+from chameleon.zpt.template import PageTemplateFile
from pyramid.interfaces import ITemplateRenderer
-
from pyramid.decorator import reify
-from pyramid.path import caller_package
from pyramid import renderers
def renderer_factory(info):
@@ -26,19 +13,26 @@ def renderer_factory(info):
@implementer(ITemplateRenderer)
class ZPTTemplateRenderer(object):
- def __init__(self, path, lookup):
+ def __init__(self, path, lookup, macro=None):
self.path = path
self.lookup = lookup
+ self.macro = macro
@reify # avoid looking up reload_templates before manager pushed
def template(self):
if sys.platform.startswith('java'): # pragma: no cover
raise RuntimeError(
'Chameleon templates are not compatible with Jython')
- return PageTemplateFile(self.path,
- auto_reload=self.lookup.auto_reload,
- debug=self.lookup.debug,
- translate=self.lookup.translate)
+ tf = PageTemplateFile(self.path,
+ auto_reload=self.lookup.auto_reload,
+ debug=self.lookup.debug,
+ translate=self.lookup.translate)
+ if self.macro:
+ # render only the portion of the template included in a
+ # define-macro named the value of self.macro
+ macro_renderer = tf.macros[self.macro].include
+ tf._render = macro_renderer
+ return tf
def implementation(self):
return self.template
@@ -51,92 +45,3 @@ class ZPTTemplateRenderer(object):
result = self.template(**system)
return result
-def get_renderer(path):
- """ Return a callable object which can be used to render a
- :term:`Chameleon` ZPT 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`.
-
- .. warning::
-
- This API is deprecated in :app:`Pyramid` 1.0. Use
- :func:`pyramid.renderers.get_renderer` instead.
- """
- package = caller_package()
- factory = renderers.RendererHelper(name=path, package=package)
- return factory.get_renderer()
-
-deprecated(
- 'get_renderer',
- '(pyramid.chameleon_zpt.get_renderer is deprecated '
- 'as of Pyramid 1.0; instead use pyramid.renderers.get_renderer)')
-
-def get_template(path):
- """ Return the underyling object representing a :term:`Chameleon`
- ZPT 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`.
-
- .. warning::
-
- This API is deprecated in :app:`Pyramid` 1.0. Use
- the ``implementation()`` method of a template renderer retrieved via
- :func:`pyramid.renderers.get_renderer` instead.
- """
- package = caller_package()
- factory = renderers.RendererHelper(name=path, package=package)
- return factory.get_renderer().implementation()
-
-deprecated(
- 'get_template',
- '(pyramid.chameleon_zpt.get_template is deprecated '
- 'as of Pyramid 1.0; instead use '
- 'pyramid.renderers.get_renderer().implementation())')
-
-def render_template(path, **kw):
- """ Render a :term:`Chameleon` ZPT 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`. The arguments in ``*kw`` are passed as top-level
- names to the template, and so may be used within the template
- itself. Returns a string.
-
- .. warning::
-
- This API is deprecated in :app:`Pyramid` 1.0. Use
- :func:`pyramid.renderers.render` instead.
- """
- package = caller_package()
- request = kw.pop('request', None)
- renderer = renderers.RendererHelper(name=path, package=package)
- return renderer.render(kw, None, request=request)
-
-deprecated(
- 'render_template',
- '(pyramid.chameleon_zpt.render_template is deprecated as of Pyramid 1.0; '
- 'instead use pyramid.renderers.render)')
-
-def render_template_to_response(path, **kw):
- """ Render a :term:`Chameleon` ZPT 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`. The arguments in ``*kw`` are passed as top-level
- names to the template, and so may be used within the template
- itself. Returns a :term:`Response` object with the body as the
- template result.
-
- .. warning::
-
- This API is deprecated in :app:`Pyramid` 1.0. Use
- :func:`pyramid.renderers.render_to_response` instead.
- """
- package = caller_package()
- request = kw.pop('request', None)
- renderer = renderers.RendererHelper(name=path, package=package)
- return renderer.render_to_response(kw, None, request=request)
-
-deprecated(
- 'render_template_to_response',
- '(pyramid.chameleon_zpt.render_template_to_response is deprecated; as of '
- 'Pyramid 1.0, instead use pyramid.renderers.render_to_response)')
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py
index 52d7aca83..1dc438597 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
@@ -12,6 +12,8 @@ from webob.exc import WSGIHTTPException as WebobWSGIHTTPException
from pyramid.interfaces import (
IDebugLogger,
IExceptionResponse,
+ IPredicateList,
+ PHASE1_CONFIG,
)
from pyramid.asset import resolve_asset_spec
@@ -22,7 +24,6 @@ from pyramid.compat import (
text_,
reraise,
string_types,
- PY3,
)
from pyramid.events import ApplicationCreated
@@ -44,6 +45,7 @@ from pyramid.registry import (
Introspectable,
Introspector,
Registry,
+ undefer,
)
from pyramid.router import Router
@@ -71,6 +73,7 @@ from pyramid.config.tweens import TweensConfiguratorMixin
from pyramid.config.util import (
action_method,
ActionInfo,
+ PredicateList,
)
from pyramid.config.views import ViewsConfiguratorMixin
from pyramid.config.zca import ZCAConfiguratorMixin
@@ -353,6 +356,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)
@@ -486,6 +492,32 @@ class Configurator(
_get_introspector, _set_introspector, _del_introspector
)
+ def get_predlist(self, name):
+ predlist = self.registry.queryUtility(IPredicateList, name=name)
+ if predlist is None:
+ predlist = PredicateList()
+ self.registry.registerUtility(predlist, IPredicateList, name=name)
+ return predlist
+
+ def _add_predicate(self, type, name, factory, weighs_more_than=None,
+ weighs_less_than=None):
+ discriminator = ('%s predicate' % type, name)
+ intr = self.introspectable(
+ '%s predicates' % type,
+ discriminator,
+ '%s predicate named %s' % (type, name),
+ '%s predicate' % type)
+ intr['name'] = name
+ intr['factory'] = factory
+ intr['weighs_more_than'] = weighs_more_than
+ intr['weighs_less_than'] = weighs_less_than
+ def register():
+ predlist = self.get_predlist(type)
+ 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 early
+
@property
def action_info(self):
info = self.info # usually a ZCML action (ParserInfo) if self.info
@@ -547,6 +579,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 +811,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 +1104,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/adapters.py b/pyramid/config/adapters.py
index 5f15f2e46..12c4de660 100644
--- a/pyramid/config/adapters.py
+++ b/pyramid/config/adapters.py
@@ -1,3 +1,5 @@
+from functools import update_wrapper
+
from zope.interface import Interface
from pyramid.interfaces import (
@@ -6,40 +8,116 @@ from pyramid.interfaces import (
IResourceURL,
)
-from pyramid.config.util import action_method
+from pyramid.config.util import (
+ action_method,
+ )
+
class AdaptersConfiguratorMixin(object):
@action_method
- def add_subscriber(self, subscriber, iface=None):
+ def add_subscriber(self, subscriber, iface=None, **predicates):
"""Add an event :term:`subscriber` for the event stream
- implied by the supplied ``iface`` interface. The
- ``subscriber`` argument represents a callable object (or a
- :term:`dotted Python name` which identifies a callable); it
- will be called with a single object ``event`` whenever
- :app:`Pyramid` emits an :term:`event` associated with the
- ``iface``, which may be an :term:`interface` or a class or a
- :term:`dotted Python name` to a global object representing an
- interface or a class. Using the default ``iface`` value,
- ``None`` will cause the subscriber to be registered for all
- event types. See :ref:`events_chapter` for more information
- about events and subscribers."""
+ implied by the supplied ``iface`` interface.
+
+ The ``subscriber`` argument represents a callable object (or a
+ :term:`dotted Python name` which identifies a callable); it will be
+ called with a single object ``event`` whenever :app:`Pyramid` emits
+ an :term:`event` associated with the ``iface``, which may be an
+ :term:`interface` or a class or a :term:`dotted Python name` to a
+ global object representing an interface or a class.
+
+ Using the default ``iface`` value, ``None`` will cause the subscriber
+ to be registered for all event types. See :ref:`events_chapter` for
+ more information about events and subscribers.
+
+ Any number of predicate keyword arguments may be passed in
+ ``**predicates``. Each predicate named will narrow the set of
+ circumstances that the subscriber will be invoked. Each named
+ predicate must have been registered via
+ :meth:`pyramid.config.Configurator.add_subscriber_predicate` before it
+ can be used. See :ref:`subscriber_predicates` for more information.
+
+ .. note::
+
+ THe ``**predicates`` argument is new as of Pyramid 1.4.
+ """
dotted = self.maybe_dotted
subscriber, iface = dotted(subscriber), dotted(iface)
if iface is None:
iface = (Interface,)
if not isinstance(iface, (tuple, list)):
iface = (iface,)
+
def register():
- self.registry.registerHandler(subscriber, iface)
- intr = self.introspectable('subscribers',
- id(subscriber),
- self.object_description(subscriber),
- 'subscriber')
+ predlist = self.get_predlist('subscriber')
+ order, preds, phash = predlist.make(self, **predicates)
+ intr.update({'phash':phash, 'order':order, 'predicates':preds})
+ derived_subscriber = self._derive_subscriber(subscriber, preds)
+ self.registry.registerHandler(derived_subscriber, iface)
+
+ intr = self.introspectable(
+ 'subscribers',
+ id(subscriber),
+ self.object_description(subscriber),
+ 'subscriber'
+ )
+
intr['subscriber'] = subscriber
intr['interfaces'] = iface
+
self.action(None, register, introspectables=(intr,))
return subscriber
+ def _derive_subscriber(self, subscriber, predicates):
+ if not predicates:
+ return subscriber
+ def subscriber_wrapper(*arg):
+ # We need to accept *arg and pass it along because zope
+ # subscribers are designed poorly. Notification will always call
+ # an associated subscriber with all of the objects involved in
+ # the subscription lookup, despite the fact that the event sender
+ # always has the option to attach those objects to the event
+ # object itself (and usually does). It would be much saner if the
+ # registry just used extra args passed to notify to do the lookup
+ # but only called event subscribers with the actual event object,
+ # or if we had been smart enough early on to always wrap
+ # subscribers in something that threw away the extra args, but
+ # c'est la vie.
+ if all((predicate(*arg) for predicate in predicates)):
+ return subscriber(*arg)
+ if hasattr(subscriber, '__name__'):
+ update_wrapper(subscriber_wrapper, subscriber)
+ return subscriber_wrapper
+
+ @action_method
+ def add_subscriber_predicate(self, name, factory, weighs_more_than=None,
+ weighs_less_than=None):
+ """
+ Adds a subscriber predicate factory. The associated subscriber
+ predicate can later be named as a keyword argument to
+ :meth:`pyramid.config.Configurator.add_subscriber` in the
+ ``**predicates`` anonyous keyword argument dictionary.
+
+ ``name`` should be the name of the predicate. It must be a valid
+ Python identifier (it will be used as a ``**predicates`` keyword
+ argument to :meth:`~pyramid.config.Configurator.add_subscriber`).
+
+ ``factory`` should be a :term:`predicate factory`.
+
+ See :ref:`subscriber_predicates` for more information.
+
+ .. note::
+
+ This method is new as of Pyramid 1.4.
+ """
+ self._add_predicate(
+ 'subscriber',
+ name,
+ factory,
+ weighs_more_than=weighs_more_than,
+ weighs_less_than=weighs_less_than
+ )
+
@action_method
def add_response_adapter(self, adapter, type_or_iface):
""" When an object of type (or interface) ``type_or_iface`` is
@@ -203,4 +281,3 @@ class AdaptersConfiguratorMixin(object):
intr['resource_iface'] = resource_iface
self.action(discriminator, register, introspectables=(intr,))
-
diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py
index ccbf3bbe9..e46519bf5 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,
)
@@ -73,9 +76,9 @@ class FactoriesConfiguratorMixin(object):
:class:`pyramid.request.Request` class (particularly
``__call__``, and ``blank``).
- See :meth:`pyramid.config.Configurator.set_request_property`
+ See :meth:`pyramid.config.Configurator.add_request_method`
for a less intrusive way to extend the request objects with
- custom properties.
+ custom methods and properties.
.. note::
@@ -93,55 +96,106 @@ 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 add_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)
-
- if plist is None:
- plist = []
- self.registry.registerUtility(plist, IRequestProperties)
- self.registry.registerHandler(_set_request_properties,
- (INewRequest,))
-
- plist.append((name, callable))
-
- 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,))
-
-def _set_request_properties(event):
- request = event.request
- plist = request.registry.queryUtility(IRequestProperties)
- request._set_properties(plist)
+ exts = self.registry.queryUtility(IRequestExtensions)
+
+ if exts is None:
+ exts = _RequestExtensions()
+ self.registry.registerUtility(exts, IRequestExtensions)
+
+ 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 docs-deprecated as of Pyramid 1.4.
+ :meth:`pyramid.config.Configurator.add_request_method` should be
+ used instead.
+
+ .. versionadded:: 1.3
+ """
+ self.add_request_method(
+ callable, name=name, property=not reify, reify=reify)
+
+@implementer(IRequestExtensions)
+class _RequestExtensions(object):
+ def __init__(self):
+ self.descriptors = {}
+ self.methods = {}
+
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..1a7fdfac9 100644
--- a/pyramid/config/routes.py
+++ b/pyramid/config/routes.py
@@ -4,19 +4,22 @@ from pyramid.interfaces import (
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,
)
+import pyramid.config.predicates
+
class RoutesConfiguratorMixin(object):
@action_method
def add_route(self,
@@ -28,7 +31,7 @@ class RoutesConfiguratorMixin(object):
factory=None,
for_=None,
header=None,
- xhr=False,
+ xhr=None,
accept=None,
path_info=None,
request_method=None,
@@ -44,7 +47,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 +257,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:`view_and_route_predicates` for more information about
+ third-party predicates. This argument is new as of Pyramid 1.4.
+
View-Related Arguments
.. warning::
@@ -351,17 +363,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 +418,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.get_predlist('route')
+ _, 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 +464,47 @@ class RoutesConfiguratorMixin(object):
attr=view_attr,
)
+ @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:`view_and_route_predicates` for more information.
+
+ .. note::
+
+ This method is new as of Pyramid 1.4.
+ """
+ self._add_predicate(
+ 'route',
+ name,
+ factory,
+ weighs_more_than=weighs_more_than,
+ weighs_less_than=weighs_less_than
+ )
+
+ 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..a4df44408 100644
--- a/pyramid/config/util.py
+++ b/pyramid/config/util.py
@@ -1,6 +1,7 @@
-import re
import traceback
+from functools import update_wrapper
+
from zope.interface import implementer
from pyramid.interfaces import IActionInfo
@@ -12,10 +13,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
@@ -59,233 +57,11 @@ def action_method(wrapped):
finally:
self._ainfo.pop()
return result
- wrapper.__name__ = wrapped.__name__
- wrapper.__doc__ = wrapped.__doc__
- 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
+ if hasattr(wrapped, '__name__'):
+ update_wrapper(wrapper, wrapped)
+ wrapper.__docobj__ = wrapped
+ return wrapper
def as_sorted_tuple(val):
if not is_nonstr_iter(val):
@@ -293,3 +69,233 @@ def as_sorted_tuple(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..36896a17e 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -54,6 +54,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 +68,16 @@ 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,
)
urljoin = urlparse.urljoin
@@ -272,20 +279,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
@@ -564,7 +573,7 @@ class MultiView(object):
return
else:
subset.append((order, view, phash))
- subset.sort()
+ subset.sort(key=operator.itemgetter(0))
accepts = set(self.accepts)
accepts.add(accept)
self.accepts = list(accepts) # dedupe
@@ -629,13 +638,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 +685,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 +714,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 +998,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:`view_and_route_predicates` for more information about
+ third-party predicates. This argument is new as of Pyramid 1.4.
"""
view = self.maybe_dotted(view)
@@ -995,18 +1031,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_
@@ -1021,18 +1045,42 @@ class ViewsConfiguratorMixin(object):
name=renderer, package=self.package,
registry = self.registry)
+ if accept is not None:
+ accept = accept.lower()
+
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 +1103,15 @@ class ViewsConfiguratorMixin(object):
decorator=decorator,
)
)
+ view_intr.update(**predicates)
introspectables.append(view_intr)
+ predlist = self.get_predlist('view')
def register(permission=permission, renderer=renderer):
+ # the discrim_func above is guaranteed to have been called already
+ order = view_intr['order']
+ preds = view_intr['predicates']
+ phash = view_intr['phash']
request_iface = IRequest
if route_name is not None:
request_iface = self.registry.queryUtility(IRouteRequest,
@@ -1082,21 +1136,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 +1262,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 +1288,62 @@ 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)
+ @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:`view_and_route_predicates` for more information.
+
+ .. note::
+
+ This method is new as of Pyramid 1.4.
+ """
+ self._add_predicate(
+ 'view',
+ name,
+ factory,
+ weighs_more_than=weighs_more_than,
+ weighs_less_than=weighs_less_than
+ )
+
+ 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 +1461,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 +1497,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 +1535,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 +1578,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 +1596,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 +1622,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/configuration.py b/pyramid/configuration.py
deleted file mode 100644
index 802c10d1f..000000000
--- a/pyramid/configuration.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from pyramid.config import Configurator as BaseConfigurator
-from pyramid.exceptions import ConfigurationError # API
-from pyramid.config import DEFAULT_RENDERERS
-from pyramid.path import caller_package
-
-from zope.deprecation import deprecated
-
-ConfigurationError = ConfigurationError # pyflakes
-
-deprecated(
- 'ConfigurationError',
- 'pyramid.configuration.ConfigurationError is deprecated as of '
- 'Pyramid 1.0. Use ``pyramid.config.ConfigurationError`` instead.')
-
-class Configurator(BaseConfigurator):
- def __init__(self,
- registry=None,
- package=None,
- settings=None,
- root_factory=None,
- authentication_policy=None,
- authorization_policy=None,
- renderers=DEFAULT_RENDERERS,
- debug_logger=None,
- locale_negotiator=None,
- request_factory=None,
- renderer_globals_factory=None,
- default_permission=None,
- session_factory=None,
- autocommit=True,
- route_prefix=None,
- introspection=True,
- ):
- if package is None:
- package = caller_package()
- BaseConfigurator.__init__(
- self,
- registry=registry,
- package=package,
- settings=settings,
- root_factory=root_factory,
- authentication_policy=authentication_policy,
- authorization_policy=authorization_policy,
- renderers=renderers,
- debug_logger=debug_logger,
- locale_negotiator=locale_negotiator,
- request_factory=request_factory,
- renderer_globals_factory=renderer_globals_factory,
- default_permission=default_permission,
- session_factory=session_factory,
- autocommit=autocommit,
- route_prefix=route_prefix,
- introspection=introspection,
- )
-
-deprecated(
- 'Configurator',
- 'pyramid.configuration.Configurator is deprecated as of Pyramid 1.0. Use'
- '``pyramid.config.Configurator`` with ``autocommit=True`` instead.')
-
diff --git a/pyramid/decorator.py b/pyramid/decorator.py
index 98d7b36b5..82d2b1280 100644
--- a/pyramid/decorator.py
+++ b/pyramid/decorator.py
@@ -1,9 +1,31 @@
class reify(object):
+ """ Use as a class method decorator. It operates almost exactly like the
+ Python ``@property`` decorator, but it puts the result of the method it
+ decorates into the instance dict after the first call, effectively
+ replacing the function it decorates with an instance variable. It is, in
+ Python parlance, a non-data descriptor. An example:
- """ Put the result of a method which uses this (non-data)
- descriptor decorator in the instance dict after the first call,
- effectively replacing the decorator with an instance variable."""
+ .. code-block:: python
+ class Foo(object):
+ @reify
+ def jammy(self):
+ print 'jammy called'
+ return 1
+
+ And usage of Foo:
+
+ .. code-block:: text
+
+ >>> f = Foo()
+ >>> v = f.jammy
+ 'jammy called'
+ >>> print v
+ 1
+ >>> f.jammy
+ 1
+ >>> # jammy func not called the second time; it replaced itself with 1
+ """
def __init__(self, wrapped):
self.wrapped = wrapped
try:
diff --git a/pyramid/events.py b/pyramid/events.py
index e181ef33f..836466ba2 100644
--- a/pyramid/events.py
+++ b/pyramid/events.py
@@ -14,9 +14,10 @@ from pyramid.interfaces import (
)
class subscriber(object):
- """ Decorator activated via a :term:`scan` which treats the
- function being decorated as an event subscriber for the set of
- interfaces passed as ``*ifaces`` to the decorator constructor.
+ """ Decorator activated via a :term:`scan` which treats the function
+ being decorated as an event subscriber for the set of interfaces passed
+ as ``*ifaces`` and the set of predicate terms passed as ``**predicates``
+ to the decorator constructor.
For example:
@@ -61,16 +62,22 @@ class subscriber(object):
config = Configurator()
config.scan('somepackage_containing_subscribers')
+ Any ``**predicate`` arguments will be passed along to
+ :meth:`pyramid.config.Configurator.add_subscriber`. See
+ :ref:`subscriber_predicates` for a description of how predicates can
+ narrow the set of circumstances in which a subscriber will be called.
+
"""
venusian = venusian # for unit testing
- def __init__(self, *ifaces):
+ def __init__(self, *ifaces, **predicates):
self.ifaces = ifaces
+ self.predicates = predicates
def register(self, scanner, name, wrapped):
config = scanner.config
for iface in self.ifaces or (Interface,):
- config.add_subscriber(wrapped, iface)
+ config.add_subscriber(wrapped, iface, **self.predicates)
def __call__(self, wrapped):
self.venusian.attach(wrapped, self.register, category='pyramid')
@@ -200,10 +207,34 @@ class BeforeRender(dict):
setting an overriding value (which can be done using ``.get`` or
``__contains__`` of the event object).
- The event has an additional attribute named ``rendering_val``. This is
- the (non-system) value returned by a view or passed to ``render*`` as
- ``value``. This feature is new in Pyramid 1.2.
-
+ The dictionary returned from the view is accessible through the
+ :attr:`rendering_val` attribute of a :class:`~pyramid.events.BeforeRender`
+ event.
+
+ Suppose you return ``{'mykey': 'somevalue', 'mykey2': 'somevalue2'}`` from
+ your view callable, like so::
+
+ from pyramid.view import view_config
+
+ @view_config(renderer='some_renderer')
+ def myview(request):
+ return {'mykey': 'somevalue', 'mykey2': 'somevalue2'}
+
+ :attr:`rendering_val` can be used to access these values from the
+ :class:`~pyramid.events.BeforeRender` object::
+
+ from pyramid.events import subscriber
+ from pyramid.events import BeforeRender
+
+ @subscriber(BeforeRender)
+ def read_return(event):
+ # {'mykey': 'somevalue'} is returned from the view
+ print(event.rendering_val['mykey'])
+
+ In other words, :attr:`rendering_val` is the (non-system) value returned by a
+ view or passed to ``render*`` as ``value``. This feature is new in Pyramid
+ 1.2.
+
For a description of the values present in the renderer globals dictionary,
see :ref:`renderer_system_values`.
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..5d09cad01 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,22 @@ 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
@@ -134,14 +156,11 @@ class MakoRendererFactoryHelper(object):
preprocessor=preprocessor
)
- registry_lock.acquire()
- try:
- registry.registerUtility(lookup, IMakoLookup,
+ with registry_lock:
+ 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 +175,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 +194,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/paster.py b/pyramid/paster.py
index 5102e7b9b..b0e4d7933 100644
--- a/pyramid/paster.py
+++ b/pyramid/paster.py
@@ -1,7 +1,5 @@
import os
-import zope.deprecation
-
from paste.deploy import (
loadapp,
appconfig,
@@ -10,14 +8,6 @@ from paste.deploy import (
from pyramid.compat import configparser
from logging.config import fileConfig
from pyramid.scripting import prepare
-from pyramid.scaffolds import PyramidTemplate # bw compat
-
-PyramidTemplate = PyramidTemplate # pyflakes
-
-zope.deprecation.deprecated(
- 'PyramidTemplate', ('pyramid.paster.PyramidTemplate was moved to '
- 'pyramid.scaffolds.PyramidTemplate in Pyramid 1.1'),
-)
def get_app(config_uri, name=None, loadapp=loadapp):
""" Return the WSGI application named ``name`` in the PasteDeploy
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/renderers.py b/pyramid/renderers.py
index e526f9997..3252c2c93 100644
--- a/pyramid/renderers.py
+++ b/pyramid/renderers.py
@@ -1,5 +1,6 @@
import json
import os
+import re
import pkg_resources
import threading
@@ -426,18 +427,24 @@ class ChameleonRendererLookup(object):
raise ValueError('Missing template file: %s' % spec)
renderer = registry.queryUtility(ITemplateRenderer, name=spec)
if renderer is None:
- renderer = self.impl(spec, self)
+ renderer = self.impl(spec, self, macro=None)
# cache the template
- try:
- self.lock.acquire()
+ with self.lock:
registry.registerUtility(renderer,
ITemplateRenderer, name=spec)
- finally:
- self.lock.release()
else:
# spec is a package:relpath asset spec
renderer = registry.queryUtility(ITemplateRenderer, name=spec)
if renderer is None:
+ p = re.compile(
+ r'(?P<asset>[\w_.:/]+)'
+ r'(?:\#(?P<defname>[\w_]+))?'
+ r'(\.(?P<ext>.*))'
+ )
+ asset, macro, ext = p.match(spec).group(
+ 'asset', 'defname', 'ext'
+ )
+ spec = '%s.%s' % (asset, ext)
try:
package_name, filename = spec.split(':', 1)
except ValueError: # pragma: no cover
@@ -450,16 +457,13 @@ class ChameleonRendererLookup(object):
if not pkg_resources.resource_exists(package_name, filename):
raise ValueError(
'Missing template asset: %s (%s)' % (spec, abspath))
- renderer = self.impl(abspath, self)
+ renderer = self.impl(abspath, self, macro=macro)
settings = info.settings
if not settings.get('reload_assets'):
# cache the template
- self.lock.acquire()
- try:
+ with self.lock:
registry.registerUtility(renderer, ITemplateRenderer,
name=spec)
- finally:
- self.lock.release()
return renderer
@@ -470,11 +474,8 @@ def template_renderer_factory(info, impl, lock=registry_lock):
lookup = registry.queryUtility(IChameleonLookup, name=info.type)
if lookup is None:
lookup = ChameleonRendererLookup(impl, registry)
- lock.acquire()
- try:
+ with lock:
registry.registerUtility(lookup, IChameleonLookup, name=info.type)
- finally:
- lock.release()
return lookup(info)
@implementer(IRendererInfo)
diff --git a/pyramid/request.py b/pyramid/request.py
index 37fac6a46..af3310829 100644
--- a/pyramid/request.py
+++ b/pyramid/request.py
@@ -454,13 +454,4 @@ def call_app_with_subpath_as_path_info(request, app):
new_request.environ['SCRIPT_NAME'] = new_script_name
new_request.environ['PATH_INFO'] = new_path_info
- # In case downstream WSGI app is a Pyramid app, hack around existence of
- # these envars until we can safely remove them (see router.py); in any
- # case, even if these get removed, it might be better to not copy the
- # existing environ but to create a new one instead.
- if 'bfg.routes.route' in new_request.environ:
- del new_request.environ['bfg.routes.route']
- if 'bfg.routes.matchdict' in new_request.environ:
- del new_request.environ['bfg.routes.matchdict']
-
return new_request.get_response(app)
diff --git a/pyramid/router.py b/pyramid/router.py
index 0c115a1ac..0cbe00f3a 100644
--- a/pyramid/router.py
+++ b/pyramid/router.py
@@ -6,6 +6,7 @@ from zope.interface import (
from pyramid.interfaces import (
IDebugLogger,
IRequest,
+ IRequestExtensions,
IRootFactory,
IRouteRequest,
IRouter,
@@ -48,6 +49,7 @@ class Router(object):
self.root_factory = q(IRootFactory, default=DefaultRootFactory)
self.routes_mapper = q(IRoutesMapper)
self.request_factory = q(IRequestFactory, default=Request)
+ self.request_extensions = q(IRequestExtensions)
tweens = q(ITweens)
if tweens is None:
tweens = excview_tween_factory
@@ -84,13 +86,6 @@ class Router(object):
request.url)
logger and logger.debug(msg)
else:
- # TODO: kill off bfg.routes.* environ keys
- # when traverser requires request arg, and
- # cant cope with environ anymore (they are
- # docs-deprecated as of BFG 1.3)
- environ = request.environ
- environ['bfg.routes.route'] = route
- environ['bfg.routes.matchdict'] = match
attrs['matchdict'] = match
attrs['matched_route'] = route
@@ -105,7 +100,8 @@ class Router(object):
request.url,
route.name,
request.path_info,
- route.pattern, match,
+ route.pattern,
+ match,
', '.join([p.__text__ for p in route.predicates]))
)
logger and logger.debug(msg)
@@ -184,6 +180,9 @@ class Router(object):
try:
try:
+ extensions = self.request_extensions
+ if extensions is not None:
+ request._set_extensions(extensions)
response = self.handle_request(request)
has_listeners and notify(NewResponse(request, response))
@@ -198,4 +197,3 @@ class Router(object):
finally:
manager.pop()
-
diff --git a/pyramid/scripting.py b/pyramid/scripting.py
index f1dc24637..00177986f 100644
--- a/pyramid/scripting.py
+++ b/pyramid/scripting.py
@@ -3,6 +3,7 @@ from pyramid.exceptions import ConfigurationError
from pyramid.request import Request
from pyramid.interfaces import (
+ IRequestExtensions,
IRequestFactory,
IRootFactory,
)
@@ -70,14 +71,18 @@ def prepare(request=None, registry=None):
'before trying to activate it.')
if request is None:
request = _make_request('/', registry)
- request.registry = registry
threadlocals = {'registry':registry, 'request':request}
threadlocal_manager.push(threadlocals)
+ extensions = registry.queryUtility(IRequestExtensions)
+ if extensions is not None:
+ request._set_extensions(extensions)
def closer():
threadlocal_manager.pop()
root_factory = registry.queryUtility(IRootFactory,
default=DefaultRootFactory)
root = root_factory(request)
+ if getattr(request, 'context', None) is None:
+ request.context = root
return {'root':root, 'closer':closer, 'registry':registry,
'request':request, 'root_factory':root_factory}
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/pserve.py b/pyramid/scripts/pserve.py
index ea2a4ae09..9fbf0729a 100644
--- a/pyramid/scripts/pserve.py
+++ b/pyramid/scripts/pserve.py
@@ -583,12 +583,8 @@ class LazyWriter(object):
def open(self):
if self.fileobj is None:
- self.lock.acquire()
- try:
- if self.fileobj is None:
- self.fileobj = open(self.filename, self.mode)
- finally:
- self.lock.release()
+ with self.lock:
+ self.fileobj = open(self.filename, self.mode)
return self.fileobj
def close(self):
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/session.py b/pyramid/session.py
index 76b2b30b1..40e21ddbc 100644
--- a/pyramid/session.py
+++ b/pyramid/session.py
@@ -33,6 +33,53 @@ def manage_accessed(wrapped):
accessed.__doc__ = wrapped.__doc__
return accessed
+def signed_serialize(data, secret):
+ """ Serialize any pickleable structure (``data``) and sign it
+ using the ``secret`` (must be a string). Return the
+ serialization, which includes the signature as its first 40 bytes.
+ The ``signed_deserialize`` method will deserialize such a value.
+
+ This function is useful for creating signed cookies. For example:
+
+ .. code-block:: python
+
+ cookieval = signed_serialize({'a':1}, 'secret')
+ response.set_cookie('signed_cookie', cookieval)
+ """
+ pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
+ sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest()
+ return sig + native_(base64.b64encode(pickled))
+
+def signed_deserialize(serialized, secret, hmac=hmac):
+ """ Deserialize the value returned from ``signed_serialize``. If
+ the value cannot be deserialized for any reason, a
+ :exc:`ValueError` exception will be raised.
+
+ This function is useful for deserializing a signed cookie value
+ created by ``signed_serialize``. For example:
+
+ .. code-block:: python
+
+ cookieval = request.cookies['signed_cookie']
+ data = signed_deserialize(cookieval, 'secret')
+ """
+ # hmac parameterized only for unit tests
+ try:
+ input_sig, pickled = (serialized[:40],
+ base64.b64decode(bytes_(serialized[40:])))
+ except (binascii.Error, TypeError) as e:
+ # Badly formed data can make base64 die
+ raise ValueError('Badly formed base64 data: %s' % e)
+
+ sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest()
+
+ # Avoid timing attacks (see
+ # http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf)
+ if strings_differ(sig, input_sig):
+ raise ValueError('Invalid signature')
+
+ return pickle.loads(pickled)
+
def UnencryptedCookieSessionFactoryConfig(
secret,
timeout=1200,
@@ -43,6 +90,8 @@ def UnencryptedCookieSessionFactoryConfig(
cookie_secure=False,
cookie_httponly=False,
cookie_on_exception=True,
+ signed_serialize=signed_serialize,
+ signed_deserialize=signed_deserialize,
):
"""
Configure a :term:`session factory` which will provide unencrypted
@@ -89,6 +138,15 @@ def UnencryptedCookieSessionFactoryConfig(
If ``True``, set a session cookie even if an exception occurs
while rendering a view. Default: ``True``.
+ ``signed_serialize``
+ A callable which takes more or less arbitrary python data structure and
+ a secret and returns a signed serialization in bytes.
+ Default: ``signed_serialize`` (using pickle).
+
+ ``signed_deserialize``
+ A callable which takes a signed and serialized data structure in bytes
+ and a secret and returns the original data structure if the signature
+ is valid. Default: ``signed_deserialize`` (using pickle).
"""
@implementer(ISession)
@@ -225,51 +283,3 @@ def UnencryptedCookieSessionFactoryConfig(
return True
return UnencryptedCookieSessionFactory
-
-def signed_serialize(data, secret):
- """ Serialize any pickleable structure (``data``) and sign it
- using the ``secret`` (must be a string). Return the
- serialization, which includes the signature as its first 40 bytes.
- The ``signed_deserialize`` method will deserialize such a value.
-
- This function is useful for creating signed cookies. For example:
-
- .. code-block:: python
-
- cookieval = signed_serialize({'a':1}, 'secret')
- response.set_cookie('signed_cookie', cookieval)
- """
- pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
- sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest()
- return sig + native_(base64.b64encode(pickled))
-
-def signed_deserialize(serialized, secret, hmac=hmac):
- """ Deserialize the value returned from ``signed_serialize``. If
- the value cannot be deserialized for any reason, a
- :exc:`ValueError` exception will be raised.
-
- This function is useful for deserializing a signed cookie value
- created by ``signed_serialize``. For example:
-
- .. code-block:: python
-
- cookieval = request.cookies['signed_cookie']
- data = signed_deserialize(cookieval, 'secret')
- """
- # hmac parameterized only for unit tests
- try:
- input_sig, pickled = (serialized[:40],
- base64.b64decode(bytes_(serialized[40:])))
- except (binascii.Error, TypeError) as e:
- # Badly formed data can make base64 die
- raise ValueError('Badly formed base64 data: %s' % e)
-
- sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest()
-
- # Avoid timing attacks (see
- # http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf)
- if strings_differ(sig, input_sig):
- raise ValueError('Invalid signature')
-
- return pickle.loads(pickled)
-
diff --git a/pyramid/settings.py b/pyramid/settings.py
index 86304307e..e2cb3cb3c 100644
--- a/pyramid/settings.py
+++ b/pyramid/settings.py
@@ -1,31 +1,5 @@
-from zope.deprecation import deprecated
-
-from pyramid.threadlocal import get_current_registry
from pyramid.compat import string_types
-def get_settings():
- """
- Return a :term:`deployment settings` object for the current application.
- The object is a dictionary-like object that contains key/value pairs
- based on the dictionary passed as the ``settings`` argument to the
- :class:`pyramid.config.Configurator` constructor or the
- :func:`pyramid.router.make_app` API.
-
- .. warning:: This method is deprecated as of Pyramid 1.0. Use
- ``pyramid.threadlocal.get_current_registry().settings`` instead or use
- the ``settings`` attribute of the registry available from the request
- (``request.registry.settings``).
- """
- reg = get_current_registry()
- return reg.settings
-
-deprecated(
- 'get_settings',
- '(pyramid.settings.get_settings is deprecated as of Pyramid 1.0. Use'
- '``pyramid.threadlocal.get_current_registry().settings`` instead or use '
- 'the ``settings`` attribute of the registry available from the request '
- '(``request.registry.settings``)).')
-
truthy = frozenset(('t', 'true', 'y', 'yes', 'on', '1'))
def asbool(s):
diff --git a/pyramid/testing.py b/pyramid/testing.py
index 5589f6ae2..9e8f2bff3 100644
--- a/pyramid/testing.py
+++ b/pyramid/testing.py
@@ -2,20 +2,14 @@ import copy
import os
from contextlib import contextmanager
-from zope.deprecation import deprecated
-
from zope.interface import (
implementer,
- Interface,
alsoProvides,
)
from pyramid.interfaces import (
IRequest,
IResponseFactory,
- ISecuredView,
- IView,
- IViewClassifier,
ISession,
)
@@ -27,14 +21,12 @@ from pyramid.compat import (
from pyramid.config import Configurator
from pyramid.decorator import reify
-from pyramid.httpexceptions import HTTPForbidden
from pyramid.response import Response
from pyramid.registry import Registry
from pyramid.security import (
Authenticated,
Everyone,
- has_permission,
)
from pyramid.threadlocal import (
@@ -48,373 +40,10 @@ from pyramid.request import (
)
from pyramid.url import URLMethodsMixin
+from pyramid.util import InstancePropertyMixin
_marker = object()
-def registerDummySecurityPolicy(userid=None, groupids=(), permissive=True):
- """ Registers a pair of faux :app:`Pyramid` security policies:
- a :term:`authentication policy` and a :term:`authorization
- policy`.
-
- The behavior of the registered :term:`authorization policy`
- depends on the ``permissive`` argument. If ``permissive`` is
- true, a permissive :term:`authorization policy` is registered;
- this policy allows all access. If ``permissive`` is false, a
- nonpermissive :term:`authorization policy` is registered; this
- policy denies all access.
-
- The behavior of the registered :term:`authentication policy`
- depends on the values provided for the ``userid`` and ``groupids``
- argument. The authentication policy will return the userid
- identifier implied by the ``userid`` argument and the group ids
- implied by the ``groupids`` argument when the
- :func:`pyramid.security.authenticated_userid` or
- :func:`pyramid.security.effective_principals` APIs are used.
-
- This function is most useful when testing code that uses the APIs named
- :func:`pyramid.security.has_permission`,
- :func:`pyramid.security.authenticated_userid`,
- :func:`pyramid.security.unauthenticated_userid`,
- :func:`pyramid.security.effective_principals`, and
- :func:`pyramid.security.principals_allowed_by_permission`.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the
- :meth:`pyramid.config.Configurator.testing_securitypolicy`
- method in your unit and integration tests.
- """
- registry = get_current_registry()
- config = Configurator(registry=registry)
- result = config.testing_securitypolicy(userid=userid, groupids=groupids,
- permissive=permissive)
- config.commit()
- return result
-
-deprecated('registerDummySecurityPolicy',
- 'The testing.registerDummySecurityPolicy API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.testing_securitypolicy '
- 'method in your unit and integration tests.')
-
-def registerResources(resources):
- """ Registers a dictionary of :term:`resource` objects that can be
- resolved via the :func:`pyramid.traversal.find_resource` API.
-
- The :func:`pyramid.traversal.find_resource` API is called with a
- path as one of its arguments. If the dictionary you register when
- calling this method contains that path as a string key
- (e.g. ``/foo/bar`` or ``foo/bar``), the corresponding value will
- be returned to ``find_resource`` (and thus to your code) when
- :func:`pyramid.traversal.find_resource` is called with an
- equivalent path string or tuple.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the
- :meth:`pyramid.config.Configurator.testing_resources`
- method in your unit and integration tests.
-
- .. note:: For ancient backward compatibility purposes, this API can also
- be accessed as :func:`pyramid.testing.registerModels`.
- """
- registry = get_current_registry()
- config = Configurator(registry=registry)
- result = config.testing_resources(resources)
- config.commit()
- return result
-
-deprecated('registerResources',
- 'The testing.registerResources API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.testing_resources '
- 'method in your unit and integration tests.')
-
-registerModels = registerResources
-
-deprecated('registerModels',
- 'The testing.registerModels API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.testing_resources '
- 'method in your unit and integration tests.')
-
-def registerEventListener(event_iface=None):
- """ Registers an :term:`event` listener (aka :term:`subscriber`)
- listening for events of the type ``event_iface``. This method
- returns a list object which is appended to by the subscriber
- whenever an event is captured.
-
- When an event is dispatched that matches ``event_iface``, that
- event will be appended to the list. You can then compare the
- values in the list to expected event notifications. This method
- is useful when testing code that wants to call
- :meth:`pyramid.registry.Registry.notify`.
-
- The default value of ``event_iface`` (``None``) implies a
- subscriber registered for *any* kind of event.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the
- :meth:`pyramid.config.Configurator.testing_add_subscriber`
- method in your unit and integration tests.
- """
- registry = get_current_registry()
- config = Configurator(registry=registry)
- result = config.testing_add_subscriber(event_iface)
- config.commit()
- return result
-
-deprecated('registerEventListener',
- 'The testing.registerEventListener API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.testing_add_subscriber '
- 'method in your unit and integration tests.')
-
-def registerTemplateRenderer(path, renderer=None):
- """ Register a template renderer at ``path`` (usually a relative
- filename ala ``templates/foo.pt``) and return the renderer object.
- If the ``renderer`` argument is None, a 'dummy' renderer will be
- used. This function is useful when testing code that calls the
- :func:`pyramid.renderers.render` function or
- :func:`pyramid.renderers.render_to_response` function or any
- other ``render_*`` or ``get_*`` API of the
- :mod:`pyramid.renderers` module.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the
- :meth:`pyramid.config.Configurator.testing_add_renderer`
- method in your unit and integration tests.
-
- """
- registry = get_current_registry()
- config = Configurator(registry=registry)
- result = config.testing_add_template(path, renderer)
- config.commit()
- return result
-
-deprecated('registerTemplateRenderer',
- 'The testing.registerTemplateRenderer API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.testing_add_renderer '
- 'method in your unit and integration tests.')
-
-# registerDummyRenderer is a deprecated alias that should never be removed
-# (too much usage in the wild)
-registerDummyRenderer = registerTemplateRenderer
-
-deprecated('registerDummyRenderer',
- 'The testing.registerDummyRenderer API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.testing_add_renderer '
- 'method in your unit and integration tests.')
-
-def registerView(name, result='', view=None, for_=(Interface, Interface),
- permission=None):
- """ Registers a :app:`Pyramid` :term:`view callable` under the
- name implied by the ``name`` argument. The view will return a
- :term:`WebOb` :term:`Response` object with the value implied by
- the ``result`` argument as its ``body`` attribute. To gain more
- control, if you pass in a non-``None`` ``view`` argument, this
- value will be used as a view callable instead of an automatically
- generated view callable (and ``result`` is not used).
-
- To protect the view using a :term:`permission`, pass in a
- non-``None`` value as ``permission``. This permission will be
- checked by any active :term:`authorization policy` when view
- execution is attempted.
-
- This function is useful when testing code which calls
- :func:`pyramid.view.render_view_to_response`.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the
- :meth:`pyramid.config.Configurator.add_view`
- method in your unit and integration tests.
- """
- for_ = (IViewClassifier, ) + for_
- if view is None:
- def view(context, request):
- return Response(result)
- if permission is None:
- return registerAdapter(view, for_, IView, name)
- else:
- def _secure(context, request):
- if not has_permission(permission, context, request):
- raise HTTPForbidden('no permission')
- else:
- return view(context, request)
- _secure.__call_permissive__ = view
- def permitted(context, request):
- return has_permission(permission, context, request)
- _secure.__permitted__ = permitted
- return registerAdapter(_secure, for_, ISecuredView, name)
-
-deprecated('registerView',
- 'The registerView API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.add_view '
- 'method in your unit and integration tests.')
-
-def registerUtility(impl, iface=Interface, name=''):
- """ Register a ZCA utility component.
-
- The ``impl`` argument specifies the implementation of the utility.
- The ``iface`` argument specifies the :term:`interface` which will
- be later required to look up the utility
- (:class:`zope.interface.Interface`, by default). The ``name``
- argument implies the utility name; it is the empty string by
- default.
-
- See `The ZCA book <http://www.muthukadan.net/docs/zca.html>`_ for
- more information about ZCA utilities.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the :meth:`pyramid.Registry.registerUtility`
- method. The ``registry`` attribute of a :term:`Configurator`
- in your unit and integration tests is an instance of the
- :class:`pyramid.Registry` class.
- """
- reg = get_current_registry()
- reg.registerUtility(impl, iface, name=name)
- return impl
-
-deprecated('registerUtility',
- 'The registerUtility API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.registry.registerUtility method (via '
- 'e.g. "config.registry.registerUtility(..)" '
- 'method in your unit and integration tests.')
-
-def registerAdapter(impl, for_=Interface, provides=Interface, name=''):
- """ Register a ZCA adapter component.
-
- The ``impl`` argument specifies the implementation of the
- component (often a class). The ``for_`` argument implies the
- ``for`` interface type used for this registration; it is
- :class:`zope.interface.Interface` by default. If ``for`` is not a
- tuple or list, it will be converted to a one-tuple before being
- passed to underlying :meth:`pyramid.registry.registerAdapter`
- API.
-
- The ``provides`` argument specifies the ZCA 'provides' interface,
- :class:`zope.interface.Interface` by default.
-
- The ``name`` argument is the empty string by default; it implies
- the name under which the adapter is registered.
-
- See `The ZCA book <http://www.muthukadan.net/docs/zca.html>`_ for
- more information about ZCA adapters.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the :meth:`pyramid.Registry.registerAdapter`
- method. The ``registry`` attribute of a :term:`Configurator`
- in your unit and integration tests is an instance of the
- :class:`pyramid.Registry` class.
- """
- reg = get_current_registry()
- if not isinstance(for_, (tuple, list)):
- for_ = (for_,)
- reg.registerAdapter(impl, for_, provides, name=name)
- return impl
-
-deprecated('registerAdapter',
- 'The registerAdapter API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.registry.registerAdapter method (via '
- 'e.g. "config.registry.registerAdapter(..)" '
- 'method in your unit and integration tests.')
-
-def registerSubscriber(subscriber, iface=Interface):
- """ Register a ZCA subscriber component.
-
- The ``subscriber`` argument specifies the implementation of the
- subscriber component (often a function).
-
- The ``iface`` argument is the interface type for which the
- subscriber will be registered (:class:`zope.interface.Interface`
- by default). If ``iface`` is not a tuple or list, it will be
- converted to a one-tuple before being passed to the underlying ZCA
- :meth:`pyramid.registry.registerHandler` method.
-
- See `The ZCA book <http://www.muthukadan.net/docs/zca.html>`_ for
- more information about ZCA subscribers.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the
- :meth:`pyramid.config.Configurator.add_subscriber`
- method in your unit and integration tests.
- """
- registry = get_current_registry()
- config = Configurator(registry)
- result = config.add_subscriber(subscriber, iface=iface)
- config.commit()
- return result
-
-deprecated('registerSubscriber',
- 'The testing.registerSubscriber API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.add_subscriber '
- 'method in your unit and integration tests.')
-
-def registerRoute(pattern, name, factory=None):
- """ Register a new :term:`route` using a pattern
- (e.g. ``:pagename``), a name (e.g. ``home``), and an optional root
- factory.
-
- The ``pattern`` argument implies the route pattern. The ``name``
- argument implies the route name. The ``factory`` argument implies
- a :term:`root factory` associated with the route.
-
- This API is useful for testing code that calls
- e.g. :func:`pyramid.url.route_url`.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the
- :meth:`pyramid.config.Configurator.add_route`
- method in your unit and integration tests.
- """
- reg = get_current_registry()
- config = Configurator(registry=reg)
- result = config.add_route(name, pattern, factory=factory)
- config.commit()
- return result
-
-deprecated('registerRoute',
- 'The testing.registerRoute API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.add_route '
- 'method in your unit and integration tests.')
-
-def registerSettings(dictarg=None, **kw):
- """Register one or more 'setting' key/value pairs. A setting is
- a single key/value pair in the dictionary-ish object returned from
- the API :attr:`pyramid.registry.Registry.settings`.
-
- You may pass a dictionary::
-
- registerSettings({'external_uri':'http://example.com'})
-
- Or a set of key/value pairs::
-
- registerSettings(external_uri='http://example.com')
-
- Use of this function is required when you need to test code that calls
- the :attr:`pyramid.registry.Registry.settings` API and which uses return
- values from that API.
-
- .. warning:: This API is deprecated as of :app:`Pyramid` 1.0.
- Instead use the
- :meth:`pyramid.config.Configurator.add_settings`
- method in your unit and integration tests.
- """
- registry = get_current_registry()
- config = Configurator(registry=registry)
- config.add_settings(dictarg, **kw)
-
-deprecated('registerSettings',
- 'The testing.registerSettings API is deprecated as of '
- 'Pyramid 1.0. Instead use the '
- 'pyramid.config.Configurator.add_settings '
- 'method in your unit and integration tests.')
-
class DummyRootFactory(object):
__parent__ = None
__name__ = None
@@ -646,7 +275,7 @@ class DummySession(dict):
@implementer(IRequest)
class DummyRequest(DeprecatedRequestMethodsMixin, URLMethodsMixin,
- CallbackMethodsMixin):
+ CallbackMethodsMixin, InstancePropertyMixin):
""" A DummyRequest object (incompletely) imitates a :term:`request` object.
The ``params``, ``environ``, ``headers``, ``path``, and
@@ -665,7 +294,7 @@ class DummyRequest(DeprecatedRequestMethodsMixin, URLMethodsMixin,
request. For example, by default, the DummyRequest ``GET`` and ``POST``
attributes are of type ``dict``, unlike a normal Request's GET and POST,
which are of type ``MultiDict``. If your code uses the features of
- MultiDict, you should either use a"real" :class:`pyramid.request.Request`
+ MultiDict, you should either use a real :class:`pyramid.request.Request`
or adapt your DummyRequest by replacing the attributes with ``MultiDict``
instances.
@@ -825,6 +454,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/fixtures/withmacro.pt b/pyramid/tests/fixtures/withmacro.pt
new file mode 100644
index 000000000..8bca01e4d
--- /dev/null
+++ b/pyramid/tests/fixtures/withmacro.pt
@@ -0,0 +1,6 @@
+<html>
+<metal:m define-macro="foo">
+ Hello!
+</metal:m>
+</html>
+
diff --git a/pyramid/tests/test_chameleon_text.py b/pyramid/tests/test_chameleon_text.py
index 8d23c8753..d9f20f241 100644
--- a/pyramid/tests/test_chameleon_text.py
+++ b/pyramid/tests/test_chameleon_text.py
@@ -1,28 +1,32 @@
+import sys
import unittest
from pyramid.compat import binary_type
-from pyramid.testing import skip_on
from pyramid import testing
-class Base:
+class Base(object):
def setUp(self):
self.config = testing.setUp()
- from zope.deprecation import __show__
- __show__.off()
def tearDown(self):
testing.tearDown()
- from zope.deprecation import __show__
- __show__.on()
def _getTemplatePath(self, name):
import os
here = os.path.abspath(os.path.dirname(__file__))
return os.path.join(here, 'fixtures', name)
- def _registerUtility(self, utility, iface, name=''):
- reg = self.config.registry
- reg.registerUtility(utility, iface, name=name)
+class Test_renderer_factory(Base, unittest.TestCase):
+ def _callFUT(self, info):
+ from pyramid.chameleon_text import renderer_factory
+ return renderer_factory(info)
+
+ def test_it(self):
+ # this test is way too functional
+ from pyramid.chameleon_text import TextTemplateRenderer
+ info = DummyInfo()
+ result = self._callFUT(info)
+ self.assertEqual(result.__class__, TextTemplateRenderer)
class TextTemplateRendererTests(Base, unittest.TestCase):
def _getTargetClass(self):
@@ -45,7 +49,6 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
from pyramid.interfaces import ITemplateRenderer
verifyClass(ITemplateRenderer, self._getTargetClass())
- @skip_on('java')
def test_template_reified(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -54,7 +57,6 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template, instance.__dict__['template'])
- @skip_on('java')
def test_template_with_ichameleon_translate(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -63,7 +65,6 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.translate, lookup.translate)
- @skip_on('java')
def test_template_with_debug_templates(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -73,7 +74,6 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.debug, True)
- @skip_on('java')
def test_template_with_reload_templates(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -83,7 +83,6 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.auto_reload, True)
- @skip_on('java')
def test_template_without_reload_templates(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -93,7 +92,6 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.auto_reload, False)
- @skip_on('java')
def test_call(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -102,14 +100,12 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
self.assertTrue(isinstance(result, binary_type))
self.assertEqual(result, b'Hello.\n')
- @skip_on('java')
def test_call_with_nondict_value(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
instance = self._makeOne(minimal, lookup)
self.assertRaises(ValueError, instance, None, {})
- @skip_on('java')
def test_call_nonminimal(self):
nonminimal = self._getTemplatePath('nonminimal.txt')
lookup = DummyLookup()
@@ -118,7 +114,6 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
self.assertTrue(isinstance(result, binary_type))
self.assertEqual(result, b'Hello, Chris!\n')
- @skip_on('java')
def test_implementation(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -127,83 +122,24 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
self.assertTrue(isinstance(result, binary_type))
self.assertEqual(result, b'Hello.\n')
-class RenderTemplateTests(Base, unittest.TestCase):
- def _callFUT(self, name, **kw):
- from pyramid.chameleon_text import render_template
- return render_template(name, **kw)
-
- @skip_on('java')
- def test_it(self):
- minimal = self._getTemplatePath('minimal.txt')
- result = self._callFUT(minimal)
- self.assertTrue(isinstance(result, binary_type))
- self.assertEqual(result, b'Hello.\n')
-
-class RenderTemplateToResponseTests(Base, unittest.TestCase):
- def _callFUT(self, name, **kw):
- from pyramid.chameleon_text import render_template_to_response
- return render_template_to_response(name, **kw)
-
- @skip_on('java')
- def test_minimal(self):
- minimal = self._getTemplatePath('minimal.txt')
- result = self._callFUT(minimal)
- from webob import Response
- self.assertTrue(isinstance(result, Response))
- self.assertEqual(result.app_iter, [b'Hello.\n'])
- self.assertEqual(result.status, '200 OK')
- self.assertEqual(len(result.headerlist), 2)
-
- @skip_on('java')
- def test_iresponsefactory_override(self):
- from webob import Response
- class Response2(Response):
- pass
- from pyramid.interfaces import IResponseFactory
- self._registerUtility(Response2, IResponseFactory)
- minimal = self._getTemplatePath('minimal.txt')
- result = self._callFUT(minimal)
- self.assertTrue(isinstance(result, Response2))
-
-class GetRendererTests(Base, unittest.TestCase):
- def _callFUT(self, name):
- from pyramid.chameleon_text import get_renderer
- return get_renderer(name)
-
- @skip_on('java')
- def test_it(self):
- from pyramid.interfaces import IRendererFactory
- class Dummy:
- template = object()
- def implementation(self): pass
- renderer = Dummy()
- def rf(spec):
- return renderer
- self._registerUtility(rf, IRendererFactory, name='foo')
- result = self._callFUT('foo')
- self.assertTrue(result is renderer)
-
-class GetTemplateTests(Base, unittest.TestCase):
- def _callFUT(self, name):
- from pyramid.chameleon_text import get_template
- return get_template(name)
-
- @skip_on('java')
- def test_it(self):
- from pyramid.interfaces import IRendererFactory
- class Dummy:
- template = object()
- def implementation(self):
- return self.template
- renderer = Dummy()
- def rf(spec):
- return renderer
- self._registerUtility(rf, IRendererFactory, name='foo')
- result = self._callFUT('foo')
- self.assertTrue(result is renderer.template)
-
class DummyLookup(object):
auto_reload=True
debug = True
def translate(self, msg): pass
+
+class DummyRegistry(object):
+ def queryUtility(self, iface, name):
+ self.queried = iface, name
+ return None
+
+ def registerUtility(self, impl, iface, name):
+ self.registered = impl, iface, name
+
+class DummyInfo(object):
+ def __init__(self):
+ self.registry = DummyRegistry()
+ self.type = '.pt'
+ self.name = 'fixtures/minimal.pt'
+ self.package = sys.modules[__name__]
+ self.settings = {}
diff --git a/pyramid/tests/test_chameleon_zpt.py b/pyramid/tests/test_chameleon_zpt.py
index e7a1499e6..37538e83e 100644
--- a/pyramid/tests/test_chameleon_zpt.py
+++ b/pyramid/tests/test_chameleon_zpt.py
@@ -1,30 +1,33 @@
+import sys
import unittest
-from pyramid.testing import skip_on
from pyramid import testing
from pyramid.compat import text_type
class Base(object):
def setUp(self):
self.config = testing.setUp()
- from zope.deprecation import __show__
- __show__.off()
def tearDown(self):
testing.tearDown()
- from zope.deprecation import __show__
- __show__.on()
def _getTemplatePath(self, name):
import os
here = os.path.abspath(os.path.dirname(__file__))
return os.path.join(here, 'fixtures', name)
- def _registerUtility(self, utility, iface, name=''):
- reg = self.config.registry
- reg.registerUtility(utility, iface, name=name)
- return reg
-
+class Test_renderer_factory(Base, unittest.TestCase):
+ def _callFUT(self, info):
+ from pyramid.chameleon_zpt import renderer_factory
+ return renderer_factory(info)
+
+ def test_it(self):
+ # this test is way too functional
+ from pyramid.chameleon_zpt import ZPTTemplateRenderer
+ info = DummyInfo()
+ result = self._callFUT(info)
+ self.assertEqual(result.__class__, ZPTTemplateRenderer)
+
class ZPTTemplateRendererTests(Base, unittest.TestCase):
def _getTargetClass(self):
from pyramid.chameleon_zpt import ZPTTemplateRenderer
@@ -46,7 +49,6 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
from pyramid.interfaces import ITemplateRenderer
verifyClass(ITemplateRenderer, self._getTargetClass())
- @skip_on('java')
def test_call(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -56,7 +58,6 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
self.assertEqual(result.rstrip('\n'),
'<div xmlns="http://www.w3.org/1999/xhtml">\n</div>')
- @skip_on('java')
def test_template_reified(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -65,7 +66,6 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template, instance.__dict__['template'])
- @skip_on('java')
def test_template_with_ichameleon_translate(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -74,7 +74,6 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.translate, lookup.translate)
- @skip_on('java')
def test_template_with_debug_templates(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -84,7 +83,6 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.debug, True)
- @skip_on('java')
def test_template_without_debug_templates(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -94,7 +92,6 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.debug, False)
- @skip_on('java')
def test_template_with_reload_templates(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -104,7 +101,6 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.auto_reload, True)
- @skip_on('java')
def test_template_without_reload_templates(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -114,14 +110,12 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.auto_reload, False)
- @skip_on('java')
def test_call_with_nondict_value(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
instance = self._makeOne(minimal, lookup)
self.assertRaises(ValueError, instance, None, {})
- @skip_on('java')
def test_implementation(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -130,86 +124,35 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
self.assertTrue(isinstance(result, text_type))
self.assertEqual(result.rstrip('\n'),
'<div xmlns="http://www.w3.org/1999/xhtml">\n</div>')
-
-
-class RenderTemplateTests(Base, unittest.TestCase):
- def _callFUT(self, name, **kw):
- from pyramid.chameleon_zpt import render_template
- return render_template(name, **kw)
-
- @skip_on('java')
- def test_it(self):
- minimal = self._getTemplatePath('minimal.pt')
- result = self._callFUT(minimal)
- self.assertTrue(isinstance(result, text_type))
- self.assertEqual(result.rstrip('\n'),
- '<div xmlns="http://www.w3.org/1999/xhtml">\n</div>')
-
-class RenderTemplateToResponseTests(Base, unittest.TestCase):
- def _callFUT(self, name, **kw):
- from pyramid.chameleon_zpt import render_template_to_response
- return render_template_to_response(name, **kw)
- @skip_on('java')
- def test_it(self):
- minimal = self._getTemplatePath('minimal.pt')
- result = self._callFUT(minimal)
- from webob import Response
- self.assertTrue(isinstance(result, Response))
- self.assertEqual(result.app_iter[0].rstrip(b'\n'),
- b'<div xmlns="http://www.w3.org/1999/xhtml">\n</div>')
- self.assertEqual(result.status, '200 OK')
- self.assertEqual(len(result.headerlist), 2)
-
- @skip_on('java')
- def test_iresponsefactory_override(self):
- from webob import Response
- class Response2(Response):
- pass
- from pyramid.interfaces import IResponseFactory
- self._registerUtility(Response2, IResponseFactory)
- minimal = self._getTemplatePath('minimal.pt')
- result = self._callFUT(minimal)
- self.assertTrue(isinstance(result, Response2))
-
-class GetRendererTests(Base, unittest.TestCase):
- def _callFUT(self, name):
- from pyramid.chameleon_zpt import get_renderer
- return get_renderer(name)
-
- @skip_on('java')
- def test_it(self):
- from pyramid.interfaces import IRendererFactory
- class Dummy:
- template = object()
- def implementation(self): pass
- renderer = Dummy()
- def rf(spec):
- return renderer
- self._registerUtility(rf, IRendererFactory, name='foo')
- result = self._callFUT('foo')
- self.assertTrue(result is renderer)
-
-class GetTemplateTests(Base, unittest.TestCase):
- def _callFUT(self, name):
- from pyramid.chameleon_zpt import get_template
- return get_template(name)
-
- @skip_on('java')
- def test_it(self):
- from pyramid.interfaces import IRendererFactory
- class Dummy:
- template = object()
- def implementation(self):
- return self.template
- renderer = Dummy()
- def rf(spec):
- return renderer
- self._registerUtility(rf, IRendererFactory, name='foo')
- result = self._callFUT('foo')
- self.assertTrue(result is renderer.template)
+ def test_macro_supplied(self):
+ minimal = self._getTemplatePath('withmacro.pt')
+ lookup = DummyLookup()
+ instance = self._makeOne(minimal, lookup, macro='foo')
+ result = instance.implementation()()
+ self.assertEqual(result, '\n Hello!\n')
+
+
+
class DummyLookup(object):
auto_reload=True
debug = True
def translate(self, msg): pass
+
+class DummyRegistry(object):
+ def queryUtility(self, iface, name):
+ self.queried = iface, name
+ return None
+
+ def registerUtility(self, impl, iface, name):
+ self.registered = impl, iface, name
+
+class DummyInfo(object):
+ def __init__(self):
+ self.registry = DummyRegistry()
+ self.type = '.pt'
+ self.name = 'fixtures/minimal.pt'
+ self.package = sys.modules[__name__]
+ self.settings = {}
+
diff --git a/pyramid/tests/test_config/__init__.py b/pyramid/tests/test_config/__init__.py
index 5b40a8c09..81d9f4965 100644
--- a/pyramid/tests/test_config/__init__.py
+++ b/pyramid/tests/test_config/__init__.py
@@ -43,3 +43,11 @@ def dummy_extend(config, discrim):
def dummy_extend2(config, discrim):
config.action(discrim, None, config.registry)
+from functools import partial
+dummy_partial = partial(dummy_extend, discrim='partial')
+
+class DummyCallable(object):
+ def __call__(self, config, discrim):
+ config.action(discrim, None, config.package)
+dummy_callable = DummyCallable()
+
diff --git a/pyramid/tests/test_config/test_adapters.py b/pyramid/tests/test_config/test_adapters.py
index 83ea0f05b..d47e012dc 100644
--- a/pyramid/tests/test_config/test_adapters.py
+++ b/pyramid/tests/test_config/test_adapters.py
@@ -81,6 +81,121 @@ class AdaptersConfiguratorMixinTests(unittest.TestCase):
config.registry.subscribers((event.object, IDummy), None)
self.assertEqual(len(L), 1)
+ def test_add_subscriber_with_specific_type_and_predicates_True(self):
+ from zope.interface import implementer
+ from zope.interface import Interface
+ class IEvent(Interface):
+ pass
+ @implementer(IEvent)
+ class Event:
+ pass
+ L = []
+ def subscriber(event):
+ L.append(event)
+ config = self._makeOne(autocommit=True)
+ predlist = config.get_predlist('subscriber')
+ jam_predicate = predicate_maker('jam')
+ jim_predicate = predicate_maker('jim')
+ predlist.add('jam', jam_predicate)
+ predlist.add('jim', jim_predicate)
+ config.add_subscriber(subscriber, IEvent, jam=True, jim=True)
+ event = Event()
+ event.jam = True
+ event.jim = True
+ config.registry.notify(event)
+ self.assertEqual(len(L), 1)
+ self.assertEqual(L[0], event)
+ config.registry.notify(object())
+ self.assertEqual(len(L), 1)
+
+ def test_add_subscriber_with_default_type_predicates_True(self):
+ from zope.interface import implementer
+ from zope.interface import Interface
+ class IEvent(Interface):
+ pass
+ @implementer(IEvent)
+ class Event:
+ pass
+ L = []
+ def subscriber(event):
+ L.append(event)
+ config = self._makeOne(autocommit=True)
+ predlist = config.get_predlist('subscriber')
+ jam_predicate = predicate_maker('jam')
+ jim_predicate = predicate_maker('jim')
+ predlist.add('jam', jam_predicate)
+ predlist.add('jim', jim_predicate)
+ config.add_subscriber(subscriber, jam=True, jim=True)
+ event = Event()
+ event.jam = True
+ event.jim = True
+ config.registry.notify(event)
+ self.assertEqual(len(L), 1)
+ self.assertEqual(L[0], event)
+ config.registry.notify(object())
+ self.assertEqual(len(L), 1)
+
+ def test_add_subscriber_with_specific_type_and_predicates_False(self):
+ from zope.interface import implementer
+ from zope.interface import Interface
+ class IEvent(Interface):
+ pass
+ @implementer(IEvent)
+ class Event:
+ pass
+ L = []
+ def subscriber(event): L.append(event)
+ config = self._makeOne(autocommit=True)
+ predlist = config.get_predlist('subscriber')
+ jam_predicate = predicate_maker('jam')
+ jim_predicate = predicate_maker('jim')
+ predlist.add('jam', jam_predicate)
+ predlist.add('jim', jim_predicate)
+ config.add_subscriber(subscriber, IEvent, jam=True, jim=True)
+ event = Event()
+ event.jam = True
+ event.jim = False
+ config.registry.notify(event)
+ self.assertEqual(len(L), 0)
+
+ def test_add_subscriber_with_default_type_predicates_False(self):
+ from zope.interface import implementer
+ from zope.interface import Interface
+ class IEvent(Interface):
+ pass
+ @implementer(IEvent)
+ class Event:
+ pass
+ L = []
+ def subscriber(event): L.append(event)
+ config = self._makeOne(autocommit=True)
+ predlist = config.get_predlist('subscriber')
+ jam_predicate = predicate_maker('jam')
+ jim_predicate = predicate_maker('jim')
+ predlist.add('jam', jam_predicate)
+ predlist.add('jim', jim_predicate)
+ config.add_subscriber(subscriber, jam=True, jim=True)
+ event = Event()
+ event.jam = False
+ event.jim = True
+ config.registry.notify(event)
+ self.assertEqual(len(L), 0)
+
+ def test_add_subscriber_predicate(self):
+ config = self._makeOne()
+ L = []
+ def add_predicate(type, name, factory, weighs_less_than=None,
+ weighs_more_than=None):
+ self.assertEqual(type, 'subscriber')
+ self.assertEqual(name, 'name')
+ self.assertEqual(factory, 'factory')
+ self.assertEqual(weighs_more_than, 1)
+ self.assertEqual(weighs_less_than, 2)
+ L.append(1)
+ config._add_predicate = add_predicate
+ config.add_subscriber_predicate('name', 'factory', 1, 2)
+ self.assertTrue(L)
+
def test_add_response_adapter(self):
from pyramid.interfaces import IResponse
config = self._makeOne(autocommit=True)
@@ -228,4 +343,14 @@ class DummyResourceURL(object):
self.resource = resource
self.request = request
-
+def predicate_maker(name):
+ class Predicate(object):
+ def __init__(self, val, config):
+ self.val = val
+ def phash(self):
+ return 'phash'
+ text = phash
+ def __call__(self, event):
+ return getattr(event, name, None) == self.val
+ return Predicate
+
diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py
index 1dfeda34c..e89fc077e 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
@@ -111,32 +112,48 @@ class TestFactoriesMixin(unittest.TestCase):
config.set_request_property(bar, name='bar')
self.assertRaises(ConfigurationConflictError, config.commit)
- def test_set_request_property_subscriber(self):
- from zope.interface import implementer
- from pyramid.interfaces import INewRequest
+ def test_add_request_method_with_callable(self):
+ from pyramid.interfaces import IRequestExtensions
+ config = self._makeOne(autocommit=True)
+ callable = lambda x: None
+ config.add_request_method(callable, name='foo')
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.methods)
+
+ def test_add_request_method_with_unnamed_callable(self):
+ from pyramid.interfaces import IRequestExtensions
+ config = self._makeOne(autocommit=True)
+ def foo(self): pass
+ config.add_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(r): pass
- config.set_request_property(foo, name='foo')
- config.set_request_property(foo, name='bar', reify=True)
- config.commit()
- @implementer(INewRequest)
- class Event(object):
- 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']))
-
-
-
-class DummyRequest(object):
- plist = None
-
- def __init__(self, registry):
- self.registry = registry
-
- def _set_properties(self, properties):
- if self.plist is None:
- self.plist = []
- self.plist.extend(properties)
+ def foo(self): pass
+ def bar(self): pass
+ config.add_request_method(foo, name='bar')
+ config.add_request_method(bar, name='bar')
+ self.assertRaises(ConfigurationConflictError, config.commit)
+
+ def test_add_request_method_with_None_callable(self):
+ from pyramid.interfaces import IRequestExtensions
+ config = self._makeOne(autocommit=True)
+ config.add_request_method(name='foo')
+ exts = config.registry.queryUtility(IRequestExtensions)
+ self.assertTrue(exts is None)
+
+ def test_add_request_method_with_None_callable_conflict(self):
+ from pyramid.exceptions import ConfigurationConflictError
+ config = self._makeOne()
+ def bar(self): pass
+ config.add_request_method(name='foo')
+ config.add_request_method(bar, name='foo')
+ self.assertRaises(ConfigurationConflictError, config.commit)
+
+ def test_add_request_method_with_None_callable_and_no_name(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(AttributeError, config.add_request_method)
+
diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py
index 37c3de275..f39906dd9 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
@@ -1414,6 +1421,32 @@ class TestConfigurator_add_directive(unittest.TestCase):
self.assertEqual(action['callable'], None)
self.assertEqual(action['args'], test_config)
+ def test_add_directive_with_partial(self):
+ from pyramid.tests import test_config
+ config = self.config
+ config.add_directive(
+ 'dummy_partial', 'pyramid.tests.test_config.dummy_partial')
+ self.assertTrue(hasattr(config, 'dummy_partial'))
+ config.dummy_partial()
+ after = config.action_state
+ action = after.actions[-1]
+ self.assertEqual(action['discriminator'], 'partial')
+ self.assertEqual(action['callable'], None)
+ self.assertEqual(action['args'], test_config)
+
+ def test_add_directive_with_custom_callable(self):
+ from pyramid.tests import test_config
+ config = self.config
+ config.add_directive(
+ 'dummy_callable', 'pyramid.tests.test_config.dummy_callable')
+ self.assertTrue(hasattr(config, 'dummy_callable'))
+ config.dummy_callable('discrim')
+ after = config.action_state
+ action = after.actions[-1]
+ self.assertEqual(action['discriminator'], 'discrim')
+ self.assertEqual(action['callable'], None)
+ self.assertEqual(action['args'], test_config)
+
def test_extend_with_python_callable(self):
from pyramid.tests import test_config
config = self.config
@@ -1682,6 +1715,7 @@ class Test_resolveConflicts(unittest.TestCase):
(3, f, (3,), {}, ('y',)),
(None, f, (5,), {}, ('y',)),
])
+ result = list(result)
self.assertEqual(
result,
[{'info': None,
@@ -1743,6 +1777,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 +1829,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 +1974,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 +1988,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_init.py:TestConfigurator_add_directive b/pyramid/tests/test_config/test_init.py:TestConfigurator_add_directive
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/pyramid/tests/test_config/test_init.py:TestConfigurator_add_directive
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_settings.py b/pyramid/tests/test_config/test_settings.py
index 0f5239915..c74f96375 100644
--- a/pyramid/tests/test_config/test_settings.py
+++ b/pyramid/tests/test_config/test_settings.py
@@ -47,6 +47,15 @@ class TestSettingsConfiguratorMixin(unittest.TestCase):
settings = reg.getUtility(ISettings)
self.assertEqual(settings['a'], 1)
+ def test_add_settings_settings_None(self):
+ from pyramid.registry import Registry
+ from pyramid.interfaces import ISettings
+ reg = Registry()
+ config = self._makeOne(reg)
+ config.add_settings(None, a=1)
+ settings = reg.getUtility(ISettings)
+ self.assertEqual(settings['a'], 1)
+
class TestSettings(unittest.TestCase):
def _getTargetClass(self):
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..575d8c738 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)
@@ -645,6 +646,24 @@ class TestViewsConfigurationMixin(unittest.TestCase):
request.accept = DummyAccept('text/html', 'text/html')
self.assertEqual(wrapper(None, request), 'OK2')
+ def test_add_view_mixed_case_replaces_existing_view(self):
+ from pyramid.renderers import null_renderer
+ def view(context, request): return 'OK'
+ def view2(context, request): return 'OK2'
+ def view3(context, request): return 'OK3'
+ config = self._makeOne(autocommit=True)
+ config.add_view(view=view, renderer=null_renderer)
+ config.add_view(view=view2, accept='text/html', renderer=null_renderer)
+ config.add_view(view=view3, accept='text/HTML', renderer=null_renderer)
+ wrapper = self._getViewCallable(config)
+ self.assertTrue(IMultiView.providedBy(wrapper))
+ self.assertEqual(len(wrapper.media_views.items()),1)
+ self.assertFalse('text/HTML' in wrapper.media_views)
+ self.assertEqual(wrapper(None, None), 'OK')
+ request = DummyRequest()
+ request.accept = DummyAccept('text/html', 'text/html')
+ self.assertEqual(wrapper(None, request), 'OK3')
+
def test_add_views_with_accept_multiview_replaces_existing(self):
from pyramid.renderers import null_renderer
def view(context, request): return 'OK'
@@ -970,8 +989,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 +1238,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 +1344,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 +1719,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 +1766,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
@@ -2055,11 +2140,28 @@ class TestMultiView(unittest.TestCase):
def test_add_with_phash_override_accept(self):
mv = self._makeOne()
- mv.add('view2', 100, accept='text/html', phash='abc')
- mv.add('view3', 100, accept='text/html', phash='abc')
- mv.add('view4', 99, accept='text/html', phash='def')
+ def view1(): pass
+ def view2(): pass
+ def view3(): pass
+ mv.add(view1, 100, accept='text/html', phash='abc')
+ mv.add(view2, 100, accept='text/html', phash='abc')
+ mv.add(view3, 99, accept='text/html', phash='def')
self.assertEqual(mv.media_views['text/html'],
- [(99, 'view4', 'def'), (100, 'view3', 'abc')])
+ [(99, view3, 'def'), (100, view2, 'abc')])
+
+ def test_add_with_phash_override_accept2(self):
+ mv = self._makeOne()
+ def view1(): pass
+ def view2(): pass
+ def view3(): pass
+ mv.add(view1, 100, accept='text/html', phash='abc')
+ mv.add(view2, 100, accept='text/html', phash='def')
+ mv.add(view3, 99, accept='text/html', phash='ghi')
+ self.assertEqual(mv.media_views['text/html'],
+ [(99, view3, 'ghi'),
+ (100, view1, 'abc'),
+ (100, view2, 'def')]
+ )
def test_multiple_with_functions_as_views(self):
# this failed on py3 at one point, because functions aren't orderable
@@ -2905,6 +3007,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 +3015,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 +3025,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 +3033,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 +3101,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 +4072,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_configuration.py b/pyramid/tests/test_configuration.py
deleted file mode 100644
index 0a98bcb5c..000000000
--- a/pyramid/tests/test_configuration.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import unittest
-
-class ConfiguratorTests(unittest.TestCase):
- def setUp(self):
- from zope.deprecation import __show__
- __show__.off()
-
- def tearDown(self):
- from zope.deprecation import __show__
- __show__.on()
-
- def _makeOne(self, *arg, **kw):
- from pyramid.configuration import Configurator
- return Configurator(*arg, **kw)
-
- def test_autocommit_true(self):
- config = self._makeOne()
- self.assertEqual(config.autocommit, True)
-
- def test_package_is_not_None(self):
- import pyramid
- config = self._makeOne(package='pyramid')
- self.assertEqual(config.package, pyramid)
-
- def test_with_package(self):
- import pyramid
- config = self._makeOne()
- newconfig = config.with_package('pyramid')
- self.assertEqual(newconfig.package, pyramid)
-
-
diff --git a/pyramid/tests/test_events.py b/pyramid/tests/test_events.py
index f35083c02..2c72c07e8 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):
@@ -127,9 +131,9 @@ class TestSubscriber(unittest.TestCase):
def tearDown(self):
testing.tearDown()
- def _makeOne(self, *ifaces):
+ def _makeOne(self, *ifaces, **predicates):
from pyramid.events import subscriber
- return subscriber(*ifaces)
+ return subscriber(*ifaces, **predicates)
def test_register_single(self):
from zope.interface import Interface
@@ -186,6 +190,16 @@ class TestSubscriber(unittest.TestCase):
self.assertEqual(dummy_venusian.attached,
[(foo, dec.register, 'pyramid')])
+ def test_regsister_with_predicates(self):
+ from zope.interface import Interface
+ dec = self._makeOne(a=1)
+ def foo(): pass
+ config = DummyConfigurator()
+ scanner = Dummy()
+ scanner.config = config
+ dec.register(scanner, None, foo)
+ self.assertEqual(config.subscribed, [(foo, Interface, {'a':1})])
+
class TestBeforeRender(unittest.TestCase):
def _makeOne(self, system, val=None):
from pyramid.events import BeforeRender
@@ -260,8 +274,11 @@ class DummyConfigurator(object):
def __init__(self):
self.subscribed = []
- def add_subscriber(self, wrapped, ifaces):
- self.subscribed.append((wrapped, ifaces))
+ def add_subscriber(self, wrapped, ifaces, **predicates):
+ if not predicates:
+ self.subscribed.append((wrapped, ifaces))
+ else:
+ self.subscribed.append((wrapped, ifaces, predicates))
class DummyRegistry(object):
pass
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..a07ebeffa 100644
--- a/pyramid/tests/test_path.py
+++ b/pyramid/tests/test_path.py
@@ -138,9 +138,9 @@ class TestPackageOf(unittest.TestCase):
self.assertEqual(result, tests)
def test_it_module(self):
- import pyramid.tests.test_configuration
+ import pyramid.tests.test_path
from pyramid import tests
- package = DummyPackageOrModule(pyramid.tests.test_configuration)
+ package = DummyPackageOrModule(pyramid.tests.test_path)
result = self._callFUT(package)
self.assertEqual(result, tests)
@@ -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()
@@ -399,7 +395,7 @@ class TestDottedNameResolver(unittest.TestCase):
self.assertEqual(result, self.__class__)
def test__zope_dottedname_style_resolve_relative_leading_dots(self):
- import pyramid.tests.test_configuration
+ import pyramid.tests.test_path
typ = self._makeOne()
result = typ._zope_dottedname_style(
'..tests.test_path.TestDottedNameResolver', pyramid.tests)
diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py
index 495d7dc23..af9188abc 100644
--- a/pyramid/tests/test_renderers.py
+++ b/pyramid/tests/test_renderers.py
@@ -292,7 +292,32 @@ class TestChameleonRendererLookup(unittest.TestCase):
if path.endswith('.pyc'): # pragma: no cover
path = path[:-1]
self.assertTrue(factory.path.startswith(path))
- self.assertEqual(factory.kw, {})
+ self.assertEqual(factory.kw, {'macro':None})
+
+ def test___call__spec_withmacro(self):
+ import os
+ from pyramid import tests
+ module_name = tests.__name__
+ relpath = 'fixtures/withmacro#foo.pt'
+ renderer = {}
+ factory = DummyFactory(renderer)
+ spec = '%s:%s' % (module_name, relpath)
+ info = DummyRendererInfo({
+ 'name':spec,
+ 'package':None,
+ 'registry':self.config.registry,
+ 'settings':{},
+ 'type':'type',
+ })
+ lookup = self._makeOne(factory)
+ result = lookup(info)
+ self.assertTrue(result is renderer)
+ path = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)),
+ 'fixtures',
+ 'withmacro.pt')
+ self.assertTrue(factory.path.startswith(path))
+ self.assertEqual(factory.kw, {'macro':'foo'})
def test___call__reload_assets_true(self):
import pyramid.tests
@@ -508,6 +533,7 @@ class TestRendererHelper(unittest.TestCase):
def renderer(*arg):
def respond(*arg):
return arg
+ renderer.respond = respond
return respond
self.config.registry.registerUtility(renderer, IRendererFactory,
name='.foo')
@@ -529,6 +555,11 @@ class TestRendererHelper(unittest.TestCase):
self.assertEqual(response.body[0], 'values')
self.assertEqual(response.body[1], {})
+ def test_get_renderer(self):
+ factory = self._registerRendererFactory()
+ helper = self._makeOne('loo.foo')
+ self.assertEqual(helper.get_renderer(), factory.respond)
+
def test_render_view(self):
self._registerRendererFactory()
self._registerResponseFactory()
@@ -627,6 +658,14 @@ class TestRendererHelper(unittest.TestCase):
response = helper._make_response(la.encode('utf-8'), request)
self.assertEqual(response.body, la.encode('utf-8'))
+ def test__make_response_result_is_None(self):
+ from pyramid.response import Response
+ request = testing.DummyRequest()
+ request.response = Response()
+ helper = self._makeOne('loo.foo')
+ response = helper._make_response(None, request)
+ self.assertEqual(response.body, b'')
+
def test__make_response_with_content_type(self):
from pyramid.response import Response
request = testing.DummyRequest()
diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py
index 6d5131013..86cfd8b09 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()
@@ -546,18 +549,6 @@ class Test_call_app_with_subpath_as_path_info(unittest.TestCase):
self.assertEqual(request.environ['SCRIPT_NAME'], '/' + encoded)
self.assertEqual(request.environ['PATH_INFO'], '/' + encoded)
- def test_it_removes_bfg_routes_info(self):
- request = DummyRequest({})
- request.environ['bfg.routes.route'] = True
- request.environ['bfg.routes.matchdict'] = True
- response = self._callFUT(request, 'app')
- self.assertTrue(request.copied)
- self.assertEqual(response, 'app')
- self.assertEqual(request.environ['SCRIPT_NAME'], '')
- self.assertEqual(request.environ['PATH_INFO'], '/')
- self.assertFalse('bfg.routes.route' in request.environ)
- self.assertFalse('bfg.routes.matchdict' in request.environ)
-
class DummyRequest:
def __init__(self, environ=None):
if environ is None:
diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py
index eb9b7285d..778b27473 100644
--- a/pyramid/tests/test_router.py
+++ b/pyramid/tests/test_router.py
@@ -312,6 +312,38 @@ class TestRouter(unittest.TestCase):
self.assertEqual(app_iter, [b'abc'])
self.assertEqual(start_response.status, '200 OK')
+ def test_call_with_request_extensions(self):
+ from pyramid.interfaces import IViewClassifier
+ from pyramid.interfaces import IRequestExtensions
+ from pyramid.interfaces import IRequest
+ from pyramid.request import Request
+ context = DummyContext()
+ self._registerTraverserFactory(context)
+ class Extensions(object):
+ def __init__(self):
+ self.methods = {}
+ self.descriptors = {}
+ extensions = Extensions()
+ L = []
+ request = Request.blank('/')
+ request.request_iface = IRequest
+ request.registry = self.registry
+ request._set_extensions = lambda *x: L.extend(x)
+ def request_factory(environ):
+ return request
+ self.registry.registerUtility(extensions, IRequestExtensions)
+ environ = self._makeEnviron()
+ response = DummyResponse()
+ response.app_iter = ['Hello world']
+ view = DummyView(response)
+ self._registerView(self.config.derive_view(view), '',
+ IViewClassifier, None, None)
+ router = self._makeOne()
+ router.request_factory = request_factory
+ start_response = DummyStartResponse()
+ router(environ, start_response)
+ self.assertEqual(L, [extensions])
+
def test_call_view_registered_nonspecific_default_path(self):
from pyramid.interfaces import IViewClassifier
context = DummyContext()
@@ -646,8 +678,6 @@ class TestRouter(unittest.TestCase):
self.assertEqual(request.context, context)
self.assertEqual(request.root, root)
matchdict = {'action':'action1', 'article':'article1'}
- self.assertEqual(environ['bfg.routes.matchdict'], matchdict)
- self.assertEqual(environ['bfg.routes.route'].name, 'foo')
self.assertEqual(request.matchdict, matchdict)
self.assertEqual(request.matched_route.name, 'foo')
self.assertEqual(len(logger.messages), 1)
@@ -712,8 +742,6 @@ class TestRouter(unittest.TestCase):
self.assertEqual(request.context, context)
self.assertEqual(request.root, root)
matchdict = {'action':'action1', 'article':'article1'}
- self.assertEqual(environ['bfg.routes.matchdict'], matchdict)
- self.assertEqual(environ['bfg.routes.route'].name, 'foo')
self.assertEqual(request.matchdict, matchdict)
self.assertEqual(request.matched_route.name, 'foo')
self.assertTrue(IFoo.providedBy(request))
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_scripting.py b/pyramid/tests/test_scripting.py
index a99ae53fe..1ccc7af3b 100644
--- a/pyramid/tests/test_scripting.py
+++ b/pyramid/tests/test_scripting.py
@@ -5,31 +5,37 @@ class Test_get_root(unittest.TestCase):
from pyramid.scripting import get_root
return get_root(app, request)
+ def _makeRegistry(self):
+ return DummyRegistry([DummyFactory])
+
def test_it_norequest(self):
- app = DummyApp(registry=dummy_registry)
+ registry = self._makeRegistry()
+ app = DummyApp(registry=registry)
root, closer = self._callFUT(app)
self.assertEqual(len(app.threadlocal_manager.pushed), 1)
pushed = app.threadlocal_manager.pushed[0]
- self.assertEqual(pushed['registry'], dummy_registry)
+ self.assertEqual(pushed['registry'], registry)
self.assertEqual(pushed['request'].registry, app.registry)
self.assertEqual(len(app.threadlocal_manager.popped), 0)
closer()
self.assertEqual(len(app.threadlocal_manager.popped), 1)
def test_it_withrequest(self):
- app = DummyApp(registry=dummy_registry)
+ registry = self._makeRegistry()
+ app = DummyApp(registry=registry)
request = DummyRequest({})
root, closer = self._callFUT(app, request)
self.assertEqual(len(app.threadlocal_manager.pushed), 1)
pushed = app.threadlocal_manager.pushed[0]
- self.assertEqual(pushed['registry'], dummy_registry)
+ self.assertEqual(pushed['registry'], registry)
self.assertEqual(pushed['request'], request)
self.assertEqual(len(app.threadlocal_manager.popped), 0)
closer()
self.assertEqual(len(app.threadlocal_manager.popped), 1)
def test_it_requestfactory_overridden(self):
- app = DummyApp(registry=dummy_registry)
+ registry = self._makeRegistry()
+ app = DummyApp(registry=registry)
root, closer = self._callFUT(app)
self.assertEqual(len(app.threadlocal_manager.pushed), 1)
pushed = app.threadlocal_manager.pushed[0]
@@ -40,8 +46,10 @@ class Test_prepare(unittest.TestCase):
from pyramid.scripting import prepare
return prepare(request, registry)
- def _makeRegistry(self):
- return DummyRegistry(DummyFactory)
+ def _makeRegistry(self, L=None):
+ if L is None:
+ L = [None, DummyFactory]
+ return DummyRegistry(L)
def setUp(self):
from pyramid.threadlocal import manager
@@ -53,21 +61,22 @@ class Test_prepare(unittest.TestCase):
self.assertRaises(ConfigurationError, self._callFUT)
def test_it_norequest(self):
- registry = self._makeRegistry()
+ registry = self._makeRegistry([DummyFactory, None, DummyFactory])
info = self._callFUT(registry=registry)
- root, closer = info['root'], info['closer']
+ root, closer, request = info['root'], info['closer'], info['request']
pushed = self.manager.get()
self.assertEqual(pushed['registry'], registry)
self.assertEqual(pushed['request'].registry, registry)
self.assertEqual(root.a, (pushed['request'],))
closer()
self.assertEqual(self.default, self.manager.get())
+ self.assertEqual(request.context, root)
def test_it_withrequest(self):
request = DummyRequest({})
registry = request.registry = self._makeRegistry()
info = self._callFUT(request=request)
- root, closer = info['root'], info['closer']
+ root, closer, request = info['root'], info['closer'], info['request']
pushed = self.manager.get()
self.assertEqual(pushed['request'], request)
self.assertEqual(pushed['registry'], registry)
@@ -75,12 +84,13 @@ class Test_prepare(unittest.TestCase):
self.assertEqual(root.a, (request,))
closer()
self.assertEqual(self.default, self.manager.get())
+ self.assertEqual(request.context, root)
def test_it_with_request_and_registry(self):
request = DummyRequest({})
registry = request.registry = self._makeRegistry()
info = self._callFUT(request=request, registry=registry)
- root, closer = info['root'], info['closer']
+ root, closer, root = info['root'], info['closer'], info['root']
pushed = self.manager.get()
self.assertEqual(pushed['request'], request)
self.assertEqual(pushed['registry'], registry)
@@ -88,21 +98,44 @@ class Test_prepare(unittest.TestCase):
self.assertEqual(root.a, (request,))
closer()
self.assertEqual(self.default, self.manager.get())
+ self.assertEqual(request.context, root)
+
+ def test_it_with_request_context_already_set(self):
+ request = DummyRequest({})
+ context = Dummy()
+ request.context = context
+ registry = request.registry = self._makeRegistry()
+ info = self._callFUT(request=request, registry=registry)
+ root, closer, root = info['root'], info['closer'], info['root']
+ closer()
+ self.assertEqual(request.context, context)
+
+ def test_it_with_extensions(self):
+ exts = Dummy()
+ request = DummyRequest({})
+ registry = request.registry = self._makeRegistry([exts, DummyFactory])
+ info = self._callFUT(request=request, registry=registry)
+ self.assertEqual(request.extensions, exts)
+ root, closer = info['root'], info['closer']
+ closer()
class Test__make_request(unittest.TestCase):
def _callFUT(self, path='/', registry=None):
from pyramid.scripting import _make_request
return _make_request(path, registry)
+ def _makeRegistry(self):
+ return DummyRegistry([DummyFactory])
+
def test_it_with_registry(self):
- request = self._callFUT('/', dummy_registry)
+ registry = self._makeRegistry()
+ request = self._callFUT('/', registry)
self.assertEqual(request.environ['path'], '/')
- self.assertEqual(request.registry, dummy_registry)
+ self.assertEqual(request.registry, registry)
def test_it_with_no_registry(self):
from pyramid.config import global_registries
- # keep registry local so that global_registries is cleared after
- registry = DummyRegistry(DummyFactory)
+ registry = self._makeRegistry()
global_registries.add(registry)
try:
request = self._callFUT('/hello')
@@ -127,13 +160,13 @@ class DummyFactory(object):
self.kw = kw
class DummyRegistry(object):
- def __init__(self, factory=None):
- self.factory = factory
+ def __init__(self, utilities):
+ self.utilities = utilities
- def queryUtility(self, iface, default=None):
- return self.factory or default
-
-dummy_registry = DummyRegistry(DummyFactory)
+ def queryUtility(self, iface, default=None): # pragma: no cover
+ if self.utilities:
+ return self.utilities.pop(0)
+ return default
class DummyApp:
def __init__(self, registry=None):
@@ -156,6 +189,10 @@ class DummyThreadLocalManager:
self.popped.append(True)
class DummyRequest:
+ matchdict = None
+ matched_route = None
def __init__(self, environ):
self.environ = environ
-
+
+ def _set_extensions(self, exts):
+ self.extensions = exts
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_session.py b/pyramid/tests/test_session.py
index 6d75c7950..5143b7a95 100644
--- a/pyramid/tests/test_session.py
+++ b/pyramid/tests/test_session.py
@@ -205,6 +205,48 @@ class TestUnencryptedCookieSession(unittest.TestCase):
self.assertTrue(token)
self.assertTrue('_csrft_' in session)
+ def test_serialize_option(self):
+ from pyramid.response import Response
+ secret = 'secret'
+ request = testing.DummyRequest()
+ session = self._makeOne(request,
+ signed_serialize=dummy_signed_serialize)
+ session['key'] = 'value'
+ response = Response()
+ self.assertEqual(session._set_cookie(response), True)
+ cookie = response.headerlist[-1][1]
+ expected_cookieval = dummy_signed_serialize(
+ (session.accessed, session.created, {'key': 'value'}), secret)
+ response = Response()
+ response.set_cookie('session', expected_cookieval)
+ expected_cookie = response.headerlist[-1][1]
+ self.assertEqual(cookie, expected_cookie)
+
+ def test_deserialize_option(self):
+ import time
+ secret = 'secret'
+ request = testing.DummyRequest()
+ accessed = time.time()
+ state = {'key': 'value'}
+ cookieval = dummy_signed_serialize((accessed, accessed, state), secret)
+ request.cookies['session'] = cookieval
+ session = self._makeOne(request,
+ signed_deserialize=dummy_signed_deserialize)
+ self.assertEqual(dict(session), state)
+
+def dummy_signed_serialize(data, secret):
+ import base64
+ from pyramid.compat import pickle, bytes_
+ pickled = pickle.dumps(data)
+ return base64.b64encode(bytes_(secret)) + base64.b64encode(pickled)
+
+def dummy_signed_deserialize(serialized, secret):
+ import base64
+ from pyramid.compat import pickle, bytes_
+ serialized_data = base64.b64decode(
+ serialized[len(base64.b64encode(bytes_(secret))):])
+ return pickle.loads(serialized_data)
+
class Test_manage_accessed(unittest.TestCase):
def _makeOne(self, wrapped):
from pyramid.session import manage_accessed
diff --git a/pyramid/tests/test_settings.py b/pyramid/tests/test_settings.py
index 2ef15f62a..a586cb6fd 100644
--- a/pyramid/tests/test_settings.py
+++ b/pyramid/tests/test_settings.py
@@ -1,30 +1,4 @@
import unittest
-from pyramid import testing
-
-class TestGetSettings(unittest.TestCase):
- def setUp(self):
- from pyramid.registry import Registry
- registry = Registry('testing')
- self.config = testing.setUp(registry=registry)
- from zope.deprecation import __show__
- __show__.off()
-
- def tearDown(self):
- self.config.end()
- from zope.deprecation import __show__
- __show__.on()
-
- def _callFUT(self):
- from pyramid.settings import get_settings
- return get_settings()
-
- def test_it_nosettings(self):
- self.assertEqual(self._callFUT()['reload_templates'], False)
-
- def test_it_withsettings(self):
- settings = {'a':1}
- self.config.registry.settings = settings
- self.assertEqual(self._callFUT(), settings)
class Test_asbool(unittest.TestCase):
def _callFUT(self, s):
diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py
index 779c66818..7f14462a2 100644
--- a/pyramid/tests/test_testing.py
+++ b/pyramid/tests/test_testing.py
@@ -1,282 +1,4 @@
import unittest
-from pyramid.compat import text_
-
-class TestBase(unittest.TestCase):
- def setUp(self):
- from pyramid.threadlocal import manager
- from pyramid.registry import Registry
- manager.clear()
- registry = Registry('testing')
- self.registry = registry
- manager.push({'registry':registry, 'request':None})
- from zope.deprecation import __show__
- __show__.off()
-
- def tearDown(self):
- from pyramid.threadlocal import manager
- manager.clear()
- from zope.deprecation import __show__
- __show__.on()
-
-class Test_registerDummySecurityPolicy(TestBase):
- def test_registerDummySecurityPolicy(self):
- from pyramid import testing
- testing.registerDummySecurityPolicy('user', ('group1', 'group2'),
- permissive=False)
- from pyramid.interfaces import IAuthenticationPolicy
- from pyramid.interfaces import IAuthorizationPolicy
- ut = self.registry.getUtility(IAuthenticationPolicy)
- from pyramid.testing import DummySecurityPolicy
- self.assertTrue(isinstance(ut, DummySecurityPolicy))
- ut = self.registry.getUtility(IAuthorizationPolicy)
- self.assertEqual(ut.userid, 'user')
- self.assertEqual(ut.groupids, ('group1', 'group2'))
- self.assertEqual(ut.permissive, False)
-
-class Test_registerResources(TestBase):
- def test_it(self):
- class Dummy:
- pass
- ob1 = Dummy()
- ob2 = Dummy()
- resources = {'/ob1':ob1, '/ob2':ob2}
- from pyramid import testing
- testing.registerResources(resources)
- from pyramid.interfaces import ITraverser
- adapter = self.registry.getAdapter(None, ITraverser)
- result = adapter(DummyRequest({'PATH_INFO':'/ob1'}))
- self.assertEqual(result['context'], ob1)
- self.assertEqual(result['view_name'], '')
- self.assertEqual(result['subpath'], ())
- self.assertEqual(result['traversed'], (text_('ob1'),))
- self.assertEqual(result['virtual_root'], ob1)
- self.assertEqual(result['virtual_root_path'], ())
- result = adapter(DummyRequest({'PATH_INFO':'/ob2'}))
- self.assertEqual(result['context'], ob2)
- self.assertEqual(result['view_name'], '')
- self.assertEqual(result['subpath'], ())
- self.assertEqual(result['traversed'], (text_('ob2'),))
- self.assertEqual(result['virtual_root'], ob2)
- self.assertEqual(result['virtual_root_path'], ())
- self.assertRaises(KeyError, adapter, DummyRequest({'PATH_INFO':'/ob3'}))
- from pyramid.traversal import find_resource
- self.assertEqual(find_resource(None, '/ob1'), ob1)
-
-class Test_registerTemplateRenderer(TestBase):
- def test_registerTemplateRenderer(self):
- from pyramid import testing
- renderer = testing.registerTemplateRenderer('templates/foo')
- from pyramid.testing import DummyTemplateRenderer
- self.assertTrue(isinstance(renderer, DummyTemplateRenderer))
- from pyramid.renderers import render_to_response
- render_to_response('templates/foo', dict(foo=1, bar=2))
- renderer.assert_(foo=1)
- renderer.assert_(bar=2)
-
- def test_registerTemplateRenderer_explicitrenderer(self):
- from pyramid import testing
- def renderer(kw, system):
- self.assertEqual(kw, {'foo':1, 'bar':2})
- renderer = testing.registerTemplateRenderer('templates/foo', renderer)
- from pyramid.renderers import render_to_response
- render_to_response('templates/foo', dict(foo=1, bar=2))
-
-class Test_registerEventListener(TestBase):
- def test_registerEventListener_single(self):
- from pyramid import testing
- L = testing.registerEventListener(IDummy)
- event = DummyEvent()
- self.registry.notify(event)
- self.assertEqual(len(L), 1)
- self.assertEqual(L[0], event)
- self.registry.notify(object())
- self.assertEqual(len(L), 1)
-
- def test_registerEventListener_multiple(self):
- from pyramid import testing
- L = testing.registerEventListener((Interface, IDummy))
- event = DummyEvent()
- event.object = 'foo'
- # the below is the equivalent of z.c.event.objectEventNotify(event)
- self.registry.subscribers((event.object, event), None)
- self.assertEqual(len(L), 2)
- self.assertEqual(L[0], 'foo')
- self.assertEqual(L[1], event)
-
- def test_registerEventListener_defaults(self):
- from pyramid import testing
- L = testing.registerEventListener()
- event = object()
- self.registry.notify(event)
- self.assertEqual(L[-1], event)
- event2 = object()
- self.registry.notify(event2)
- self.assertEqual(L[-1], event2)
-
-class Test_registerView(TestBase):
- def test_registerView_defaults(self):
- from pyramid import testing
- view = testing.registerView('moo.html')
- import types
- self.assertTrue(isinstance(view, types.FunctionType))
- from pyramid.view import render_view_to_response
- request = DummyRequest()
- request.registry = self.registry
- response = render_view_to_response(None, request, 'moo.html')
- self.assertEqual(view(None, None).body, response.body)
-
- def test_registerView_withresult(self):
- from pyramid import testing
- view = testing.registerView('moo.html', 'yo')
- import types
- self.assertTrue(isinstance(view, types.FunctionType))
- from pyramid.view import render_view_to_response
- request = DummyRequest()
- request.registry = self.registry
- response = render_view_to_response(None, request, 'moo.html')
- self.assertEqual(response.body, b'yo')
-
- def test_registerView_custom(self):
- from pyramid import testing
- def view(context, request):
- from webob import Response
- return Response('123')
- view = testing.registerView('moo.html', view=view)
- import types
- self.assertTrue(isinstance(view, types.FunctionType))
- from pyramid.view import render_view_to_response
- request = DummyRequest()
- request.registry = self.registry
- response = render_view_to_response(None, request, 'moo.html')
- self.assertEqual(response.body, b'123')
-
- def test_registerView_with_permission_denying(self):
- from pyramid import testing
- from pyramid.httpexceptions import HTTPForbidden
- def view(context, request):
- """ """
- view = testing.registerView('moo.html', view=view, permission='bar')
- testing.registerDummySecurityPolicy(permissive=False)
- import types
- self.assertTrue(isinstance(view, types.FunctionType))
- from pyramid.view import render_view_to_response
- request = DummyRequest()
- request.registry = self.registry
- self.assertRaises(HTTPForbidden, render_view_to_response,
- None, request, 'moo.html')
-
- def test_registerView_with_permission_denying2(self):
- from pyramid import testing
- from pyramid.security import view_execution_permitted
- def view(context, request):
- """ """
- view = testing.registerView('moo.html', view=view, permission='bar')
- testing.registerDummySecurityPolicy(permissive=False)
- import types
- self.assertTrue(isinstance(view, types.FunctionType))
- result = view_execution_permitted(None, None, 'moo.html')
- self.assertEqual(result, False)
-
- def test_registerView_with_permission_allowing(self):
- from pyramid import testing
- def view(context, request):
- from webob import Response
- return Response('123')
- view = testing.registerView('moo.html', view=view, permission='bar')
- testing.registerDummySecurityPolicy(permissive=True)
- import types
- self.assertTrue(isinstance(view, types.FunctionType))
- from pyramid.view import render_view_to_response
- request = DummyRequest()
- request.registry = self.registry
- result = render_view_to_response(None, request, 'moo.html')
- self.assertEqual(result.app_iter, [b'123'])
-
-
-class Test_registerAdapter(TestBase):
- def test_registerAdapter(self):
- from zope.interface import Interface
- class provides(Interface):
- pass
- class Provider:
- pass
- class for_(Interface):
- pass
- from pyramid import testing
- testing.registerAdapter(Provider, (for_, for_), provides, name='foo')
- adapter = self.registry.adapters.lookup(
- (for_, for_), provides, name='foo')
- self.assertEqual(adapter, Provider)
-
- def test_registerAdapter_notlist(self):
- from zope.interface import Interface
- class provides(Interface):
- pass
- class Provider:
- pass
- class for_(Interface):
- pass
- from pyramid import testing
- testing.registerAdapter(Provider, for_, provides, name='foo')
- adapter = self.registry.adapters.lookup(
- (for_,), provides, name='foo')
- self.assertEqual(adapter, Provider)
-
-class Test_registerUtility(TestBase):
- def test_registerUtility(self):
- from zope.interface import implementer
- from zope.interface import Interface
- class iface(Interface):
- pass
- @implementer(iface)
- class impl:
- def __call__(self):
- return 'foo'
- utility = impl()
- from pyramid import testing
- testing.registerUtility(utility, iface, name='mudge')
- self.assertEqual(self.registry.getUtility(iface, name='mudge')(), 'foo')
-
-class Test_registerSubscriber(TestBase):
- def test_it(self):
- from pyramid import testing
- L = []
- def subscriber(event):
- L.append(event)
- testing.registerSubscriber(subscriber, iface=IDummy)
- event = DummyEvent()
- self.registry.notify(event)
- self.assertEqual(len(L), 1)
- self.assertEqual(L[0], event)
- self.registry.notify(object())
- self.assertEqual(len(L), 1)
-
-class Test_registerRoute(TestBase):
- def test_registerRoute(self):
- from pyramid.request import Request
- from pyramid.interfaces import IRoutesMapper
- from pyramid.testing import registerRoute
- registerRoute(':pagename', 'home', DummyFactory)
- mapper = self.registry.getUtility(IRoutesMapper)
- self.assertEqual(len(mapper.routelist), 1)
- request = Request.blank('/')
- request.registry = self.registry
- self.assertEqual(request.route_url('home', pagename='abc'),
- 'http://localhost/abc')
-
-class Test_registerSettings(TestBase):
- def test_registerSettings(self):
- from pyramid.interfaces import ISettings
- from pyramid.testing import registerSettings
- registerSettings({'a':1, 'b':2})
- settings = self.registry.getUtility(ISettings)
- self.assertEqual(settings['a'], 1)
- self.assertEqual(settings['b'], 2)
- registerSettings(b=3, c=4)
- settings = self.registry.getUtility(ISettings)
- self.assertEqual(settings['a'], 1)
- self.assertEqual(settings['b'], 3)
- self.assertEqual(settings['c'], 4)
class TestDummyRootFactory(unittest.TestCase):
def _makeOne(self, environ):
@@ -910,14 +632,6 @@ class IDummy(Interface):
class DummyEvent:
pass
-
-class DummyRequest:
- application_url = 'http://example.com'
- def __init__(self, environ=None):
- if environ is None:
- environ = {}
- self.environ = environ
-
class DummyFactory:
def __init__(self, environ):
""" """
diff --git a/pyramid/tests/test_traversal.py b/pyramid/tests/test_traversal.py
index 8e0bb2494..2e45ae1a9 100644
--- a/pyramid/tests/test_traversal.py
+++ b/pyramid/tests/test_traversal.py
@@ -128,10 +128,23 @@ class ResourceTreeTraverserTests(unittest.TestCase):
context = DummyContext()
verifyObject(ITraverser, self._makeOne(context))
- def test_call_with_no_pathinfo(self):
+ def test_call_with_empty_pathinfo(self):
policy = self._makeOne(None)
environ = self._getEnviron()
- request = DummyRequest(environ)
+ request = DummyRequest(environ, path_info='')
+ result = policy(request)
+ self.assertEqual(result['context'], None)
+ self.assertEqual(result['view_name'], '')
+ self.assertEqual(result['subpath'], ())
+ self.assertEqual(result['traversed'], ())
+ self.assertEqual(result['root'], policy.root)
+ self.assertEqual(result['virtual_root'], policy.root)
+ self.assertEqual(result['virtual_root_path'], ())
+
+ def test_call_with_pathinfo_KeyError(self):
+ policy = self._makeOne(None)
+ environ = self._getEnviron()
+ request = DummyRequest(environ, toraise=KeyError)
result = policy(request)
self.assertEqual(result['context'], None)
self.assertEqual(result['view_name'], '')
@@ -142,31 +155,25 @@ class ResourceTreeTraverserTests(unittest.TestCase):
self.assertEqual(result['virtual_root_path'], ())
def test_call_with_pathinfo_highorder(self):
- foo = DummyContext(None, text_(b'Qu\xc3\xa9bec', 'utf-8'))
+ path = text_(b'/Qu\xc3\xa9bec', 'utf-8')
+ foo = DummyContext(None, path)
root = DummyContext(foo, 'root')
policy = self._makeOne(root)
- if PY3: # pragma: no cover
- path_info = b'/Qu\xc3\xa9bec'.decode('latin-1')
- else:
- path_info = b'/Qu\xc3\xa9bec'
- environ = self._getEnviron(PATH_INFO=path_info)
- request = DummyRequest(environ)
+ environ = self._getEnviron()
+ request = DummyRequest(environ, path_info=path)
result = policy(request)
self.assertEqual(result['context'], foo)
self.assertEqual(result['view_name'], '')
self.assertEqual(result['subpath'], ())
- self.assertEqual(
- result['traversed'],
- (text_(b'Qu\xc3\xa9bec', 'utf-8'),)
- )
+ self.assertEqual(result['traversed'], (path[1:],))
self.assertEqual(result['root'], policy.root)
self.assertEqual(result['virtual_root'], policy.root)
self.assertEqual(result['virtual_root_path'], ())
def test_call_pathel_with_no_getitem(self):
policy = self._makeOne(None)
- environ = self._getEnviron(PATH_INFO='/foo/bar')
- request = DummyRequest(environ)
+ environ = self._getEnviron()
+ request = DummyRequest(environ, path_info=text_('/foo/bar'))
result = policy(request)
self.assertEqual(result['context'], None)
self.assertEqual(result['view_name'], 'foo')
@@ -179,8 +186,8 @@ class ResourceTreeTraverserTests(unittest.TestCase):
def test_call_withconn_getitem_emptypath_nosubpath(self):
root = DummyContext()
policy = self._makeOne(root)
- environ = self._getEnviron(PATH_INFO='')
- request = DummyRequest(environ)
+ environ = self._getEnviron()
+ request = DummyRequest(environ, path_info=text_(''))
result = policy(request)
self.assertEqual(result['context'], root)
self.assertEqual(result['view_name'], '')
@@ -194,8 +201,8 @@ class ResourceTreeTraverserTests(unittest.TestCase):
foo = DummyContext()
root = DummyContext(foo)
policy = self._makeOne(root)
- environ = self._getEnviron(PATH_INFO='/foo/bar')
- request = DummyRequest(environ)
+ environ = self._getEnviron()
+ request = DummyRequest(environ, path_info=text_('/foo/bar'))
result = policy(request)
self.assertEqual(result['context'], foo)
self.assertEqual(result['view_name'], 'bar')
@@ -209,8 +216,8 @@ class ResourceTreeTraverserTests(unittest.TestCase):
foo = DummyContext()
root = DummyContext(foo)
policy = self._makeOne(root)
- environ = self._getEnviron(PATH_INFO='/foo/bar/baz/buz')
- request = DummyRequest(environ)
+ environ = self._getEnviron()
+ request = DummyRequest(environ, path_info=text_('/foo/bar/baz/buz'))
result = policy(request)
self.assertEqual(result['context'], foo)
self.assertEqual(result['view_name'], 'bar')
@@ -224,8 +231,8 @@ class ResourceTreeTraverserTests(unittest.TestCase):
foo = DummyContext()
root = DummyContext(foo)
policy = self._makeOne(root)
- environ = self._getEnviron(PATH_INFO='/@@foo')
- request = DummyRequest(environ)
+ environ = self._getEnviron()
+ request = DummyRequest(environ, path_info=text_('/@@foo'))
result = policy(request)
self.assertEqual(result['context'], root)
self.assertEqual(result['view_name'], 'foo')
@@ -236,14 +243,13 @@ class ResourceTreeTraverserTests(unittest.TestCase):
self.assertEqual(result['virtual_root_path'], ())
def test_call_with_vh_root(self):
- environ = self._getEnviron(PATH_INFO='/baz',
- HTTP_X_VHM_ROOT='/foo/bar')
+ environ = self._getEnviron(HTTP_X_VHM_ROOT='/foo/bar')
baz = DummyContext(None, 'baz')
bar = DummyContext(baz, 'bar')
foo = DummyContext(bar, 'foo')
root = DummyContext(foo, 'root')
policy = self._makeOne(root)
- request = DummyRequest(environ)
+ request = DummyRequest(environ, path_info=text_('/baz'))
result = policy(request)
self.assertEqual(result['context'], baz)
self.assertEqual(result['view_name'], '')
@@ -256,14 +262,13 @@ class ResourceTreeTraverserTests(unittest.TestCase):
(text_('foo'), text_('bar')))
def test_call_with_vh_root2(self):
- environ = self._getEnviron(PATH_INFO='/bar/baz',
- HTTP_X_VHM_ROOT='/foo')
+ environ = self._getEnviron(HTTP_X_VHM_ROOT='/foo')
baz = DummyContext(None, 'baz')
bar = DummyContext(baz, 'bar')
foo = DummyContext(bar, 'foo')
root = DummyContext(foo, 'root')
policy = self._makeOne(root)
- request = DummyRequest(environ)
+ request = DummyRequest(environ, path_info=text_('/bar/baz'))
result = policy(request)
self.assertEqual(result['context'], baz)
self.assertEqual(result['view_name'], '')
@@ -275,14 +280,13 @@ class ResourceTreeTraverserTests(unittest.TestCase):
self.assertEqual(result['virtual_root_path'], (text_('foo'),))
def test_call_with_vh_root3(self):
- environ = self._getEnviron(PATH_INFO='/foo/bar/baz',
- HTTP_X_VHM_ROOT='/')
+ environ = self._getEnviron(HTTP_X_VHM_ROOT='/')
baz = DummyContext()
bar = DummyContext(baz)
foo = DummyContext(bar)
root = DummyContext(foo)
policy = self._makeOne(root)
- request = DummyRequest(environ)
+ request = DummyRequest(environ, path_info=text_('/foo/bar/baz'))
result = policy(request)
self.assertEqual(result['context'], baz)
self.assertEqual(result['view_name'], '')
@@ -294,14 +298,13 @@ class ResourceTreeTraverserTests(unittest.TestCase):
self.assertEqual(result['virtual_root_path'], ())
def test_call_with_vh_root4(self):
- environ = self._getEnviron(PATH_INFO='/',
- HTTP_X_VHM_ROOT='/foo/bar/baz')
+ environ = self._getEnviron(HTTP_X_VHM_ROOT='/foo/bar/baz')
baz = DummyContext(None, 'baz')
bar = DummyContext(baz, 'bar')
foo = DummyContext(bar, 'foo')
root = DummyContext(foo, 'root')
policy = self._makeOne(root)
- request = DummyRequest(environ)
+ request = DummyRequest(environ, path_info=text_('/'))
result = policy(request)
self.assertEqual(result['context'], baz)
self.assertEqual(result['view_name'], '')
@@ -315,9 +318,8 @@ class ResourceTreeTraverserTests(unittest.TestCase):
def test_call_with_vh_root_path_root(self):
policy = self._makeOne(None)
- environ = self._getEnviron(HTTP_X_VHM_ROOT='/',
- PATH_INFO='/')
- request = DummyRequest(environ)
+ environ = self._getEnviron(HTTP_X_VHM_ROOT='/')
+ request = DummyRequest(environ, path_info=text_('/'))
result = policy(request)
self.assertEqual(result['context'], None)
self.assertEqual(result['view_name'], '')
@@ -328,57 +330,48 @@ class ResourceTreeTraverserTests(unittest.TestCase):
self.assertEqual(result['virtual_root_path'], ())
def test_call_with_vh_root_highorder(self):
+ path = text_(b'Qu\xc3\xa9bec', 'utf-8')
bar = DummyContext(None, 'bar')
- foo = DummyContext(bar, text_(b'Qu\xc3\xa9bec', 'utf-8'))
+ foo = DummyContext(bar, path)
root = DummyContext(foo, 'root')
policy = self._makeOne(root)
if PY3: # pragma: no cover
vhm_root = b'/Qu\xc3\xa9bec'.decode('latin-1')
else:
vhm_root = b'/Qu\xc3\xa9bec'
- environ = self._getEnviron(HTTP_X_VHM_ROOT=vhm_root,
- PATH_INFO='/bar')
- request = DummyRequest(environ)
+ environ = self._getEnviron(HTTP_X_VHM_ROOT=vhm_root)
+ request = DummyRequest(environ, path_info=text_('/bar'))
result = policy(request)
self.assertEqual(result['context'], bar)
self.assertEqual(result['view_name'], '')
self.assertEqual(result['subpath'], ())
self.assertEqual(
result['traversed'],
- (text_(b'Qu\xc3\xa9bec', 'utf-8'), text_('bar'))
+ (path, text_('bar'))
)
self.assertEqual(result['root'], policy.root)
self.assertEqual(result['virtual_root'], foo)
self.assertEqual(
result['virtual_root_path'],
- (text_(b'Qu\xc3\xa9bec', 'utf-8'),)
+ (path,)
)
- def test_non_utf8_path_segment_unicode_path_segments_fails(self):
- from pyramid.exceptions import URLDecodeError
- foo = DummyContext()
- root = DummyContext(foo)
- policy = self._makeOne(root)
- segment = native_(text_(b'LaPe\xc3\xb1a', 'utf-8'), 'utf-16')
- environ = self._getEnviron(PATH_INFO='/%s' % segment)
- request = DummyRequest(environ)
- self.assertRaises(URLDecodeError, policy, request)
-
- def test_non_utf8_path_segment_settings_unicode_path_segments_fails(self):
+ def test_path_info_raises_unicodedecodeerror(self):
from pyramid.exceptions import URLDecodeError
foo = DummyContext()
root = DummyContext(foo)
policy = self._makeOne(root)
- segment = native_(text_(b'LaPe\xc3\xb1a', 'utf-8'), 'utf-16')
- environ = self._getEnviron(PATH_INFO='/%s' % segment)
- request = DummyRequest(environ)
+ environ = self._getEnviron()
+ toraise = UnicodeDecodeError('ascii', b'a', 2, 3, '5')
+ request = DummyRequest(environ, toraise=toraise)
+ request.matchdict = None
self.assertRaises(URLDecodeError, policy, request)
def test_withroute_nothingfancy(self):
resource = DummyContext()
traverser = self._makeOne(resource)
- environ = {'bfg.routes.matchdict': {}}
- request = DummyRequest(environ)
+ request = DummyRequest({})
+ request.matchdict = {}
result = traverser(request)
self.assertEqual(result['context'], resource)
self.assertEqual(result['view_name'], '')
@@ -391,8 +384,9 @@ class ResourceTreeTraverserTests(unittest.TestCase):
def test_withroute_with_subpath_string(self):
resource = DummyContext()
traverser = self._makeOne(resource)
- environ = {'bfg.routes.matchdict': {'subpath':'/a/b/c'}}
- request = DummyRequest(environ)
+ matchdict = {'subpath':'/a/b/c'}
+ request = DummyRequest({})
+ request.matchdict = matchdict
result = traverser(request)
self.assertEqual(result['context'], resource)
self.assertEqual(result['view_name'], '')
@@ -405,8 +399,9 @@ class ResourceTreeTraverserTests(unittest.TestCase):
def test_withroute_with_subpath_tuple(self):
resource = DummyContext()
traverser = self._makeOne(resource)
- environ = {'bfg.routes.matchdict': {'subpath':('a', 'b', 'c')}}
- request = DummyRequest(environ)
+ matchdict = {'subpath':('a', 'b', 'c')}
+ request = DummyRequest({})
+ request.matchdict = matchdict
result = traverser(request)
self.assertEqual(result['context'], resource)
self.assertEqual(result['view_name'], '')
@@ -419,8 +414,9 @@ class ResourceTreeTraverserTests(unittest.TestCase):
def test_withroute_and_traverse_string(self):
resource = DummyContext()
traverser = self._makeOne(resource)
- environ = {'bfg.routes.matchdict': {'traverse':'foo/bar'}}
- request = DummyRequest(environ)
+ matchdict = {'traverse':text_('foo/bar')}
+ request = DummyRequest({})
+ request.matchdict = matchdict
result = traverser(request)
self.assertEqual(result['context'], resource)
self.assertEqual(result['view_name'], 'foo')
@@ -433,8 +429,9 @@ class ResourceTreeTraverserTests(unittest.TestCase):
def test_withroute_and_traverse_tuple(self):
resource = DummyContext()
traverser = self._makeOne(resource)
- environ = {'bfg.routes.matchdict': {'traverse':('foo', 'bar')}}
- request = DummyRequest(environ)
+ matchdict = {'traverse':('foo', 'bar')}
+ request = DummyRequest({})
+ request.matchdict = matchdict
result = traverser(request)
self.assertEqual(result['context'], resource)
self.assertEqual(result['view_name'], 'foo')
@@ -447,8 +444,9 @@ class ResourceTreeTraverserTests(unittest.TestCase):
def test_withroute_and_traverse_empty(self):
resource = DummyContext()
traverser = self._makeOne(resource)
- environ = {'bfg.routes.matchdict': {'traverse':''}}
- request = DummyRequest(environ)
+ matchdict = {'traverse':''}
+ request = DummyRequest({})
+ request.matchdict = matchdict
result = traverser(request)
self.assertEqual(result['context'], resource)
self.assertEqual(result['view_name'], '')
@@ -458,21 +456,6 @@ class ResourceTreeTraverserTests(unittest.TestCase):
self.assertEqual(result['virtual_root'], resource)
self.assertEqual(result['virtual_root_path'], ())
- def test_call_with_environ(self):
- with warnings.catch_warnings(record=True) as w:
- warnings.filterwarnings('always')
- policy = self._makeOne(None)
- environ = self._getEnviron()
- result = policy(environ)
- self.assertEqual(result['context'], None)
- self.assertEqual(result['view_name'], '')
- self.assertEqual(result['subpath'], ())
- self.assertEqual(result['traversed'], ())
- self.assertEqual(result['root'], policy.root)
- self.assertEqual(result['virtual_root'], policy.root)
- self.assertEqual(result['virtual_root_path'], ())
- self.assertEqual(len(w), 1)
-
class FindInterfaceTests(unittest.TestCase):
def _callFUT(self, context, iface):
from pyramid.traversal import find_interface
@@ -1310,11 +1293,28 @@ class DummyContext(object):
return '<DummyContext with name %s at id %s>'%(self.__name__, id(self))
class DummyRequest:
+
application_url = 'http://example.com:5432' # app_url never ends with slash
- def __init__(self, environ=None):
+ matchdict = None
+ matched_route = None
+
+ def __init__(self, environ=None, path_info=text_('/'), toraise=None):
if environ is None:
environ = {}
self.environ = environ
+ self._set_path_info(path_info)
+ self.toraise = toraise
+
+ def _get_path_info(self):
+ if self.toraise:
+ raise self.toraise
+ return self._path_info
+
+ def _set_path_info(self, v):
+ self._path_info = v
+
+ path_info = property(_get_path_info, _set_path_info)
+
class DummyContextURL:
def __init__(self, context, request):
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_util.py b/pyramid/tests/test_util.py
index e83ad5922..3d85e18f5 100644
--- a/pyramid/tests/test_util.py
+++ b/pyramid/tests/test_util.py
@@ -132,6 +132,20 @@ class Test_InstancePropertyMixin(unittest.TestCase):
self.assertEqual(1, foo.x)
self.assertEqual(2, foo.y)
+ def test__set_extensions(self):
+ inst = self._makeOne()
+ def foo(self, result):
+ return result
+ n, bar = inst._make_property(lambda _: 'bar', name='bar')
+ class Extensions(object):
+ def __init__(self):
+ self.methods = {'foo':foo}
+ self.descriptors = {'bar':bar}
+ extensions = Extensions()
+ inst._set_extensions(extensions)
+ self.assertEqual(inst.bar, 'bar')
+ self.assertEqual(inst.foo('abc'), 'abc')
+
class Test_WeakOrderedSet(unittest.TestCase):
def _makeOne(self):
from pyramid.config import WeakOrderedSet
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/traversal.py b/pyramid/traversal.py
index b514d4c16..6832ce69a 100644
--- a/pyramid/traversal.py
+++ b/pyramid/traversal.py
@@ -614,6 +614,7 @@ else:
_segment_cache[(segment, safe)] = result
return result
+slash = text_('/')
@implementer(ITraverser)
class ResourceTreeTraverser(object):
@@ -629,27 +630,17 @@ class ResourceTreeTraverser(object):
self.root = root
def __call__(self, request):
- try:
- environ = request.environ
- except AttributeError:
- # In BFG 1.0 and before, this API expected an environ
- # rather than a request; some bit of code may still be
- # passing us an environ. If so, deal.
- environ = request
- depwarn = ('Passing an environ dictionary directly to a traverser '
- 'is deprecated in Pyramid 1.1. Pass a request object '
- 'instead.')
- warnings.warn(depwarn, DeprecationWarning, 2)
-
- if 'bfg.routes.matchdict' in environ:
- matchdict = environ['bfg.routes.matchdict']
-
- path = matchdict.get('traverse', '/') or '/'
+ environ = request.environ
+ matchdict = request.matchdict
+
+ if matchdict is not None:
+
+ path = matchdict.get('traverse', slash) or slash
if is_nonstr_iter(path):
# this is a *traverse stararg (not a {traverse})
# routing has already decoded these elements, so we just
# need to join them
- path = '/'.join(path) or '/'
+ path = slash.join(path) or slash
subpath = matchdict.get('subpath', ())
if not is_nonstr_iter(subpath):
@@ -663,9 +654,10 @@ class ResourceTreeTraverser(object):
subpath = ()
try:
# empty if mounted under a path in mod_wsgi, for example
- path = decode_path_info(environ['PATH_INFO'] or '/')
+ path = request.path_info or slash
except KeyError:
- path = '/'
+ # if environ['PATH_INFO'] is just not there
+ path = slash
except UnicodeDecodeError as e:
raise URLDecodeError(e.encoding, e.object, e.start, e.end,
e.reason)
@@ -684,7 +676,7 @@ class ResourceTreeTraverser(object):
root = self.root
ob = vroot = root
- if vpath == '/': # invariant: vpath must not be empty
+ if vpath == slash: # invariant: vpath must not be empty
# prevent a call to traversal_path if we know it's going
# to return the empty tuple
vpath_tuple = ()
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..6190e8156 100644
--- a/pyramid/util.py
+++ b/pyramid/util.py
@@ -2,6 +2,7 @@ import inspect
import weakref
from pyramid.compat import (
+ iteritems_,
integer_types,
string_types,
text_,
@@ -74,6 +75,12 @@ class InstancePropertyMixin(object):
cls = type(parent.__name__, (parent, object), attrs)
self.__class__ = cls
+ def _set_extensions(self, extensions):
+ for name, fn in iteritems_(extensions.methods):
+ method = fn.__get__(self, self.__class__)
+ setattr(self, name, method)
+ self._set_properties(extensions.descriptors)
+
def set_property(self, callable, name=None, reify=False):
""" Add a callable or a property descriptor to the instance.
@@ -280,3 +287,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()
diff --git a/setup.py b/setup.py
index cbc1075bd..03ebb4293 100644
--- a/setup.py
+++ b/setup.py
@@ -39,7 +39,7 @@ install_requires=[
'setuptools',
'Chameleon >= 1.2.3',
'Mako >= 0.3.6', # strict_undefined
- 'WebOb >= 1.2dev', # response.text / py3 compat
+ 'WebOb >= 1.2b3', # request.path_info is unicode
'repoze.lru >= 0.4', # py3 compat
'zope.interface >= 3.8.0', # has zope.interface.registry
'zope.deprecation >= 3.5.0', # py3 compat
diff --git a/tox.ini b/tox.ini
index 85bd41bda..b50e56544 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,45 +4,16 @@ envlist =
[testenv]
commands =
+ python setup.py dev
python setup.py test -q
-deps =
- zope.component
- Sphinx
- repoze.sphinx.autointerface
- WebTest
- virtualenv
- venusian
-
-[testenv:py32]
-commands =
- python setup.py test -q
-deps =
- WebTest
- virtualenv
- venusian
-
-[testenv:py33]
-commands =
- python setup.py test -q
-deps =
- WebTest
- virtualenv
- venusian
[testenv:cover]
basepython =
python2.6
commands =
+ python setup.py dev
python setup.py nosetests --with-xunit --with-xcoverage
deps =
- zope.component
- Sphinx
- WebTest
- repoze.sphinx.autointerface
- virtualenv
- venusian
- nose
- coverage
nosexcover
# we separate coverage into its own testenv because a) "last run wins" wrt