diff options
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 = {} |
