summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--CHANGES.txt14
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--docs/api/config.rst1
-rw-r--r--docs/api/interfaces.rst3
-rw-r--r--docs/glossary.rst5
-rw-r--r--docs/narr/project.rst14
-rw-r--r--docs/quick_tour.rst24
-rw-r--r--docs/quick_tutorial/cookiecutters.rst13
-rw-r--r--docs/tutorials/modwsgi/index.rst15
-rw-r--r--docs/tutorials/wiki/installation.rst9
-rw-r--r--docs/tutorials/wiki/src/authorization/README.txt2
-rw-r--r--docs/tutorials/wiki/src/basiclayout/README.txt2
-rw-r--r--docs/tutorials/wiki/src/installation/README.txt2
-rw-r--r--docs/tutorials/wiki/src/models/README.txt2
-rw-r--r--docs/tutorials/wiki/src/tests/README.txt2
-rw-r--r--docs/tutorials/wiki/src/views/README.txt2
-rw-r--r--docs/tutorials/wiki2/installation.rst9
-rw-r--r--docs/tutorials/wiki2/src/authentication/README.txt2
-rw-r--r--docs/tutorials/wiki2/src/authorization/README.txt2
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/README.txt2
-rw-r--r--docs/tutorials/wiki2/src/installation/README.txt2
-rw-r--r--docs/tutorials/wiki2/src/models/README.txt2
-rw-r--r--docs/tutorials/wiki2/src/tests/README.txt2
-rw-r--r--docs/tutorials/wiki2/src/views/README.txt2
-rw-r--r--pyramid/config/factories.py25
-rw-r--r--pyramid/interfaces.py43
-rw-r--r--pyramid/registry.py4
-rw-r--r--pyramid/router.py46
-rw-r--r--pyramid/tests/pkgs/subrequestapp/__init__.py4
-rw-r--r--pyramid/tests/test_config/test_factories.py19
-rw-r--r--pyramid/tests/test_integration.py30
-rw-r--r--pyramid/tests/test_router.py13
-rw-r--r--pyramid/util.py9
34 files changed, 274 insertions, 58 deletions
diff --git a/.travis.yml b/.travis.yml
index b0e63ba97..ffc6caa72 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -31,6 +31,10 @@ install:
script:
- travis_retry tox
+cache:
+ directories:
+ - $HOME/.cache/pip
+
notifications:
email:
- pyramid-checkins@lists.repoze.org
diff --git a/CHANGES.txt b/CHANGES.txt
index 59a733bcd..9c9acf3d0 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -4,12 +4,24 @@ unreleased
Features
--------
+- Added an execution policy hook to the request pipeline. An execution
+ policy has the ability to control creation and execution of the request
+ objects before they enter rest of the pipeline. This means for a given
+ request that the policy may create more than one request for retry
+ purposes. See https://github.com/Pylons/pyramid/pull/2964
+
Bug Fixes
---------
- HTTPException's accepts a detail kwarg that may be used to pass additional
details to the exception. You may now pass objects so long as they have a
- valid __str__ method. See https://github.com/Pylons/pyramid/pull/2951
+ valid __str__ method. See https://github.com/Pylons/pyramid/pull/2951
+
+- Fix a reference cycle causing memory leaks in which the registry
+ would keep a ``Configurator`` instance alive even after the configurator
+ was discarded. Another fix was also added for the ``global_registries``
+ object in which the registry was stored in a closure preventing it from
+ being deallocated. See https://github.com/Pylons/pyramid/pull/2967
Deprecations
------------
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index d5c178418..566e91195 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -292,3 +292,5 @@ Contributors
- Mikko Ohtamaa, 2016/12/6
- Martin Frlin, 2016/12/7
+
+- Kirill Kuzminykh, 2017/03/01
diff --git a/docs/api/config.rst b/docs/api/config.rst
index 62f138b76..c76d3d5ff 100644
--- a/docs/api/config.rst
+++ b/docs/api/config.rst
@@ -70,6 +70,7 @@
.. automethod:: add_subscriber_predicate
.. automethod:: add_view_predicate
.. automethod:: add_view_deriver
+ .. automethod:: set_execution_policy
.. automethod:: set_request_factory
.. automethod:: set_root_factory
.. automethod:: set_session_factory
diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst
index 521d65d2b..a212ba7a9 100644
--- a/docs/api/interfaces.rst
+++ b/docs/api/interfaces.rst
@@ -65,6 +65,9 @@ Other Interfaces
.. autointerface:: IResponseFactory
:members:
+ .. autointerface:: IRouter
+ :members:
+
.. autointerface:: IViewMapperFactory
:members:
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 0f299c169..0a46fac3b 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -1154,3 +1154,8 @@ Glossary
coverage
A measurement of code coverage, usually expressed as a percentage of which lines of code have been executed over which lines are executable, typically run during test execution.
+
+ execution policy
+ A policy which wraps the :term:`router` by creating the request object
+ and sending it through the request pipeline.
+ See :class:`pyramid.config.Configurator.set_execution_policy`.
diff --git a/docs/narr/project.rst b/docs/narr/project.rst
index f32fad370..525fdd501 100644
--- a/docs/narr/project.rst
+++ b/docs/narr/project.rst
@@ -89,10 +89,16 @@ On all platforms, generate a project using cookiecutter.
If prompted for the first item, accept the default ``yes`` by hitting return.
-#. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. Is it
- okay to delete and re-clone it? [yes]:``
-#. ``project_name [Pyramid Scaffold]: myproject``
-#. ``repo_name [scaffold]: myproject``
+.. code-block:: text
+
+ You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before.
+ Is it okay to delete and re-clone it? [yes]: yes
+ project_name [Pyramid Scaffold]: myproject
+ repo_name [scaffold]: myproject
+ Select template_language:
+ 1 - jinja2
+ 2 - chameleon
+ Choose from 1, 2 [1]: 1
We then run through the following commands.
diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst
index 053846276..fa9dfabad 100644
--- a/docs/quick_tour.rst
+++ b/docs/quick_tour.rst
@@ -514,10 +514,16 @@ Let's use the cookiecutter ``pyramid-cookiecutter-starter`` to create a starter
If prompted for the first item, accept the default ``yes`` by hitting return.
-#. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. Is it
- okay to delete and re-clone it? [yes]:``
-#. ``project_name [Pyramid Scaffold]: hello_world``
-#. ``repo_name [scaffold]: hello_world``
+.. code-block:: text
+
+ You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before.
+ Is it okay to delete and re-clone it? [yes]: yes
+ project_name [Pyramid Scaffold]: hello_world
+ repo_name [scaffold]: hello_world
+ Select template_language:
+ 1 - jinja2
+ 2 - chameleon
+ Choose from 1, 2 [1]: 1
We then run through the following commands.
@@ -863,10 +869,12 @@ Pyramid and SQLAlchemy are great friends. That friendship includes a cookiecutte
If prompted for the first item, accept the default ``yes`` by hitting return.
-#. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-alchemy before. Is it
- okay to delete and re-clone it? [yes]:``
-#. ``project_name [Pyramid Scaffold]: sqla_demo``
-#. ``repo_name [scaffold]: sqla_demo``
+.. code-block:: text
+
+ You've cloned ~/.cookiecutters/pyramid-cookiecutter-alchemy before.
+ Is it okay to delete and re-clone it? [yes]: yes
+ project_name [Pyramid Scaffold]: sqla_demo
+ repo_name [scaffold]: sqla_demo
We then run through the following commands as before.
diff --git a/docs/quick_tutorial/cookiecutters.rst b/docs/quick_tutorial/cookiecutters.rst
index 8e7048f78..f7251618f 100644
--- a/docs/quick_tutorial/cookiecutters.rst
+++ b/docs/quick_tutorial/cookiecutters.rst
@@ -32,9 +32,16 @@ Steps
If prompted for the first item, accept the default ``yes`` by hitting return.
- #. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. Is it okay to delete and re-clone it? [yes]:``
- #. ``project_name [Pyramid Scaffold]: cc_starter``
- #. ``repo_name [scaffold]: cc_starter``
+ .. code-block:: text
+
+ You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before.
+ Is it okay to delete and re-clone it? [yes]: yes
+ project_name [Pyramid Scaffold]: cc_starter
+ repo_name [scaffold]: cc_starter
+ Select template_language:
+ 1 - jinja2
+ 2 - chameleon
+ Choose from 1, 2 [1]: 1
#. We then run through the following commands.
diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst
index 0c3b58bac..44e892a27 100644
--- a/docs/tutorials/modwsgi/index.rst
+++ b/docs/tutorials/modwsgi/index.rst
@@ -40,8 +40,19 @@ specific path information for commands and files.
$ cd ~
$ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter
- project_name [Pyramid Scaffold]: myproject
- repo_name [scaffold]: myproject
+
+ If prompted for the first item, accept the default ``yes`` by hitting return.
+
+ .. code-block:: text
+
+ You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before.
+ Is it okay to delete and re-clone it? [yes]: yes
+ project_name [Pyramid Scaffold]: myproject
+ repo_name [scaffold]: myproject
+ Select template_language:
+ 1 - jinja2
+ 2 - chameleon
+ Choose from 1, 2 [1]: 1
#. Create a :term:`virtual environment` which we'll use to install our
application. It is important to use the same base Python interpreter
diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst
index c735bdf9d..6be826395 100644
--- a/docs/tutorials/wiki/installation.rst
+++ b/docs/tutorials/wiki/installation.rst
@@ -45,11 +45,12 @@ On all operating systems
^^^^^^^^^^^^^^^^^^^^^^^^
If prompted for the first item, accept the default ``yes`` by hitting return.
-#. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-zodb before. Is it
- okay to delete and re-clone it? [yes]:``
-#. ``project_name [Pyramid Scaffold]: myproj``
-#. ``repo_name [scaffold]: tutorial``
+.. code-block:: text
+ You've cloned ~/.cookiecutters/pyramid-cookiecutter-zodb before.
+ Is it okay to delete and re-clone it? [yes]: yes
+ project_name [Pyramid Scaffold]: myproj
+ repo_name [scaffold]: tutorial
Change directory into your newly created project
------------------------------------------------
diff --git a/docs/tutorials/wiki/src/authorization/README.txt b/docs/tutorials/wiki/src/authorization/README.txt
index bd67221cc..98683bf8c 100644
--- a/docs/tutorials/wiki/src/authorization/README.txt
+++ b/docs/tutorials/wiki/src/authorization/README.txt
@@ -6,7 +6,7 @@ Getting Started
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
diff --git a/docs/tutorials/wiki/src/basiclayout/README.txt b/docs/tutorials/wiki/src/basiclayout/README.txt
index bd67221cc..98683bf8c 100644
--- a/docs/tutorials/wiki/src/basiclayout/README.txt
+++ b/docs/tutorials/wiki/src/basiclayout/README.txt
@@ -6,7 +6,7 @@ Getting Started
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
diff --git a/docs/tutorials/wiki/src/installation/README.txt b/docs/tutorials/wiki/src/installation/README.txt
index bd67221cc..98683bf8c 100644
--- a/docs/tutorials/wiki/src/installation/README.txt
+++ b/docs/tutorials/wiki/src/installation/README.txt
@@ -6,7 +6,7 @@ Getting Started
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
diff --git a/docs/tutorials/wiki/src/models/README.txt b/docs/tutorials/wiki/src/models/README.txt
index bd67221cc..98683bf8c 100644
--- a/docs/tutorials/wiki/src/models/README.txt
+++ b/docs/tutorials/wiki/src/models/README.txt
@@ -6,7 +6,7 @@ Getting Started
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
diff --git a/docs/tutorials/wiki/src/tests/README.txt b/docs/tutorials/wiki/src/tests/README.txt
index bd67221cc..98683bf8c 100644
--- a/docs/tutorials/wiki/src/tests/README.txt
+++ b/docs/tutorials/wiki/src/tests/README.txt
@@ -6,7 +6,7 @@ Getting Started
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
diff --git a/docs/tutorials/wiki/src/views/README.txt b/docs/tutorials/wiki/src/views/README.txt
index bd67221cc..98683bf8c 100644
--- a/docs/tutorials/wiki/src/views/README.txt
+++ b/docs/tutorials/wiki/src/views/README.txt
@@ -6,7 +6,7 @@ Getting Started
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst
index fd323fcfc..9eeb1711d 100644
--- a/docs/tutorials/wiki2/installation.rst
+++ b/docs/tutorials/wiki2/installation.rst
@@ -57,11 +57,12 @@ On all operating systems
^^^^^^^^^^^^^^^^^^^^^^^^
If prompted for the first item, accept the default ``yes`` by hitting return.
-#. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-alchemy before. Is it
- okay to delete and re-clone it? [yes]:``
-#. ``project_name [Pyramid Scaffold]: myproj``
-#. ``repo_name [scaffold]: tutorial``
+.. code-block:: text
+ You've cloned ~/.cookiecutters/pyramid-cookiecutter-alchemy before.
+ Is it okay to delete and re-clone it? [yes]: yes
+ project_name [Pyramid Scaffold]: myproj
+ repo_name [scaffold]: tutorial
Change directory into your newly created project
------------------------------------------------
diff --git a/docs/tutorials/wiki2/src/authentication/README.txt b/docs/tutorials/wiki2/src/authentication/README.txt
index 8466fd7b5..5e21b8aa4 100644
--- a/docs/tutorials/wiki2/src/authentication/README.txt
+++ b/docs/tutorials/wiki2/src/authentication/README.txt
@@ -6,7 +6,7 @@ Getting Started
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
diff --git a/docs/tutorials/wiki2/src/authorization/README.txt b/docs/tutorials/wiki2/src/authorization/README.txt
index 8466fd7b5..5e21b8aa4 100644
--- a/docs/tutorials/wiki2/src/authorization/README.txt
+++ b/docs/tutorials/wiki2/src/authorization/README.txt
@@ -6,7 +6,7 @@ Getting Started
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
diff --git a/docs/tutorials/wiki2/src/basiclayout/README.txt b/docs/tutorials/wiki2/src/basiclayout/README.txt
index 8466fd7b5..5e21b8aa4 100644
--- a/docs/tutorials/wiki2/src/basiclayout/README.txt
+++ b/docs/tutorials/wiki2/src/basiclayout/README.txt
@@ -6,7 +6,7 @@ Getting Started
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
diff --git a/docs/tutorials/wiki2/src/installation/README.txt b/docs/tutorials/wiki2/src/installation/README.txt
index 8466fd7b5..5e21b8aa4 100644
--- a/docs/tutorials/wiki2/src/installation/README.txt
+++ b/docs/tutorials/wiki2/src/installation/README.txt
@@ -6,7 +6,7 @@ Getting Started
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
diff --git a/docs/tutorials/wiki2/src/models/README.txt b/docs/tutorials/wiki2/src/models/README.txt
index 8466fd7b5..5e21b8aa4 100644
--- a/docs/tutorials/wiki2/src/models/README.txt
+++ b/docs/tutorials/wiki2/src/models/README.txt
@@ -6,7 +6,7 @@ Getting Started
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
diff --git a/docs/tutorials/wiki2/src/tests/README.txt b/docs/tutorials/wiki2/src/tests/README.txt
index 8466fd7b5..5e21b8aa4 100644
--- a/docs/tutorials/wiki2/src/tests/README.txt
+++ b/docs/tutorials/wiki2/src/tests/README.txt
@@ -6,7 +6,7 @@ Getting Started
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
diff --git a/docs/tutorials/wiki2/src/views/README.txt b/docs/tutorials/wiki2/src/views/README.txt
index 8466fd7b5..5e21b8aa4 100644
--- a/docs/tutorials/wiki2/src/views/README.txt
+++ b/docs/tutorials/wiki2/src/views/README.txt
@@ -6,7 +6,7 @@ Getting Started
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py
index f0b6252ae..c8633cc47 100644
--- a/pyramid/config/factories.py
+++ b/pyramid/config/factories.py
@@ -3,6 +3,7 @@ from zope.interface import implementer
from pyramid.interfaces import (
IDefaultRootFactory,
+ IExecutionPolicy,
IRequestFactory,
IResponseFactory,
IRequestExtensions,
@@ -10,6 +11,7 @@ from pyramid.interfaces import (
ISessionFactory,
)
+from pyramid.router import default_execution_policy
from pyramid.traversal import DefaultRootFactory
from pyramid.util import (
@@ -231,6 +233,29 @@ class FactoriesConfiguratorMixin(object):
'set_request_propery() is deprecated as of Pyramid 1.5; use '
'add_request_method() with the property=True argument instead')
+ @action_method
+ def set_execution_policy(self, policy):
+ """
+ Override the :app:`Pyramid` :term:`execution policy` in the
+ current configuration. The ``policy`` argument must be an instance
+ of an :class:`pyramid.interfaces.IExecutionPolicy` or a
+ :term:`dotted Python name` that points at an instance of an
+ execution policy.
+
+ """
+ policy = self.maybe_dotted(policy)
+ if policy is None:
+ policy = default_execution_policy
+
+ def register():
+ self.registry.registerUtility(policy, IExecutionPolicy)
+
+ intr = self.introspectable('execution policy', None,
+ self.object_description(policy),
+ 'execution policy')
+ intr['policy'] = policy
+ self.action(IExecutionPolicy, register, introspectables=(intr,))
+
@implementer(IRequestExtensions)
class _RequestExtensions(object):
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index 450cd9c24..bbb4754e4 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -682,7 +682,48 @@ class IRouter(Interface):
registry = Attribute(
"""Component architecture registry local to this application.""")
-class ISettings(Interface):
+ def make_request(environ):
+ """
+ Create a new request object.
+
+ This method initializes a new :class:`pyramid.interfaces.IRequest`
+ object using the application's
+ :class:`pyramid.interfaces.IRequestFactory`.
+ """
+
+ def invoke_request(request):
+ """
+ Invoke the :app:`Pyramid` request pipeline.
+
+ See :ref:`router_chapter` for information on the request pipeline.
+ """
+
+class IExecutionPolicy(Interface):
+ def __call__(environ, router):
+ """
+ This callable triggers the router to process a raw WSGI environ dict
+ into a response and controls the :app:`Pyramid` request pipeline.
+
+ The ``environ`` is the raw WSGI environ.
+
+ The ``router`` is an :class:`pyramid.interfaces.IRouter` object which
+ should be used to create a request object and send it into the
+ processing pipeline.
+
+ The return value should be a :class:`pyramid.interfaces.IResponse`
+ object or an exception that will be handled by WSGI middleware.
+
+ The default execution policy simple creates a request and sends it
+ through the pipeline:
+
+ .. code-block:: python
+
+ def simple_execution_policy(environ, router):
+ request = router.make_request(environ)
+ return router.invoke_request(request)
+ """
+
+class ISettings(IDict):
""" Runtime settings utility for pyramid; represents the
deployment settings for the application. Implements a mapping
interface."""
diff --git a/pyramid/registry.py b/pyramid/registry.py
index 20b3643e9..7589dfcac 100644
--- a/pyramid/registry.py
+++ b/pyramid/registry.py
@@ -276,7 +276,9 @@ class Deferred(object):
@reify
def value(self):
- return self.func()
+ result = self.func()
+ del self.func
+ return result
def resolve(self):
return self.value
diff --git a/pyramid/router.py b/pyramid/router.py
index fd11925e9..8b7b7b6bc 100644
--- a/pyramid/router.py
+++ b/pyramid/router.py
@@ -5,6 +5,7 @@ from zope.interface import (
from pyramid.interfaces import (
IDebugLogger,
+ IExecutionPolicy,
IRequest,
IRequestExtensions,
IRootFactory,
@@ -49,6 +50,8 @@ class Router(object):
self.routes_mapper = q(IRoutesMapper)
self.request_factory = q(IRequestFactory, default=Request)
self.request_extensions = q(IRequestExtensions)
+ self.execution_policy = q(
+ IExecutionPolicy, default=default_execution_policy)
self.orig_handle_request = self.handle_request
tweens = q(ITweens)
if tweens is not None:
@@ -182,19 +185,36 @@ class Router(object):
:term:`tween` in the tween stack closest to the request ingress. If
``use_tweens`` is ``False``, the request will be sent to the main
router handler, and no tweens will be invoked.
-
+
See the API for pyramid.request for complete documentation.
"""
+ request.registry = self.registry
+ request.invoke_subrequest = self.invoke_subrequest
+ return self.invoke_request(
+ request,
+ _use_tweens=use_tweens,
+ _apply_extensions=True,
+ )
+
+ def make_request(self, environ):
+ request = self.request_factory(environ)
+ request.registry = self.registry
+ request.invoke_subrequest = self.invoke_subrequest
+ extensions = self.request_extensions
+ if extensions is not None:
+ apply_request_extensions(request, extensions=extensions)
+ return request
+
+ def invoke_request(self, request,
+ _use_tweens=True, _apply_extensions=False):
registry = self.registry
has_listeners = self.registry.has_listeners
notify = self.registry.notify
- threadlocals = {'registry':registry, 'request':request}
+ threadlocals = {'registry': registry, 'request': request}
manager = self.threadlocal_manager
manager.push(threadlocals)
- request.registry = registry
- request.invoke_subrequest = self.invoke_subrequest
-
- if use_tweens:
+
+ if _use_tweens:
handle_request = self.handle_request
else:
handle_request = self.orig_handle_request
@@ -203,7 +223,7 @@ class Router(object):
try:
extensions = self.request_extensions
- if extensions is not None:
+ if _apply_extensions and extensions is not None:
apply_request_extensions(request, extensions=extensions)
response = handle_request(request)
@@ -211,7 +231,7 @@ class Router(object):
request._process_response_callbacks(response)
has_listeners and notify(NewResponse(request, response))
-
+
return response
finally:
@@ -229,6 +249,10 @@ class Router(object):
within the application registry; call ``start_response`` and
return an iterable.
"""
- request = self.request_factory(environ)
- response = self.invoke_subrequest(request, use_tweens=True)
- return response(request.environ, start_response)
+ response = self.execution_policy(environ, self)
+ return response(environ, start_response)
+
+
+def default_execution_policy(environ, router):
+ request = router.make_request(environ)
+ return router.invoke_request(request)
diff --git a/pyramid/tests/pkgs/subrequestapp/__init__.py b/pyramid/tests/pkgs/subrequestapp/__init__.py
index b8f44cd7f..e4b1d386a 100644
--- a/pyramid/tests/pkgs/subrequestapp/__init__.py
+++ b/pyramid/tests/pkgs/subrequestapp/__init__.py
@@ -7,7 +7,8 @@ def view_one(request):
return response
def view_two(request):
- return 'This came from view_two'
+ # check that request.foo is valid for a subrequest
+ return 'This came from view_two, foo=%s' % (request.foo,)
def view_three(request):
subreq = Request.blank('/view_four')
@@ -46,5 +47,6 @@ def main():
config.add_view(view_three, route_name='three')
config.add_view(view_four, route_name='four')
config.add_view(view_five, route_name='five')
+ config.add_request_method(lambda r: 'bar', 'foo', property=True)
return config
diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py
index 452d762f8..eb1f3534c 100644
--- a/pyramid/tests/test_config/test_factories.py
+++ b/pyramid/tests/test_config/test_factories.py
@@ -144,6 +144,24 @@ class TestFactoriesMixin(unittest.TestCase):
self.assertRaises(ConfigurationError, get_bad_name)
+ def test_set_execution_policy(self):
+ from pyramid.interfaces import IExecutionPolicy
+ config = self._makeOne(autocommit=True)
+ def dummy_policy(environ, router): pass
+ config.set_execution_policy(dummy_policy)
+ registry = config.registry
+ result = registry.queryUtility(IExecutionPolicy)
+ self.assertEqual(result, dummy_policy)
+
+ def test_set_execution_policy_to_None(self):
+ from pyramid.interfaces import IExecutionPolicy
+ from pyramid.router import default_execution_policy
+ config = self._makeOne(autocommit=True)
+ config.set_execution_policy(None)
+ registry = config.registry
+ result = registry.queryUtility(IExecutionPolicy)
+ self.assertEqual(result, default_execution_policy)
+
class TestDeprecatedFactoriesMixinMethods(unittest.TestCase):
def setUp(self):
from zope.deprecation import __show__
@@ -203,4 +221,3 @@ class TestDeprecatedFactoriesMixinMethods(unittest.TestCase):
config.set_request_property(bar, name='bar')
self.assertRaises(ConfigurationConflictError, config.commit)
-
diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py
index c2786c391..f23e54609 100644
--- a/pyramid/tests/test_integration.py
+++ b/pyramid/tests/test_integration.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import datetime
+import gc
import locale
import os
import unittest
@@ -8,6 +9,7 @@ import unittest
from pyramid.wsgi import wsgiapp
from pyramid.view import view_config
from pyramid.static import static_view
+from pyramid.testing import skip_on
from pyramid.compat import (
text_,
url_quote,
@@ -610,7 +612,7 @@ class SubrequestAppTest(unittest.TestCase):
def test_one(self):
res = self.testapp.get('/view_one', status=200)
- self.assertTrue(b'This came from view_two' in res.body)
+ self.assertTrue(b'This came from view_two, foo=bar' in res.body)
def test_three(self):
res = self.testapp.get('/view_three', status=500)
@@ -741,3 +743,29 @@ def _assertBody(body, filename):
data = data.replace(b'\r', b'')
data = data.replace(b'\n', b'')
assert(body == data)
+
+
+class MemoryLeaksTest(unittest.TestCase):
+
+ def tearDown(self):
+ import pyramid.config
+ pyramid.config.global_registries.empty()
+
+ def get_gc_count(self):
+ last_collected = 0
+ while True:
+ collected = gc.collect()
+ if collected == last_collected:
+ break
+ last_collected = collected
+ return len(gc.get_objects())
+
+ @skip_on('pypy')
+ def test_memory_leaks(self):
+ from pyramid.config import Configurator
+ Configurator().make_wsgi_app() # Initialize all global objects
+
+ initial_count = self.get_gc_count()
+ Configurator().make_wsgi_app()
+ current_count = self.get_gc_count()
+ self.assertEqual(current_count, initial_count)
diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py
index 7aa42804c..a5da5c627 100644
--- a/pyramid/tests/test_router.py
+++ b/pyramid/tests/test_router.py
@@ -1271,6 +1271,19 @@ class TestRouter(unittest.TestCase):
start_response = DummyStartResponse()
self.assertRaises(PredicateMismatch, router, environ, start_response)
+ def test_custom_execution_policy(self):
+ from pyramid.interfaces import IExecutionPolicy
+ from pyramid.request import Request
+ from pyramid.response import Response
+ registry = self.config.registry
+ def dummy_policy(environ, router):
+ return Response(status=200, body=b'foo')
+ registry.registerUtility(dummy_policy, IExecutionPolicy)
+ router = self._makeOne()
+ resp = Request.blank('/').get_response(router)
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.body, b'foo')
+
class DummyPredicate(object):
def __call__(self, info, request):
return True
diff --git a/pyramid/util.py b/pyramid/util.py
index 3337d410d..2827884a3 100644
--- a/pyramid/util.py
+++ b/pyramid/util.py
@@ -231,17 +231,20 @@ class WeakOrderedSet(object):
self._order.remove(oid)
self._order.append(oid)
return
- ref = weakref.ref(item, lambda x: self.remove(item))
+ ref = weakref.ref(item, lambda x: self._remove_by_id(oid))
self._items[oid] = ref
self._order.append(oid)
- def remove(self, item):
+ def _remove_by_id(self, oid):
""" Remove an item from the set."""
- oid = id(item)
if oid in self._items:
del self._items[oid]
self._order.remove(oid)
+ def remove(self, item):
+ """ Remove an item from the set."""
+ self._remove_by_id(id(item))
+
def empty(self):
""" Clear all objects from the set."""
self._items = {}