From 68d12cd78c6406e21e2b861c3fcfd3b37f038953 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 10 Jul 2011 12:38:42 -0500 Subject: Adding a global to track the last registry loaded by Pyramid. --- pyramid/config.py | 14 ++++++++++++++ pyramid/tests/test_config.py | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/pyramid/config.py b/pyramid/config.py index 44ce5110e..dff88b574 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -5,6 +5,7 @@ import sys import types import traceback import warnings +import weakref import venusian @@ -989,6 +990,14 @@ class Configurator(object): self.registry.notify(ApplicationCreated(app)) finally: self.manager.pop() + + # see the comments on p.config.last_registry to understand why + def cleanup_last_registry(ref): + global last_registry + last_registry = None + global last_registry + last_registry = weakref.ref(self.registry, cleanup_last_registry) + return app @action_method @@ -3318,3 +3327,8 @@ def isexception(o): (inspect.isclass(o) and (issubclass(o, Exception))) ) +# last_registry is a hack to keep track of the registry for the last Pyramid +# application created. This is useful to access the registry after the app +# itself has been wrapped in a WSGI stack, specifically for scripting +# purposes in pyramid.scripting. +last_registry = None diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 002eab8e8..f49a693f0 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -672,6 +672,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(len(L), 1) def test_make_wsgi_app(self): + import pyramid.config from pyramid.router import Router from pyramid.interfaces import IApplicationCreated manager = DummyThreadLocalManager() @@ -683,9 +684,14 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(manager.pushed['registry'], config.registry) self.assertEqual(manager.pushed['request'], None) self.assertTrue(manager.popped) + self.assertEqual(pyramid.config.last_registry(), app.registry) self.assertEqual(len(subscriber), 1) self.assertTrue(IApplicationCreated.providedBy(subscriber[0])) + def test_uninitialized_last_registry(self): + import pyramid.config + self.assertEqual(pyramid.config.last_registry, None) + def test_include_with_dotted_name(self): from pyramid import tests config = self._makeOne() -- cgit v1.2.3 From 981c054d746e29f42ac16da48c838729537f2eea Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 11 Jul 2011 04:53:46 -0500 Subject: Added tracking of p.config.global_registries for created apps. --- pyramid/config.py | 56 ++++++++++++++++++++++------ pyramid/tests/test_config.py | 89 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 129 insertions(+), 16 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index dff88b574..93ef51163 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -1,3 +1,4 @@ +import collections import inspect import os import re @@ -982,6 +983,7 @@ class Configurator(object): self.commit() from pyramid.router import Router # avoid circdep app = Router(self.registry) + global_registries.add(self.registry) # We push the registry on to the stack here in case any code # that depends on the registry threadlocal APIs used in # listeners subscribed to the IApplicationCreated event. @@ -991,13 +993,6 @@ class Configurator(object): finally: self.manager.pop() - # see the comments on p.config.last_registry to understand why - def cleanup_last_registry(ref): - global last_registry - last_registry = None - global last_registry - last_registry = weakref.ref(self.registry, cleanup_last_registry) - return app @action_method @@ -3327,8 +3322,45 @@ def isexception(o): (inspect.isclass(o) and (issubclass(o, Exception))) ) -# last_registry is a hack to keep track of the registry for the last Pyramid -# application created. This is useful to access the registry after the app -# itself has been wrapped in a WSGI stack, specifically for scripting -# purposes in pyramid.scripting. -last_registry = None +class WeakOrderedSet(object): + """ Maintain a set of items. + + Each item is stored as a weakref to avoid extending their lifetime. + + The values may be iterated over or the last item added may be + accessed via the ``last`` property. + """ + + def __init__(self): + self._items = {} + self._order = [] + + def add(self, item): + """ Add a registry to the set.""" + oid = id(item) + if oid in self._items: + return + def cleanup(ref): + del self._items[oid] + self._order.remove(oid) + ref = weakref.ref(item, cleanup) + self._items[oid] = ref + self._order.append(oid) + + def __len__(self): + return len(self._order) + + def __contains__(self, item): + oid = id(item) + return oid in self._items + + def __iter__(self): + return (self._items[oid]() for oid in self._order) + + @property + def last(self): + if self._order: + oid = self._order[-1] + return self._items[oid]() + +global_registries = WeakOrderedSet() diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index f49a693f0..1e73573ae 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -684,13 +684,30 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(manager.pushed['registry'], config.registry) self.assertEqual(manager.pushed['request'], None) self.assertTrue(manager.popped) - self.assertEqual(pyramid.config.last_registry(), app.registry) + self.assertEqual(pyramid.config.global_registries.last, app.registry) self.assertEqual(len(subscriber), 1) self.assertTrue(IApplicationCreated.providedBy(subscriber[0])) - def test_uninitialized_last_registry(self): - import pyramid.config - self.assertEqual(pyramid.config.last_registry, None) + def test_global_registries_empty(self): + import gc + from pyramid.config import global_registries + gc.collect() # force weakref updates + self.assertEqual(global_registries.last, None) + + def test_global_registries(self): + import gc + from pyramid.config import global_registries + config1 = self._makeOne() + config1.make_wsgi_app() + self.assertEqual(global_registries.last, config1.registry) + config2 = self._makeOne() + config2.make_wsgi_app() + self.assertEqual(global_registries.last, config2.registry) + self.assertEqual(list(global_registries), + [config1.registry, config2.registry]) + del config2 + gc.collect() # force weakref updates + self.assertEqual(global_registries.last, config1.registry) def test_include_with_dotted_name(self): from pyramid import tests @@ -5307,6 +5324,70 @@ class Test_isexception(unittest.TestCase): pass self.assertEqual(self._callFUT(ISubException), True) +class Test_WeakOrderedSet(unittest.TestCase): + def _makeOne(self): + from pyramid.config import WeakOrderedSet + return WeakOrderedSet() + + def test_empty(self): + wos = self._makeOne() + self.assertEqual(len(wos), 0) + self.assertEqual(wos.last, None) + + def test_add_item(self): + wos = self._makeOne() + reg = DummyRegistry() + wos.add(reg) + self.assertEqual(list(wos), [reg]) + self.assert_(reg in wos) + self.assertEqual(wos.last, reg) + + def test_add_multiple_items(self): + wos = self._makeOne() + reg1 = DummyRegistry() + reg2 = DummyRegistry() + wos.add(reg1) + wos.add(reg2) + self.assertEqual(len(wos), 2) + self.assertEqual(list(wos), [reg1, reg2]) + self.assert_(reg1 in wos) + self.assert_(reg2 in wos) + self.assertEqual(wos.last, reg2) + + def test_add_duplicate_items(self): + wos = self._makeOne() + reg = DummyRegistry() + wos.add(reg) + wos.add(reg) + self.assertEqual(len(wos), 1) + self.assertEqual(list(wos), [reg]) + self.assert_(reg in wos) + self.assertEqual(wos.last, reg) + + def test_weakref_removal(self): + import gc + wos = self._makeOne() + reg = DummyRegistry() + wos.add(reg) + del reg + gc.collect() # force gc + self.assertEqual(len(wos), 0) + self.assertEqual(list(wos), []) + self.assertEqual(wos.last, None) + + def test_last_updated(self): + import gc + wos = self._makeOne() + reg = DummyRegistry() + reg2 = DummyRegistry() + wos.add(reg) + wos.add(reg2) + del reg2 + gc.collect() # force gc + self.assertEqual(len(wos), 1) + self.assertEqual(list(wos), [reg]) + self.assertEqual(wos.last, reg) + class DummyRequest: subpath = () matchdict = None -- cgit v1.2.3 From 91cd7e3c00e659391ffc64b19126c1016749bdd5 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 11 Jul 2011 05:00:42 -0500 Subject: Moved the WeakOrderedSet into pyramid.util. --- pyramid/config.py | 44 +---------------------------- pyramid/tests/test_config.py | 64 ------------------------------------------ pyramid/tests/test_util.py | 66 ++++++++++++++++++++++++++++++++++++++++++++ pyramid/util.py | 40 +++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 107 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 93ef51163..0ba7fb995 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -1,4 +1,3 @@ -import collections import inspect import os import re @@ -6,7 +5,6 @@ import sys import types import traceback import warnings -import weakref import venusian @@ -84,6 +82,7 @@ from pyramid.traversal import find_interface from pyramid.traversal import traversal_path from pyramid.urldispatch import RoutesMapper from pyramid.util import DottedNameResolver +from pyramid.util import WeakOrderedSet from pyramid.view import render_view_to_response DEFAULT_RENDERERS = ( @@ -3322,45 +3321,4 @@ def isexception(o): (inspect.isclass(o) and (issubclass(o, Exception))) ) -class WeakOrderedSet(object): - """ Maintain a set of items. - - Each item is stored as a weakref to avoid extending their lifetime. - - The values may be iterated over or the last item added may be - accessed via the ``last`` property. - """ - - def __init__(self): - self._items = {} - self._order = [] - - def add(self, item): - """ Add a registry to the set.""" - oid = id(item) - if oid in self._items: - return - def cleanup(ref): - del self._items[oid] - self._order.remove(oid) - ref = weakref.ref(item, cleanup) - self._items[oid] = ref - self._order.append(oid) - - def __len__(self): - return len(self._order) - - def __contains__(self, item): - oid = id(item) - return oid in self._items - - def __iter__(self): - return (self._items[oid]() for oid in self._order) - - @property - def last(self): - if self._order: - oid = self._order[-1] - return self._items[oid]() - global_registries = WeakOrderedSet() diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 1e73573ae..9dd07a65b 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -5324,70 +5324,6 @@ class Test_isexception(unittest.TestCase): pass self.assertEqual(self._callFUT(ISubException), True) -class Test_WeakOrderedSet(unittest.TestCase): - def _makeOne(self): - from pyramid.config import WeakOrderedSet - return WeakOrderedSet() - - def test_empty(self): - wos = self._makeOne() - self.assertEqual(len(wos), 0) - self.assertEqual(wos.last, None) - - def test_add_item(self): - wos = self._makeOne() - reg = DummyRegistry() - wos.add(reg) - self.assertEqual(list(wos), [reg]) - self.assert_(reg in wos) - self.assertEqual(wos.last, reg) - - def test_add_multiple_items(self): - wos = self._makeOne() - reg1 = DummyRegistry() - reg2 = DummyRegistry() - wos.add(reg1) - wos.add(reg2) - self.assertEqual(len(wos), 2) - self.assertEqual(list(wos), [reg1, reg2]) - self.assert_(reg1 in wos) - self.assert_(reg2 in wos) - self.assertEqual(wos.last, reg2) - - def test_add_duplicate_items(self): - wos = self._makeOne() - reg = DummyRegistry() - wos.add(reg) - wos.add(reg) - self.assertEqual(len(wos), 1) - self.assertEqual(list(wos), [reg]) - self.assert_(reg in wos) - self.assertEqual(wos.last, reg) - - def test_weakref_removal(self): - import gc - wos = self._makeOne() - reg = DummyRegistry() - wos.add(reg) - del reg - gc.collect() # force gc - self.assertEqual(len(wos), 0) - self.assertEqual(list(wos), []) - self.assertEqual(wos.last, None) - - def test_last_updated(self): - import gc - wos = self._makeOne() - reg = DummyRegistry() - reg2 = DummyRegistry() - wos.add(reg) - wos.add(reg2) - del reg2 - gc.collect() # force gc - self.assertEqual(len(wos), 1) - self.assertEqual(list(wos), [reg]) - self.assertEqual(wos.last, reg) - class DummyRequest: subpath = () matchdict = None diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 47aab948a..65aca88b2 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -175,3 +175,69 @@ class TestDottedNameResolver(unittest.TestCase): self.assertEqual(typ.package, None) self.assertEqual(typ.package_name, None) +class Test_WeakOrderedSet(unittest.TestCase): + def _makeOne(self): + from pyramid.config import WeakOrderedSet + return WeakOrderedSet() + + def test_empty(self): + wos = self._makeOne() + self.assertEqual(len(wos), 0) + self.assertEqual(wos.last, None) + + def test_add_item(self): + wos = self._makeOne() + reg = Dummy() + wos.add(reg) + self.assertEqual(list(wos), [reg]) + self.assert_(reg in wos) + self.assertEqual(wos.last, reg) + + def test_add_multiple_items(self): + wos = self._makeOne() + reg1 = Dummy() + reg2 = Dummy() + wos.add(reg1) + wos.add(reg2) + self.assertEqual(len(wos), 2) + self.assertEqual(list(wos), [reg1, reg2]) + self.assert_(reg1 in wos) + self.assert_(reg2 in wos) + self.assertEqual(wos.last, reg2) + + def test_add_duplicate_items(self): + wos = self._makeOne() + reg = Dummy() + wos.add(reg) + wos.add(reg) + self.assertEqual(len(wos), 1) + self.assertEqual(list(wos), [reg]) + self.assert_(reg in wos) + self.assertEqual(wos.last, reg) + + def test_weakref_removal(self): + import gc + wos = self._makeOne() + reg = Dummy() + wos.add(reg) + del reg + gc.collect() # force gc + self.assertEqual(len(wos), 0) + self.assertEqual(list(wos), []) + self.assertEqual(wos.last, None) + + def test_last_updated(self): + import gc + wos = self._makeOne() + reg = Dummy() + reg2 = Dummy() + wos.add(reg) + wos.add(reg2) + del reg2 + gc.collect() # force gc + self.assertEqual(len(wos), 1) + self.assertEqual(list(wos), [reg]) + self.assertEqual(wos.last, reg) + +class Dummy(object): + pass diff --git a/pyramid/util.py b/pyramid/util.py index 3e6cd2e60..b3fda9016 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -1,5 +1,6 @@ import pkg_resources import sys +import weakref from pyramid.exceptions import ConfigurationError from pyramid.path import package_of @@ -143,4 +144,43 @@ class DottedNameResolver(object): return self._zope_dottedname_style(dotted) return dotted +class WeakOrderedSet(object): + """ Maintain a set of items. + Each item is stored as a weakref to avoid extending their lifetime. + + The values may be iterated over or the last item added may be + accessed via the ``last`` property. + """ + + def __init__(self): + self._items = {} + self._order = [] + + def add(self, item): + """ Add a registry to the set.""" + oid = id(item) + if oid in self._items: + return + def cleanup(ref): + del self._items[oid] + self._order.remove(oid) + ref = weakref.ref(item, cleanup) + self._items[oid] = ref + self._order.append(oid) + + def __len__(self): + return len(self._order) + + def __contains__(self, item): + oid = id(item) + return oid in self._items + + def __iter__(self): + return (self._items[oid]() for oid in self._order) + + @property + def last(self): + if self._order: + oid = self._order[-1] + return self._items[oid]() -- cgit v1.2.3 From 31c20b20346d93c326159cabe02cff076eb4ce0f Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 11 Jul 2011 05:25:40 -0500 Subject: Added the ability to make a request object for use in scripts. --- pyramid/scripting.py | 47 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/pyramid/scripting.py b/pyramid/scripting.py index a3ec9bee5..d2495675e 100644 --- a/pyramid/scripting.py +++ b/pyramid/scripting.py @@ -1,3 +1,4 @@ +from pyramid.config import global_registries from pyramid.request import Request from pyramid.interfaces import IRequestFactory @@ -6,16 +7,18 @@ def get_root(app, request=None): :term:`router` instance as the ``app`` argument. The ``root`` returned is the application root object. The ``closer`` returned is a callable (accepting no arguments) that should be called when - your scripting application is finished using the root. If - ``request`` is not None, it is used as the request passed to the - :app:`Pyramid` application root factory. A request is - constructed and passed to the root factory if ``request`` is None.""" - registry = app.registry + your scripting application is finished using the root. + + If ``request`` is not None, it is used as the request passed to the + :app:`Pyramid` application root factory. A request is constructed + using :meth:`pyramid.scripting.make_request` and passed to the root + factory if ``request`` is None.""" + if hasattr(app, 'registry'): + registry = app.registry + else: + registry = global_registries.last if request is None: - request_factory = registry.queryUtility( - IRequestFactory, default=Request) - request = request_factory.blank('/') - request.registry = registry + request = make_request('/', registry) threadlocals = {'registry':registry, 'request':request} app.threadlocal_manager.push(threadlocals) def closer(request=request): # keep request alive via this function default @@ -23,3 +26,29 @@ def get_root(app, request=None): root = app.root_factory(request) return root, closer +def make_request(url, registry=None): + """ Return a :meth:`pyramid.request.Request` object anchored at a + given URL. The object returned will be generated from the supplied + registry's :term:`Request Factory` using the + :meth:`pyramid.interfaces.IRequestFactory.blank` method. + + This request object can be passed to + :meth:`pyramid.scripting.get_root` to initialize an application in + preparation for executing a script with a proper environment setup. + URLs can then be generated with the object, as well as rendering + templates. + + If ``registry`` is not supplied, the last registry loaded from + :meth:`pyramid.config.global_registries` will be used. If you have + loaded more than one :app:`Pyramid` application in the current + process, you may not want to use the last registry loaded, thus + you can search the ``global_registries`` and supply the appropriate + one based on your own criteria. + """ + if registry is None: + registry = global_registries.last + request_factory = registry.queryUtility(IRequestFactory, default=Request) + request = request_factory.blank(url) + request.registry = registry + return request + -- cgit v1.2.3 From 37e3bebf0165ac5f32c82c0bc87296e0ca5fefd3 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 11 Jul 2011 05:40:06 -0500 Subject: Added some docs for make_request and global_registries. --- docs/api/config.rst | 10 ++++++++++ docs/api/scripting.rst | 2 ++ pyramid/scripting.py | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/api/config.rst b/docs/api/config.rst index 71ef4a746..d021412b8 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -86,3 +86,13 @@ .. automethod:: testing_add_renderer + .. attribute:: global_registries + + A set of registries that have been created for :app:`Pyramid` + applications. The object itself supports iteration and has a + ``last`` property containing the last registry loaded. + + The registries contained in this object are stored as weakrefs, + thus they will only exist for the lifetime of the actual + applications for which they are being used. + diff --git a/docs/api/scripting.rst b/docs/api/scripting.rst index 9d5bc2e58..2029578ba 100644 --- a/docs/api/scripting.rst +++ b/docs/api/scripting.rst @@ -7,3 +7,5 @@ .. autofunction:: get_root + .. autofunction:: make_request + diff --git a/pyramid/scripting.py b/pyramid/scripting.py index d2495675e..fe942ceaf 100644 --- a/pyramid/scripting.py +++ b/pyramid/scripting.py @@ -39,7 +39,7 @@ def make_request(url, registry=None): templates. If ``registry`` is not supplied, the last registry loaded from - :meth:`pyramid.config.global_registries` will be used. If you have + :attr:`pyramid.config.global_registries` will be used. If you have loaded more than one :app:`Pyramid` application in the current process, you may not want to use the last registry loaded, thus you can search the ``global_registries`` and supply the appropriate -- cgit v1.2.3 From a02407ee018a17a0186b3e139b15e05f8ff1c795 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 11 Jul 2011 20:30:38 -0500 Subject: Updated scripting test coverage to 100%. --- pyramid/scripting.py | 6 ++-- pyramid/tests/test_scripting.py | 61 ++++++++++++++++++++++++++++++++--------- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/pyramid/scripting.py b/pyramid/scripting.py index fe942ceaf..e04c52a08 100644 --- a/pyramid/scripting.py +++ b/pyramid/scripting.py @@ -26,9 +26,9 @@ def get_root(app, request=None): root = app.root_factory(request) return root, closer -def make_request(url, registry=None): +def make_request(path, registry=None): """ Return a :meth:`pyramid.request.Request` object anchored at a - given URL. The object returned will be generated from the supplied + given path. The object returned will be generated from the supplied registry's :term:`Request Factory` using the :meth:`pyramid.interfaces.IRequestFactory.blank` method. @@ -48,7 +48,7 @@ def make_request(url, registry=None): if registry is None: registry = global_registries.last request_factory = registry.queryUtility(IRequestFactory, default=Request) - request = request_factory.blank(url) + request = request_factory.blank(path) request.registry = registry return request diff --git a/pyramid/tests/test_scripting.py b/pyramid/tests/test_scripting.py index d2139b7db..6ed1325ce 100644 --- a/pyramid/tests/test_scripting.py +++ b/pyramid/tests/test_scripting.py @@ -2,11 +2,11 @@ import unittest class TestGetRoot(unittest.TestCase): def _callFUT(self, app, request=None): - from pyramid.paster import get_root + from pyramid.scripting import get_root return get_root(app, request) def test_it_norequest(self): - app = DummyApp() + app = DummyApp(registry=dummy_registry) root, closer = self._callFUT(app) self.assertEqual(len(app.threadlocal_manager.pushed), 1) pushed = app.threadlocal_manager.pushed[0] @@ -17,7 +17,7 @@ class TestGetRoot(unittest.TestCase): self.assertEqual(len(app.threadlocal_manager.popped), 1) def test_it_withrequest(self): - app = DummyApp() + app = DummyApp(registry=dummy_registry) request = DummyRequest({}) root, closer = self._callFUT(app, request) self.assertEqual(len(app.threadlocal_manager.pushed), 1) @@ -29,24 +29,58 @@ class TestGetRoot(unittest.TestCase): self.assertEqual(len(app.threadlocal_manager.popped), 1) def test_it_requestfactory_overridden(self): + app = DummyApp(registry=dummy_registry) + root, closer = self._callFUT(app) + self.assertEqual(len(app.threadlocal_manager.pushed), 1) + pushed = app.threadlocal_manager.pushed[0] + self.assertEqual(pushed['request'].environ['path'], '/') + + def test_it_with_no_registry(self): + from pyramid.config import global_registries app = DummyApp() - request = Dummy() - class DummyFactory(object): - @classmethod - def blank(cls, path): - return request + # keep registry local so that global_registries is cleared after registry = DummyRegistry(DummyFactory) - app.registry = registry + global_registries.add(registry) root, closer = self._callFUT(app) self.assertEqual(len(app.threadlocal_manager.pushed), 1) pushed = app.threadlocal_manager.pushed[0] - self.assertEqual(pushed['request'], request) + self.assertEqual(pushed['request'].registry, registry) + +class TestMakeRequest(unittest.TestCase): + def _callFUT(self, path='/', registry=None): + from pyramid.scripting import make_request + return make_request(path, registry) + + def test_it(self): + request = self._callFUT('/', dummy_registry) + self.assertEqual(request.environ['path'], '/') + self.assertEqual(request.registry, dummy_registry) + + def test_it_with_nondefault_path(self): + request = self._callFUT('/users/login', dummy_registry) + self.assertEqual(request.environ['path'], '/users/login') + self.assertEqual(request.registry, dummy_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) + global_registries.add(registry) + request = self._callFUT() + self.assertEqual(request.environ['path'], '/') + self.assertEqual(request.registry, registry) class Dummy: pass dummy_root = Dummy() +class DummyFactory(object): + @classmethod + def blank(cls, path): + req = DummyRequest({'path': path}) + return req + class DummyRegistry(object): def __init__(self, result=None): self.result = result @@ -54,12 +88,13 @@ class DummyRegistry(object): def queryUtility(self, iface, default=None): return self.result or default -dummy_registry = DummyRegistry() +dummy_registry = DummyRegistry(DummyFactory) class DummyApp: - def __init__(self): - self.registry = dummy_registry + def __init__(self, registry=None): self.threadlocal_manager = DummyThreadLocalManager() + if registry: + self.registry = registry def root_factory(self, environ): return dummy_root -- cgit v1.2.3 From 54376af6ae324606a96bbc92629cfb2b0b375e12 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 11 Jul 2011 21:08:27 -0500 Subject: Reverted get_root back to its behavior of expecting a router instance. --- pyramid/scripting.py | 5 +---- pyramid/tests/test_scripting.py | 22 +++------------------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/pyramid/scripting.py b/pyramid/scripting.py index e04c52a08..79523dff1 100644 --- a/pyramid/scripting.py +++ b/pyramid/scripting.py @@ -13,10 +13,7 @@ def get_root(app, request=None): :app:`Pyramid` application root factory. A request is constructed using :meth:`pyramid.scripting.make_request` and passed to the root factory if ``request`` is None.""" - if hasattr(app, 'registry'): - registry = app.registry - else: - registry = global_registries.last + registry = app.registry if request is None: request = make_request('/', registry) threadlocals = {'registry':registry, 'request':request} diff --git a/pyramid/tests/test_scripting.py b/pyramid/tests/test_scripting.py index 6ed1325ce..315ab222f 100644 --- a/pyramid/tests/test_scripting.py +++ b/pyramid/tests/test_scripting.py @@ -35,39 +35,23 @@ class TestGetRoot(unittest.TestCase): pushed = app.threadlocal_manager.pushed[0] self.assertEqual(pushed['request'].environ['path'], '/') - def test_it_with_no_registry(self): - from pyramid.config import global_registries - app = DummyApp() - # keep registry local so that global_registries is cleared after - registry = DummyRegistry(DummyFactory) - global_registries.add(registry) - root, closer = self._callFUT(app) - self.assertEqual(len(app.threadlocal_manager.pushed), 1) - pushed = app.threadlocal_manager.pushed[0] - self.assertEqual(pushed['request'].registry, registry) - class TestMakeRequest(unittest.TestCase): def _callFUT(self, path='/', registry=None): from pyramid.scripting import make_request return make_request(path, registry) - def test_it(self): + def test_it_with_registry(self): request = self._callFUT('/', dummy_registry) self.assertEqual(request.environ['path'], '/') self.assertEqual(request.registry, dummy_registry) - def test_it_with_nondefault_path(self): - request = self._callFUT('/users/login', dummy_registry) - self.assertEqual(request.environ['path'], '/users/login') - self.assertEqual(request.registry, dummy_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) global_registries.add(registry) - request = self._callFUT() - self.assertEqual(request.environ['path'], '/') + request = self._callFUT('/hello') + self.assertEqual(request.environ['path'], '/hello') self.assertEqual(request.registry, registry) class Dummy: -- cgit v1.2.3 From 2e3a01d35413debcaf0081d17b48cfc5c74a6d59 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 12 Jul 2011 00:38:59 -0500 Subject: Just changed some of the docstrings to reference config_uri. --- pyramid/paster.py | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index a5cd63dfb..08a54001c 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -17,17 +17,17 @@ zope.deprecation.deprecated( 'pyramid.scaffolds.PyramidTemplate in Pyramid 1.1'), ) -def get_app(config_file, name=None, loadapp=loadapp): +def get_app(config_uri, name=None, loadapp=loadapp): """ Return the WSGI application named ``name`` in the PasteDeploy - config file ``config_file``. + config file specified by ``config_uri``. If the ``name`` is None, this will attempt to parse the name from - the ``config_file`` string expecting the format ``ini_file#name``. + the ``config_uri`` string expecting the format ``inifile#name``. If no name is found, the name will default to "main".""" - if '#' in config_file: - path, section = config_file.split('#', 1) + if '#' in config_uri: + path, section = config_uri.split('#', 1) else: - path, section = config_file, 'main' + path, section = config_uri, 'main' if name: section = name config_name = 'config:%s' % path @@ -56,18 +56,19 @@ class PShellCommand(PCommand): This command accepts one positional argument: - ``config_file#section_name`` -- specifies the PasteDeploy config file - to use for the interactive shell. If the section_name is left off, + ``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. Example:: $ paster pshell myapp.ini#main - .. note:: You should use a ``section_name`` that refers to the - actual ``app`` section in the config file that points at - your Pyramid app without any middleware wrapping, or this - command will almost certainly fail. + .. note:: If you do not point the loader directly at the section of the + ini file containing your :app:`Pyramid` application, the + command will attempt to find the app for you. If you are + loading a pipeline that contains more than one :app:`Pyramid` + application within it, the loader will use the last one. """ summary = "Open an interactive shell with a Pyramid application loaded" @@ -106,10 +107,10 @@ class PShellCommand(PCommand): IPShell = None cprt = 'Type "help" for more information.' banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) - app_spec = self.args[0] - config_file = app_spec.split('#', 1)[0] + config_uri = self.args[0] + config_file = config_uri.split('#', 1)[0] self.logging_file_config(config_file) - app = self.get_app(app_spec, loadapp=self.loadapp[0]) + app = self.get_app(config_uri, loadapp=self.loadapp[0]) # load default globals shell_globals = { @@ -184,8 +185,8 @@ class PRoutesCommand(PCommand): This command accepts one positional argument: - ``config_file#section_name`` -- specifies the PasteDeploy config file - to use for the interactive shell. If the section_name is left off, + ``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. Example:: @@ -218,8 +219,8 @@ class PRoutesCommand(PCommand): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView from zope.interface import Interface - app_spec = self.args[0] - app = self.get_app(app_spec, loadapp=self.loadapp[0]) + config_uri = self.args[0] + app = self.get_app(config_uri, loadapp=self.loadapp[0]) registry = app.registry mapper = self._get_mapper(app) if mapper is not None: @@ -253,8 +254,8 @@ class PViewsCommand(PCommand): This command accepts two positional arguments: - ``config_file#section_name`` -- specifies the PasteDeploy config file - to use for the interactive shell. If the section_name is left off, + ``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`` -- specifies the URL that will be used to find matching views. @@ -465,10 +466,10 @@ class PViewsCommand(PCommand): self.out("%sview predicates (%s)" % (indent, predicate_text)) def command(self): - app_spec, url = self.args + config_uri, url = self.args if not url.startswith('/'): url = '/%s' % url - app = self.get_app(app_spec, loadapp=self.loadapp[0]) + app = self.get_app(config_uri, loadapp=self.loadapp[0]) registry = app.registry view = self._find_view(url, registry) self.out('') -- cgit v1.2.3 From 359906f09a389db4386984c84cc615eb1f033b8c Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 13 Jul 2011 22:33:13 -0500 Subject: Added p.scripting.get_root2 that doesn't require an app arg. --- docs/api/scripting.rst | 2 ++ pyramid/scripting.py | 36 +++++++++++++++++++++++++- pyramid/tests/test_scripting.py | 57 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 91 insertions(+), 4 deletions(-) diff --git a/docs/api/scripting.rst b/docs/api/scripting.rst index 2029578ba..3e9a814fc 100644 --- a/docs/api/scripting.rst +++ b/docs/api/scripting.rst @@ -7,5 +7,7 @@ .. autofunction:: get_root + .. autofunction:: get_root2 + .. autofunction:: make_request diff --git a/pyramid/scripting.py b/pyramid/scripting.py index 79523dff1..cbcba95df 100644 --- a/pyramid/scripting.py +++ b/pyramid/scripting.py @@ -1,6 +1,9 @@ from pyramid.config import global_registries from pyramid.request import Request from pyramid.interfaces import IRequestFactory +from pyramid.interfaces import IRootFactory +from pyramid.threadlocal import manager as threadlocal_manager +from pyramid.traversal import DefaultRootFactory def get_root(app, request=None): """ Return a tuple composed of ``(root, closer)`` when provided a @@ -23,6 +26,38 @@ def get_root(app, request=None): root = app.root_factory(request) return root, closer +def get_root2(request=None, registry=None): + """ Return a tuple composed of ``(root, closer)``. The ``root`` + returned is the application's root object. The ``closer`` returned + is a callable (accepting no arguments) that should be called when + your scripting application is finished using the root. + + If ``request`` is None, a default one is constructed using + :meth:`pyramid.scripting.make_request`. It is used as the request + passed to the :app:`Pyramid` application root factory. + + If ``registry`` is not supplied, the last registry loaded from + :attr:`pyramid.config.global_registries` will be used. If you have + loaded more than one :app:`Pyramid` application in the current + process, you may not want to use the last registry loaded, thus + you can search the ``global_registries`` and supply the appropriate + one based on your own criteria. + """ + if registry is None: + registry = getattr(request, 'registry', global_registries.last) + if request is None: + request = make_request('/', registry) + request.registry = registry + threadlocals = {'registry':registry, 'request':request} + threadlocal_manager.push(threadlocals) + def closer(request=request): # keep request alive via this function default + threadlocal_manager.pop() + q = registry.queryUtility + root_factory = registry.queryUtility(IRootFactory, + default=DefaultRootFactory) + root = root_factory(request) + return root, closer + def make_request(path, registry=None): """ Return a :meth:`pyramid.request.Request` object anchored at a given path. The object returned will be generated from the supplied @@ -48,4 +83,3 @@ def make_request(path, registry=None): request = request_factory.blank(path) request.registry = registry return request - diff --git a/pyramid/tests/test_scripting.py b/pyramid/tests/test_scripting.py index 315ab222f..9bf57be06 100644 --- a/pyramid/tests/test_scripting.py +++ b/pyramid/tests/test_scripting.py @@ -35,6 +35,53 @@ class TestGetRoot(unittest.TestCase): pushed = app.threadlocal_manager.pushed[0] self.assertEqual(pushed['request'].environ['path'], '/') +class TestGetRoot2(unittest.TestCase): + def _callFUT(self, request=None, registry=None): + from pyramid.scripting import get_root2 + return get_root2(request, registry) + + def _makeRegistry(self): + return DummyRegistry(DummyFactory) + + def setUp(self): + from pyramid.threadlocal import manager + self.manager = manager + self.default = manager.get() + + def tearDown(self): + self.assertEqual(self.default, self.manager.get()) + + def test_it_norequest(self): + registry = self._makeRegistry() + root, closer = self._callFUT(registry=registry) + pushed = self.manager.get() + self.assertEqual(pushed['registry'], registry) + self.assertEqual(pushed['request'].registry, registry) + self.assertEqual(root.a, (pushed['request'],)) + closer() + + def test_it_withrequest(self): + request = DummyRequest({}) + registry = request.registry = self._makeRegistry() + root, closer = self._callFUT(request) + pushed = self.manager.get() + self.assertEqual(pushed['request'], request) + self.assertEqual(pushed['registry'], registry) + self.assertEqual(pushed['request'].registry, registry) + self.assertEqual(root.a, (request,)) + closer() + + def test_it_with_request_and_registry(self): + request = DummyRequest({}) + registry = request.registry = self._makeRegistry() + root, closer = self._callFUT(request, registry) + pushed = self.manager.get() + self.assertEqual(pushed['request'], request) + self.assertEqual(pushed['registry'], registry) + self.assertEqual(pushed['request'].registry, registry) + self.assertEqual(root.a, (request,)) + closer() + class TestMakeRequest(unittest.TestCase): def _callFUT(self, path='/', registry=None): from pyramid.scripting import make_request @@ -65,12 +112,16 @@ class DummyFactory(object): req = DummyRequest({'path': path}) return req + def __init__(self, *a, **kw): + self.a = a + self.kw = kw + class DummyRegistry(object): - def __init__(self, result=None): - self.result = result + def __init__(self, factory=None): + self.factory = factory def queryUtility(self, iface, default=None): - return self.result or default + return self.factory or default dummy_registry = DummyRegistry(DummyFactory) -- cgit v1.2.3 From c01a8429ca4f385f0443f324c76d57b17d77dedb Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 13 Jul 2011 22:57:00 -0500 Subject: garden --- pyramid/paster.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index 08a54001c..7d75def75 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -57,8 +57,8 @@ class PShellCommand(PCommand): This command accepts one positional argument: ``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. + interactive shell. The format is ``inifile#name``. If the name is left + off, ``main`` will be assumed. Example:: @@ -186,8 +186,8 @@ class PRoutesCommand(PCommand): This command accepts one positional argument: ``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. + interactive shell. The format is ``inifile#name``. If the name is left + off, ``main`` will be assumed. Example:: @@ -255,8 +255,8 @@ class PViewsCommand(PCommand): 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. + interactive shell. The format is ``inifile#name``. If the name is left + off, ``main`` will be assumed. ``url`` -- specifies the URL that will be used to find matching views. -- cgit v1.2.3 From f422adb9108520182c7eee5128c0f1e1f64d2e17 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 13 Jul 2011 22:57:09 -0500 Subject: Added p.paster.bootstrap for handling simple loading of INI files. --- docs/api/paster.rst | 12 +++--------- pyramid/paster.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/docs/api/paster.rst b/docs/api/paster.rst index 6668f3c77..09e768fae 100644 --- a/docs/api/paster.rst +++ b/docs/api/paster.rst @@ -3,14 +3,8 @@ :mod:`pyramid.paster` --------------------------- -.. module:: pyramid.paster +.. automodule:: pyramid.paster -.. function:: get_app(config_file, name=None) - - Return the WSGI application named ``name`` in the PasteDeploy - config file ``config_file``. - - If the ``name`` is None, this will attempt to parse the name from - the ``config_file`` string expecting the format ``ini_file#name``. - If no name is found, the name will default to "main". + .. autofunction:: get_app + .. autofunction:: bootstrap diff --git a/pyramid/paster.py b/pyramid/paster.py index 7d75def75..9ebeacab9 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -9,6 +9,7 @@ from paste.deploy import loadapp from paste.script.command import Command from pyramid.scripting import get_root +from pyramid.scripting import get_root2 from pyramid.util import DottedNameResolver from pyramid.scaffolds import PyramidTemplate # bw compat @@ -35,6 +36,37 @@ def get_app(config_uri, name=None, loadapp=loadapp): app = loadapp(config_name, name=section, relative_to=here_dir) return app +def bootstrap(config_uri, request=None): + """ Load a WSGI application from the PasteDeploy config file specified + by ``config_uri``. + + .. note:: Most operations within :app:`Pyramid` expect to be invoked + within the context of a WSGI request, thus it's important when + loading your application to anchor it when executing scripts + and other code that is not normally invoked during active WSGI + requests. + + .. note:: For a complex config file containing multiple :app:`Pyramid` + applications, this function will setup the environment under + the context of the last-loaded :app:`Pyramid` application. You + may load a specific application yourself by using the + lower-level functions :meth:`pyramid.paster.get_app` and + :meth:`pyramid.scripting.get_root2` in conjunction with + :attr:`pyramid.config.global_registries`. + + ``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. + + ``request`` -- specified to anchor the script to a given set of WSGI + parameters. For example, most people would want to specify the host, + scheme and port such that their script will generate URLs in relation + to those parameters. + """ + app = get_app(config_uri) + root, closer = get_root2(request) + return (app, root, closer) + _marker = object() class PCommand(Command): -- cgit v1.2.3 From 6fa3ad096942a171765ffca94d648eed911feefe Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 14 Jul 2011 18:53:20 -0500 Subject: Removed unused variable --- pyramid/scripting.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyramid/scripting.py b/pyramid/scripting.py index cbcba95df..bdc287c83 100644 --- a/pyramid/scripting.py +++ b/pyramid/scripting.py @@ -52,7 +52,6 @@ def get_root2(request=None, registry=None): threadlocal_manager.push(threadlocals) def closer(request=request): # keep request alive via this function default threadlocal_manager.pop() - q = registry.queryUtility root_factory = registry.queryUtility(IRootFactory, default=DefaultRootFactory) root = root_factory(request) -- cgit v1.2.3 From 71696bd1a38466033370c3de56c55ebcd64163f2 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 14 Jul 2011 18:59:20 -0500 Subject: Modified bootstrap to return a dict. --- pyramid/paster.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index 9ebeacab9..578b854a5 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -38,7 +38,9 @@ def get_app(config_uri, name=None, loadapp=loadapp): def bootstrap(config_uri, request=None): """ Load a WSGI application from the PasteDeploy config file specified - by ``config_uri``. + by ``config_uri``. The environment will be configured as if it is + currently serving ``request``, leaving a natural environment in place + to write scripts that can generate URLs and utilize renderers. .. note:: Most operations within :app:`Pyramid` expect to be invoked within the context of a WSGI request, thus it's important when @@ -65,7 +67,11 @@ def bootstrap(config_uri, request=None): """ app = get_app(config_uri) root, closer = get_root2(request) - return (app, root, closer) + return { + 'app': app, + 'root': root, + 'closer': closer, + } _marker = object() -- cgit v1.2.3 From 00f7c6abb9ed581411044e9aee2f1647cfadfcb7 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 14 Jul 2011 19:54:59 -0500 Subject: Added test coverage for p.paster.bootstrap. --- docs/api/paster.rst | 9 ++++++++- pyramid/tests/test_paster.py | 41 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/docs/api/paster.rst b/docs/api/paster.rst index 09e768fae..2a32e07e9 100644 --- a/docs/api/paster.rst +++ b/docs/api/paster.rst @@ -5,6 +5,13 @@ .. automodule:: pyramid.paster - .. autofunction:: get_app + .. function:: get_app(config_uri, name=None) + + Return the WSGI application named ``name`` in the PasteDeploy + config file specified by ``config_uri``. + + If the ``name`` is None, this will attempt to parse the name from + the ``config_uri`` string expecting the format ``inifile#name``. + If no name is found, the name will default to "main". .. autofunction:: bootstrap diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index e7a3b7507..df55afcb8 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -928,8 +928,45 @@ class TestGetApp(unittest.TestCase): self.assertEqual(loadapp.section_name, 'yourapp') self.assertEqual(loadapp.relative_to, os.getcwd()) self.assertEqual(result, app) - - + +class TestBootstrap(unittest.TestCase): + def _callFUT(self, config_uri, request=None): + from pyramid.paster import bootstrap + return bootstrap(config_uri, request) + + def setUp(self): + import pyramid.paster + self.original_get_app = pyramid.paster.get_app + self.original_getroot2 = pyramid.paster.get_root2 + self.app = app = DummyApp() + self.root = root = Dummy() + + class DummyGetApp(object): + def __call__(self, *a, **kw): + self.a = a + self.kw = kw + return app + self.get_app = pyramid.paster.get_app = DummyGetApp() + + class DummyGetRoot2(object): + def __call__(self, *a, **kw): + self.a = a + self.kw = kw + return (root, lambda: None) + self.getroot = pyramid.paster.get_root2 = DummyGetRoot2() + + def tearDown(self): + import pyramid.paster + pyramid.paster.get_app = self.original_get_app + pyramid.paster.get_root2 = self.original_getroot2 + + def test_it_request_with_registry(self): + request = DummyRequest({}) + request.registry = dummy_registry + result = self._callFUT('/foo/bar/myapp.ini', request) + self.assertEqual(result['app'], self.app) + self.assertEqual(result['root'], self.root) + self.assert_('closer' in result) class Dummy: pass -- cgit v1.2.3 From eff1cb657b787771aeb2ed0be28c3709ae019fc3 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 14 Jul 2011 21:04:32 -0500 Subject: Modified tests to use global_registries.remove() instead of relying on gc. --- pyramid/tests/test_config.py | 9 ++++----- pyramid/tests/test_scripting.py | 1 + pyramid/tests/test_util.py | 19 +++++++++++++------ pyramid/util.py | 19 ++++++++++++++----- 4 files changed, 32 insertions(+), 16 deletions(-) diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 9dd07a65b..dd5c90bf7 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -687,16 +687,15 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(pyramid.config.global_registries.last, app.registry) self.assertEqual(len(subscriber), 1) self.assertTrue(IApplicationCreated.providedBy(subscriber[0])) + pyramid.config.global_registries.empty() def test_global_registries_empty(self): - import gc from pyramid.config import global_registries - gc.collect() # force weakref updates self.assertEqual(global_registries.last, None) def test_global_registries(self): - import gc from pyramid.config import global_registries + global_registries.empty() config1 = self._makeOne() config1.make_wsgi_app() self.assertEqual(global_registries.last, config1.registry) @@ -705,9 +704,9 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(global_registries.last, config2.registry) self.assertEqual(list(global_registries), [config1.registry, config2.registry]) - del config2 - gc.collect() # force weakref updates + global_registries.remove(config2.registry) self.assertEqual(global_registries.last, config1.registry) + global_registries.empty() def test_include_with_dotted_name(self): from pyramid import tests diff --git a/pyramid/tests/test_scripting.py b/pyramid/tests/test_scripting.py index 9bf57be06..50dbaae85 100644 --- a/pyramid/tests/test_scripting.py +++ b/pyramid/tests/test_scripting.py @@ -100,6 +100,7 @@ class TestMakeRequest(unittest.TestCase): request = self._callFUT('/hello') self.assertEqual(request.environ['path'], '/hello') self.assertEqual(request.registry, registry) + global_registries.empty() class Dummy: pass diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 65aca88b2..821d1ff31 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -216,28 +216,35 @@ class Test_WeakOrderedSet(unittest.TestCase): self.assertEqual(wos.last, reg) def test_weakref_removal(self): - import gc wos = self._makeOne() reg = Dummy() wos.add(reg) - del reg - gc.collect() # force gc + wos.remove(reg) self.assertEqual(len(wos), 0) self.assertEqual(list(wos), []) self.assertEqual(wos.last, None) def test_last_updated(self): - import gc wos = self._makeOne() reg = Dummy() reg2 = Dummy() wos.add(reg) wos.add(reg2) - del reg2 - gc.collect() # force gc + wos.remove(reg2) self.assertEqual(len(wos), 1) self.assertEqual(list(wos), [reg]) self.assertEqual(wos.last, reg) + def test_empty(self): + wos = self._makeOne() + reg = Dummy() + reg2 = Dummy() + wos.add(reg) + wos.add(reg2) + wos.empty() + self.assertEqual(len(wos), 0) + self.assertEqual(list(wos), []) + self.assertEqual(wos.last, None) + class Dummy(object): pass diff --git a/pyramid/util.py b/pyramid/util.py index b3fda9016..a4b69ed96 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -158,17 +158,26 @@ class WeakOrderedSet(object): self._order = [] def add(self, item): - """ Add a registry to the set.""" + """ Add an item to the set.""" oid = id(item) if oid in self._items: return - def cleanup(ref): - del self._items[oid] - self._order.remove(oid) - ref = weakref.ref(item, cleanup) + ref = weakref.ref(item, lambda x: self.remove(item)) self._items[oid] = ref self._order.append(oid) + def remove(self, item): + """ Remove an item from the set.""" + oid = id(item) + if oid in self._items: + del self._items[oid] + self._order.remove(oid) + + def empty(self): + """ Clear all objects from the set.""" + self._items = {} + self._order = [] + def __len__(self): return len(self._order) -- cgit v1.2.3 From c38aaf545681738e841351c74f7c687b488fe6c6 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 15 Jul 2011 05:00:59 -0400 Subject: deshadow test --- pyramid/tests/test_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 821d1ff31..247b61dad 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -180,7 +180,7 @@ class Test_WeakOrderedSet(unittest.TestCase): from pyramid.config import WeakOrderedSet return WeakOrderedSet() - def test_empty(self): + def test_ctor(self): wos = self._makeOne() self.assertEqual(len(wos), 0) self.assertEqual(wos.last, None) -- cgit v1.2.3 From 153c2b9bce3170d58cafa48d819aef4497159091 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 15 Jul 2011 09:31:34 -0400 Subject: add description of return value --- pyramid/paster.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyramid/paster.py b/pyramid/paster.py index 578b854a5..8211dc637 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -42,6 +42,12 @@ def bootstrap(config_uri, request=None): currently serving ``request``, leaving a natural environment in place to write scripts that can generate URLs and utilize renderers. + This function returns a dictionary with ``app``, ``root`` and ``closer`` + keys. ``app`` is the WSGI app loaded (based on the ``config_uri``), + ``root`` is the traversal root resource of the Pyramid application, and + ``closer`` is a parameterless callback that may be called when your + script is complete (it pops a threadlocal stack). + .. note:: Most operations within :app:`Pyramid` expect to be invoked within the context of a WSGI request, thus it's important when loading your application to anchor it when executing scripts -- cgit v1.2.3 From c515d77de5b2f62727251ebc32d1292e67811771 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 15 Jul 2011 10:13:07 -0400 Subject: - get_root2 -> prepare - change prepare return value to a dict, and return the registry, request, etc - various docs and changelog entries. --- CHANGES.txt | 33 +++++++++++++++++++++++++++++++++ docs/api/config.rst | 8 +++++--- docs/api/scripting.rst | 2 +- docs/narr/assets.rst | 2 +- docs/whatsnew-1.1.rst | 39 +++++++++++++++++++++++++++++++++++++++ pyramid/paster.py | 11 ++++------- pyramid/scripting.py | 32 ++++++++++++++++++++++---------- pyramid/tests/test_paster.py | 10 +++++----- pyramid/tests/test_scripting.py | 23 +++++++++++++---------- 9 files changed, 123 insertions(+), 37 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index bc906772d..0e1f67cdc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -16,6 +16,39 @@ Features ``pyramid.static.static_view`` exposes a ``use_subpath`` flag for use when you want the static view to behave like the older deprecated version. +- A new API function ``pyramid.paster.bootstrap`` has been added to make + writing scripts that bootstrap a Pyramid environment easier, e.g.:: + + from pyramid.paster import bootstrap + info = bootstrap('/path/to/my/development.ini') + request = info['request'] + print request.route_url('myroute') + +- A new API function ``pyramid.scripting.prepare`` has been added. It is a + lower-level analogue of ``pyramid.paster.boostrap`` that accepts a request + and a registry instead of a config file argument, and is used for the same + purpose:: + + from pyramid.scripting import prepare + info = prepare(registry=myregistry) + request = info['request'] + print request.route_url('myroute') + +- A new API function ``pyramid.scripting.make_request`` has been added. The + resulting request will have a ``registry`` attribute. It is meant to be + used in conjunction with ``pyramid.scripting.prepare`` and/or + ``pyramid.paster.bootstrap`` (both of which accept a request as an + argument):: + + from pyramid.scripting import make_request + request = make_request('/') + +- New API attribute ``pyramid.config.global_registries`` is an iterable + object that contains references to every Pyramid registry loaded into the + current process via ``pyramid.config.Configurator.make_app``. It also has + a ``last`` attribute containing the last registry loaded. This is used by + the scripting machinery, and is available for introspection. + Deprecations ------------ diff --git a/docs/api/config.rst b/docs/api/config.rst index d021412b8..2c394ac41 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -88,9 +88,11 @@ .. attribute:: global_registries - A set of registries that have been created for :app:`Pyramid` - applications. The object itself supports iteration and has a - ``last`` property containing the last registry loaded. + The set of registries that have been created for :app:`Pyramid` + applications, one per each call to + :meth:`pyramid.config.Configurator.make_app` in the current process. The + object itself supports iteration and has a ``last`` property containing + the last registry loaded. The registries contained in this object are stored as weakrefs, thus they will only exist for the lifetime of the actual diff --git a/docs/api/scripting.rst b/docs/api/scripting.rst index 3e9a814fc..79136a98b 100644 --- a/docs/api/scripting.rst +++ b/docs/api/scripting.rst @@ -7,7 +7,7 @@ .. autofunction:: get_root - .. autofunction:: get_root2 + .. autofunction:: prepare .. autofunction:: make_request diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index d57687477..f35f6dd7d 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -312,7 +312,7 @@ its behavior is almost exactly the same once it's configured. ``add_view`` (at least those without a ``route_name``). A :class:`~pyramid.static.static_view` static view cannot be made root-relative when you use traversal unless it's registered as a - :term:`NotFound view`. + :term:`Not Found view`. To serve files within a directory located on your filesystem at ``/path/to/static/dir`` as the result of a "catchall" route hanging from the diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index dd4d488a0..32955ab75 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -266,6 +266,45 @@ Minor Feature Additions :class:`pyramid.static.static_view` exposes a ``use_subpath`` flag for use when you want the static view to behave like the older deprecated version. +- A new API function :func:`pyramid.paster.bootstrap` has been added to make + writing scripts that bootstrap a Pyramid environment easier, e.g.: + + .. code-block:: python + + from pyramid.paster import bootstrap + info = bootstrap('/path/to/my/development.ini') + request = info['request'] + print request.route_url('myroute') + +- A new api function :func:`pyramid.scripting.prepare` has been added. It is + a lower-level analogue of :func:`pyramid.paster.boostrap` that accepts a + request and a registry instead of a config file argument, and is used for + the same purpose: + + .. code-block:: python + + from pyramid.scripting import prepare + info = prepare(registry=myregistry) + request = info['request'] + print request.route_url('myroute') + +- A new API function :func:`pyramid.scripting.make_request` has been added. + The resulting request will have a ``registry`` attribute. It is meant to + be used in conjunction with :func:`pyramid.scripting.prepare` and/or + :func:`pyramid.paster.bootstrap` (both of which accept a request as an + argument): + + .. code-block:: python + + from pyramid.scripting import make_request + request = make_request('/') + +- New API attribute :attr:`pyramid.config.global_registries` is an iterable + object that contains references to every Pyramid registry loaded into the + current process via :meth:`pyramid.config.Configurator.make_app`. It also + has a ``last`` attribute containing the last registry loaded. This is used + by the scripting machinery, and is available for introspection. + Backwards Incompatibilities --------------------------- diff --git a/pyramid/paster.py b/pyramid/paster.py index 8211dc637..b6d7777c0 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -9,7 +9,7 @@ from paste.deploy import loadapp from paste.script.command import Command from pyramid.scripting import get_root -from pyramid.scripting import get_root2 +from pyramid.scripting import prepare from pyramid.util import DottedNameResolver from pyramid.scaffolds import PyramidTemplate # bw compat @@ -72,12 +72,9 @@ def bootstrap(config_uri, request=None): to those parameters. """ app = get_app(config_uri) - root, closer = get_root2(request) - return { - 'app': app, - 'root': root, - 'closer': closer, - } + info = prepare(request) + info['app'] = app + return info _marker = object() diff --git a/pyramid/scripting.py b/pyramid/scripting.py index bdc287c83..c04915d3a 100644 --- a/pyramid/scripting.py +++ b/pyramid/scripting.py @@ -26,15 +26,15 @@ def get_root(app, request=None): root = app.root_factory(request) return root, closer -def get_root2(request=None, registry=None): - """ Return a tuple composed of ``(root, closer)``. The ``root`` - returned is the application's root object. The ``closer`` returned - is a callable (accepting no arguments) that should be called when - your scripting application is finished using the root. +def prepare(request=None, registry=None): + """ This function pushes data onto the Pyramid threadlocal stack (request + and registry), making those objects 'current'. It returns a dictionary + useful for bootstrapping a Pyramid application in a scripting + environment. - If ``request`` is None, a default one is constructed using - :meth:`pyramid.scripting.make_request`. It is used as the request - passed to the :app:`Pyramid` application root factory. + If ``request`` is None, a default request is constructed using + :meth:`pyramid.scripting.make_request`. The request passed to the + :app:`Pyramid` application root factory to compute the root. If ``registry`` is not supplied, the last registry loaded from :attr:`pyramid.config.global_registries` will be used. If you have @@ -42,6 +42,17 @@ def get_root2(request=None, registry=None): process, you may not want to use the last registry loaded, thus you can search the ``global_registries`` and supply the appropriate one based on your own criteria. + + The function returns a dictionary composed of ``root``, ``closer``, + ``registry``, ``request`` and ``root_factory``. The ``root`` returned is + the application's root resource object. The ``closer`` returned is a + callable (accepting no arguments) that should be called when your + scripting application is finished using the root. ``registry`` is the + registry object passed or the last registry loaded into + :attr:`pyramid.config.global_registries` if no registry is passed. + ``request`` is the request object passed or the constructed request if no + request is passed. ``root_factory`` is the root factory used to + construct the root. """ if registry is None: registry = getattr(request, 'registry', global_registries.last) @@ -50,12 +61,13 @@ def get_root2(request=None, registry=None): request.registry = registry threadlocals = {'registry':registry, 'request':request} threadlocal_manager.push(threadlocals) - def closer(request=request): # keep request alive via this function default + def closer(): threadlocal_manager.pop() root_factory = registry.queryUtility(IRootFactory, default=DefaultRootFactory) root = root_factory(request) - return root, closer + return {'root':root, 'closer':closer, 'registry':registry, + 'request':request, 'root_factory':root_factory} def make_request(path, registry=None): """ Return a :meth:`pyramid.request.Request` object anchored at a diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index df55afcb8..7785b006e 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -937,7 +937,7 @@ class TestBootstrap(unittest.TestCase): def setUp(self): import pyramid.paster self.original_get_app = pyramid.paster.get_app - self.original_getroot2 = pyramid.paster.get_root2 + self.original_prepare = pyramid.paster.prepare self.app = app = DummyApp() self.root = root = Dummy() @@ -948,17 +948,17 @@ class TestBootstrap(unittest.TestCase): return app self.get_app = pyramid.paster.get_app = DummyGetApp() - class DummyGetRoot2(object): + class DummyPrepare(object): def __call__(self, *a, **kw): self.a = a self.kw = kw - return (root, lambda: None) - self.getroot = pyramid.paster.get_root2 = DummyGetRoot2() + return {'root':root, 'closer':lambda: None} + self.getroot = pyramid.paster.prepare = DummyPrepare() def tearDown(self): import pyramid.paster pyramid.paster.get_app = self.original_get_app - pyramid.paster.get_root2 = self.original_getroot2 + pyramid.paster.prepare = self.original_prepare def test_it_request_with_registry(self): request = DummyRequest({}) diff --git a/pyramid/tests/test_scripting.py b/pyramid/tests/test_scripting.py index 50dbaae85..ccc6656df 100644 --- a/pyramid/tests/test_scripting.py +++ b/pyramid/tests/test_scripting.py @@ -1,6 +1,6 @@ import unittest -class TestGetRoot(unittest.TestCase): +class Test_get_root(unittest.TestCase): def _callFUT(self, app, request=None): from pyramid.scripting import get_root return get_root(app, request) @@ -35,10 +35,10 @@ class TestGetRoot(unittest.TestCase): pushed = app.threadlocal_manager.pushed[0] self.assertEqual(pushed['request'].environ['path'], '/') -class TestGetRoot2(unittest.TestCase): +class Test_prepare(unittest.TestCase): def _callFUT(self, request=None, registry=None): - from pyramid.scripting import get_root2 - return get_root2(request, registry) + from pyramid.scripting import prepare + return prepare(request, registry) def _makeRegistry(self): return DummyRegistry(DummyFactory) @@ -48,39 +48,42 @@ class TestGetRoot2(unittest.TestCase): self.manager = manager self.default = manager.get() - def tearDown(self): - self.assertEqual(self.default, self.manager.get()) - def test_it_norequest(self): registry = self._makeRegistry() - root, closer = self._callFUT(registry=registry) + info = self._callFUT(registry=registry) + root, closer = info['root'], info['closer'] 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()) def test_it_withrequest(self): request = DummyRequest({}) registry = request.registry = self._makeRegistry() - root, closer = self._callFUT(request) + info = self._callFUT(request=request) + root, closer = info['root'], info['closer'] pushed = self.manager.get() self.assertEqual(pushed['request'], request) self.assertEqual(pushed['registry'], registry) self.assertEqual(pushed['request'].registry, registry) self.assertEqual(root.a, (request,)) closer() + self.assertEqual(self.default, self.manager.get()) def test_it_with_request_and_registry(self): request = DummyRequest({}) registry = request.registry = self._makeRegistry() - root, closer = self._callFUT(request, registry) + info = self._callFUT(request=request, registry=registry) + root, closer = info['root'], info['closer'] pushed = self.manager.get() self.assertEqual(pushed['request'], request) self.assertEqual(pushed['registry'], registry) self.assertEqual(pushed['request'].registry, registry) self.assertEqual(root.a, (request,)) closer() + self.assertEqual(self.default, self.manager.get()) class TestMakeRequest(unittest.TestCase): def _callFUT(self, path='/', registry=None): -- cgit v1.2.3