diff options
43 files changed, 523 insertions, 156 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index eafdcab55..dab788233 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,9 +1,64 @@ Next release ============ +Features +-------- + +- The ``Configurator`` object now has two new methods: ``begin`` and + ``end``. The ``begin`` method is meant to be called before any + "configuration" begins (e.g. before ``add_view``, et. al are + called). The ``end`` method is meant to be called after all + "configuration" is complete. + + Previously, before there was imperative configuration at all (1.1 + and prior), configuration begin and end was invariably implied by + the process of loading a ZCML file. When a ZCML load happened, the + threadlocal data structure containing the request and registry was + modified before the load, and torn down after the load, making sure + that all framework code that needed ``get_current_registry`` for the + duration of the ZCML load was satisfied. + + Some API methods called during imperative configuration, (such as + ``Configurator.add_view`` when a renderer is involved) end up for + historical reasons calling ``get_current_registry``. However, in + 1.2a5 and below, the Configurator supplied no functionality that + allowed people to make sure that ``get_current_registry`` returned + the registry implied by the configurator being used. ``begin`` now + serves this purpose. Inversely, ``end`` pops the thread local + stack, undoing the actions of ``begin``. + + We make this boundary explicit to reduce the potential for confusion + when the configurator is used in different circumstances (e.g. in + unit tests and app code vs. just in initial app setup). + + Existing code written for 1.2a1-1.2a5 which does not call ``begin`` + or ``end`` continues to work in the same manner it did before. It + is however suggested that this code be changed to call ``begin`` and + ``end`` to reduce the potential for confusion in the future. + +- All ``paster`` templates which generate an application skeleton now + make use of the new ``begin`` and ``end`` methods of the + Configurator they use in their respective copies of ``run.py`` and + ``tests.py``. + +Documentation +------------- + +- All documentation that makes use of a ``Configurator`` object to do + application setup and test setup now makes use of the new ``begin`` + and ``end`` methods of the configurator. + Bug Fixes --------- +- When a ``repoze.bfg.exceptions.NotFound`` or + ``repoze.bfg.exceptions.Forbidden`` *class* (as opposed to instance) + was raised as an exception within a root factory (or route root + factory), the exception would not be caught properly by the + ``repoze.bfg.`` Router and it would propagate to up the call stack, + as opposed to rendering the not found view or the forbidden view as + would have been expected. + - When Chameleon page or text templates used as renderers were added imperatively (via ``Configurator.add_view`` or some derivative), they too-eagerly attempted to look up the ``reload_templates`` diff --git a/docs/api/configuration.rst b/docs/api/configuration.rst index 376ff3e40..5e16ac13b 100644 --- a/docs/api/configuration.rst +++ b/docs/api/configuration.rst @@ -7,6 +7,10 @@ .. autoclass:: Configurator(registry=None, package=None, settings=None, root_factory=None, authentication_policy=None, authorization_policy=None, renderers=DEFAULT_RENDERERS, debug_logger=None) + .. automethod:: begin + + .. automethod:: end + .. automethod:: add_renderer(name, factory) .. automethod:: add_route diff --git a/docs/glossary.rst b/docs/glossary.rst index 0fa827188..fab733878 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -509,3 +509,15 @@ Glossary :mod:`repoze.bfg` provides a default implementation of a forbidden view; it can be overridden. See :ref:`changing_the_forbidden_view`. + Thread Local + A thread-local variable is one which is essentially a global + variable in terms of how it is accessed and treated, however, + each `thread + <http://en.wikipedia.org/wiki/Thread_(computer_science)>` used by + the application may have a different value for this same "global" + variable. :mod:`repoze.bfg` uses a small number of thread local + variables, as described in :ref:`threadlocals_chapter`. See also + the `threading.local documentation + <http://docs.python.org/library/threading.html#threading.local>` + for more information. + diff --git a/docs/narr/MyProject/myproject/run.py b/docs/narr/MyProject/myproject/run.py index 0d7647aa7..6a3671c1e 100644 --- a/docs/narr/MyProject/myproject/run.py +++ b/docs/narr/MyProject/myproject/run.py @@ -6,8 +6,10 @@ def app(global_config, **settings): is usually called by the PasteDeploy framework during ``paster serve``""" config = Configurator(root_factory=get_root, settings=settings) + config.begin() zcml_file = settings.get('configure_zcml', 'configure.zcml') config.load_zcml(zcml_file) + config.end() return config.make_wsgi_app() diff --git a/docs/narr/MyProject/myproject/tests.py b/docs/narr/MyProject/myproject/tests.py index 7c3caac74..498bd96e8 100644 --- a/docs/narr/MyProject/myproject/tests.py +++ b/docs/narr/MyProject/myproject/tests.py @@ -1,13 +1,15 @@ import unittest +from repoze.bfg.configuration import Configurator from repoze.bfg import testing class ViewTests(unittest.TestCase): def setUp(self): - testing.setUp() + self.config = Configurator() + self.config.begin() def tearDown(self): - testing.tearDown() + self.config.end() def test_my_view(self): from myproject.views import my_view diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst index 9e7c75005..367df2a2d 100644 --- a/docs/narr/configuration.rst +++ b/docs/narr/configuration.rst @@ -86,8 +86,10 @@ imperatively: if __name__ == '__main__': config = Configurator() + config.begin() config.add_view(hello_world) config.add_view(goodbye_world, name='goodbye') + config.end() app = config.make_wsgi_app() serve(app) @@ -274,12 +276,17 @@ imports and function definitions is placed within the confines of an if __name__ == '__main__': config = Configurator() + config.begin() config.add_view(hello_world) config.add_view(goodbye_world, name='goodbye') + config.end() app = config.make_wsgi_app() simple_server.make_server('', 8080, app).serve_forever() -Let's break this down this piece-by-piece: +Let's break this down this piece-by-piece. + +Configurator Construction +~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python :linenos: @@ -315,6 +322,27 @@ this particular :mod:`repoze.bfg` application. registry object being configured by a ``Configurator`` is available as its ``registry`` attribute. +Beginning Configuration +~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + :linenos: + + config.begin() + +The ``begin`` method of a Configurator tells the the system that +application configuration has begun. In particular, this causes the +:term:`application registry` associated with this configurator to +become the "current" application registry, meaning that code which +attempts to use the application registry :term:`thread local` will +obtain the registry associated with the configurator. This is an +explicit step because it's sometimes convenient to use a configurator +without causing the registry associated with the configurator to +become "current". + +Adding Configuration +~~~~~~~~~~~~~~~~~~~~ + .. code-block:: python :linenos: @@ -411,6 +439,22 @@ the best view configuration for any request, the ``goodbye_world`` view callable will be used when the URL contains path information that ends with ``/goodbye``. +Ending Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + :linenos: + + config.end() + +The ``end`` method of a Configurator tells the the system that +application configuration has ended. It is the inverse of +``config.begin``. In particular, this causes the :term:`application +registry` associated with this configurator to no longer be the +"current" application registry, meaning that code which attempts to +use the application registry :term:`thread local` will no longer +obtain the registry associated with the configurator. + WSGI Application Creation ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -419,13 +463,14 @@ WSGI Application Creation app = config.make_wsgi_app() -After configuring views, the script creates a WSGI *application* via -the ``config.make_wsgi_app`` method. A call to ``make_wsgi_app`` -implies that all configuration is finished (meaning all method calls -to the configurator which set up views, and various other -configuration settings have been performed). The ``make_wsgi_app`` -method returns a :term:`WSGI` application object that can be used by -any WSGI server to present an application to a requestor. +After configuring views and ending configuration, the script creates a +WSGI *application* via the ``config.make_wsgi_app`` method. A call to +``make_wsgi_app`` implies that all configuration is finished (meaning +all method calls to the configurator which set up views, and various +other configuration settings have been performed). The +``make_wsgi_app`` method returns a :term:`WSGI` application object +that can be used by any WSGI server to present an application to a +requestor. The :mod:`repoze.bfg` application object, in particular, is an instance of the ``repoze.bfg.router.Router`` class. It has a @@ -510,7 +555,9 @@ In a file named ``helloworld.py``: if __name__ == '__main__': config = Configurator() + config.begin() config.load_zcml('configure.zcml) + config.end() app = config.make_wsgi_app() serve(app) @@ -547,8 +594,10 @@ within the ``if __name__ == '__main__'`` section of ``helloworld.py``: if __name__ == '__main__': config = Configurator() + config.begin() config.add_view(hello_world) config.add_view(goodbye_world, name='goodbye') + config.end() app = config.make_wsgi_app() simple_server.make_server('', 8080, app).serve_forever() @@ -563,7 +612,9 @@ reads as: if __name__ == '__main__': config = Configurator() + config.begin() config.load_zcml('configure.zcml') + config.end() app = config.make_wsgi_app() simple_server.make_server('', 8080, app).serve_forever() diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 81e570e36..16879b69b 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -724,7 +724,7 @@ without the PasteDeploy configuration file: #. Line 2 imports the ``get_root`` function from :mod:`myproject.models` that we use later. -#. Lines 4-11 define a function that returns a :mod:`repoze.bfg` +#. Lines 4-13 define a function that returns a :mod:`repoze.bfg` WSGI application. This function is meant to be called by the :term:`PasteDeploy` framework as a result of running ``paster serve``. diff --git a/docs/narr/threadlocals.rst b/docs/narr/threadlocals.rst index 476b179ec..b97d9e4f2 100644 --- a/docs/narr/threadlocals.rst +++ b/docs/narr/threadlocals.rst @@ -69,10 +69,11 @@ defined entirely by the behavior of a repoze.bfg :term:`Router`. However, during unit testing, no Router code is ever invoked, and the definition of "current" is defined by the boundary between calls to -the ``repoze.bfg.testing.setUp`` and ``repoze.bfg.testing.tearDown``. -These functions push and pop the threadlocal stack when the system is -under test. See :ref:`test_setup_and_teardown` for the definitions of -these functions. +the ``begin`` and ``end`` methods of a :term:`Configurator` (or, +pre-1.2a6, between calls to the ``repoze.bfg.testing.setUp`` and +``repoze.bfg.testing.tearDown`` functions). These functions push and +pop the threadlocal stack when the system is under test. See +:ref:`test_setup_and_teardown` for the definitions of these functions. Scripts which use :mod:`repoze.bfg` machinery but never actually start a WSGI server or receive requests via HTTP such as scripts which use @@ -142,7 +143,8 @@ and the hack that uses ``get_current_request`` is removed. This would be an appropriate place to use the ``get_current_request`` function. Use of the ``get_current_registry`` function should be limited to -testing scenarios. The registry created by -``repoze.bfg.testing.setUp`` when you do not pass one in is available +testing scenarios. The registry made current by use of a +Configurator's ``begin`` method during a test (or pre-1.2a6, via +``repoze.bfg.testing.setUp``) when you do not pass one in is available to you via this API. diff --git a/docs/narr/unittesting.rst b/docs/narr/unittesting.rst index cc8ab6e32..790c0577d 100644 --- a/docs/narr/unittesting.rst +++ b/docs/narr/unittesting.rst @@ -56,7 +56,7 @@ functions. Test Set Up and Tear Down -------------------------- -:mod:`repoze.bfg` uses a "global" (actually thread-local) data +:mod:`repoze.bfg` uses a "global" (actually :term:`thread local`) data structure to hold on to two items: the current :term:`request` and the current :term:`application registry`. These data structures are available via the ``repoze.bfg.threadlocal.get_current_request`` and @@ -65,42 +65,84 @@ respectively. See :ref:`threadlocals_chapter` for information about these functions and the data structures they return. If your code uses these ``get_current_*`` functions or calls -:mod:`repoze.bfg` code which uses the ``get_current_*`` functions, you -will need to use the ``repoze.bfg.testing.setUp`` and -``repoze.bfg.testing.tearDown`` functions within the ``setUp`` and -``tearDown`` methods of your unit tests, respectively. +:mod:`repoze.bfg` code which uses ``get_current_*`` functions, you +will need to construct at :term:`Configurator` and call its ``begin`` +method within the ``setUp`` method of your unit test and call the same +configurator's ``end`` method within the ``tearDown`` method of your +unit test. -The ``repoze.bfg.testing.setUp`` and ``repoze.bfg.testing.tearDown`` -functions allow you to supply a unit test with an environment that has -a default registry and a default request for the duration of a single -test. Here's an example of using both: +The use of a Configurator and its ``begin`` and ``end`` methods allows +you to supply each unit test method in a test case with an environment +that has a isolated registry and an isolated request for the duration +of a single test. Here's an example of using this feature: .. code-block:: python :linenos: import unittest - from repoze.bfg import testing + from repoze.bfg.configuration import Configurator + + class MyTest(unittest.TestCase): + def setUp(self): + self.config = Configurator() + self.config.begin() + + def tearDown(self): + self.config.end() + +The above will make sure that +``repoze.bfg.threadlocal.get_current_registry`` will return the +:term:`application registry` associated with the ``config`` +Configurator instance when ``get_current_registry`` is called in a +test case method attached to ``MyTest``. Each test case method +attached to ``MyTest`` will use an isolated registry. + +The ``begin`` method of a Configurator accepts various arguments that +influence the code run during the test. See the +:ref:`configuration_module` chapter for information about the API of a +:term:`Configurator`, including its ``begin`` and ``end`` methods. + +If you also want to make ``repoze.bfg.get_current_registry`` return +something other than ``None`` during the course of a single test, you +can pass a :term:`request` object into the ``begin`` method of the +Configurator within the ``setUp`` method of your test: + +.. code-block:: python + :linenos: + + import unittest + from repoze.bfg.configuration import Configurator + from repoze.bfg.request import Request class MyTest(unittest.TestCase): def setUp(self): - testing.setUp() + self.config = Configurator() + request = Request() + self.config.begin(request=request) def tearDown(self): - testing.tearDown() - -If you don't *know* whether you're calling code that uses these -functions, a rule of thumb applies: just always use the -``repoze.bfg.testing.setUp`` and ``repoze.bfg.testing.tearDown`` -functions in the ``setUp`` and ``tearDown`` respectively of unit tests -that test :mod:`repoze.bfg` application code, unless it's obvious -you're not calling any :mod:`repoze.bfg` APIs which might make use of -the any "current" global. - -The ``repoze.bfg.testing.setUp`` and ``repoze.bfg.testing.tearDown`` -functions accept various arguments that influence the code run during -the test. See the :ref:`testing_module` chapter for information about -the APIs of ``repoze.bfg.testing.setUp`` and -``repoze.bfg.testing.tearDown``. + self.config.end() + +If you pass a term:`Request` object into the ``begin`` method of the +configurator within your test case's ``setUp``, any test method +attached to the ``MyTest`` test case that directly or indirectly calls +``get_current_request`` will receive the request you passed into the +``begin`` method. Otherwise, during testing, ``get_current_request`` +will return ``None``. + +What? +~~~~~ + +Thread local data structures are always a bit confusing, especially +when used by frameworks. Sorry. So here's a rule of thumb: if you +don't *know* whether you're calling code that uses the +``get_current_registry`` or ``get_current_request`` functions, or you +don't care about any of this, but you still want to write test code, +just always create a configurator instance and call its ``begin`` +method within the ``setUp`` of a unit test, then subsequently call its +``end`` method in the test's ``tearDown``. This won't really hurt +anything if the application you're testing does not call any +``get_current*`` function. Using the ``repoze.bfg.testing`` API in Unit Tests -------------------------------------------------- @@ -148,14 +190,16 @@ you could write a unittest TestCase that used the testing API. :linenos: import unittest + from repoze.bfg.configuration import Configurator from repoze.bfg import testing class MyTest(unittest.TestCase): def setUp(self): - testing.setUp() + self.config = Configurator() + self.config.begin() def tearDown(self): - testing.tearDown() + self.config.end() def test_view_fn_not_submitted(self): from my.package import view_fn @@ -204,12 +248,12 @@ assertion. We assert at the end of this that the renderer's ``say`` attribute is ``Yo``, as this is what is expected of the view function in the branch it's testing. -Note that the test calls the ``repoze.bfg.testing.setUp`` function in -its ``setUp`` method and the ``repoze.bfg.testing.tearDown`` function -in its ``tearDown`` method. Use of this pattern is required to -perform cleanup between the test runs. If you use any of the testing -API, be sure to call ``repoze.bfg.testing.setUp`` in the test setup -and ``repoze.bfg.testing.tearDown`` in the test teardown. +Note that the test calls the ``begin`` method of a +:term:`Configurator` in its ``setUp`` method and the ``end`` method of +the same in its ``tearDown`` method. If you use any of the +``repoze.bfg.testing`` APIs, be sure to use this pattern in your test +setUp and tearDown; these methods make sure you're using a "fresh" +:term:`application registry` per test run. See the :ref:`testing_module` chapter for the entire :mod:`repoze.bfg` -specific testing API. This chapter describes APIs for registering a @@ -251,6 +295,7 @@ environment. import unittest + from repoze.bfg.configuration import Configurator from repoze.bfg import testing class ViewIntegrationTests(unittest.TestCase): @@ -259,15 +304,14 @@ environment. registrations your application declares in its configure.zcml (including dependent registrations for repoze.bfg itself). """ - from repoze.bfg.configuration import Configurator import myapp - configurator = Configurator(package=myapp) - configurator.load_zcml('myapp:configure.zcml') - testing.setUp(registry=configurator.registry) + self.config = Configurator(package=myapp) + self.config.begin() + self.config.load_zcml('myapp:configure.zcml') def tearDown(self): """ Clear out the application registry """ - testing.tearDown() + self.config.end() def test_my_view(self): from myapp.views import my_view diff --git a/docs/tutorials/bfgwiki/src/authorization/tutorial/run.py b/docs/tutorials/bfgwiki/src/authorization/tutorial/run.py index 98baa440b..c80fce02a 100644 --- a/docs/tutorials/bfgwiki/src/authorization/tutorial/run.py +++ b/docs/tutorials/bfgwiki/src/authorization/tutorial/run.py @@ -15,6 +15,8 @@ def app(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) + config.begin() config.load_zcml('configure.zcml') + config.end() return config.make_wsgi_app() diff --git a/docs/tutorials/bfgwiki/src/authorization/tutorial/tests.py b/docs/tutorials/bfgwiki/src/authorization/tutorial/tests.py index fa025787e..6853bc376 100644 --- a/docs/tutorials/bfgwiki/src/authorization/tutorial/tests.py +++ b/docs/tutorials/bfgwiki/src/authorization/tutorial/tests.py @@ -94,7 +94,7 @@ class AddPageTests(unittest.TestCase): request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) request.subpath = ['AnotherPage'] - response = self._callFUT(context, request) + self._callFUT(context, request) page = context['AnotherPage'] self.assertEqual(page.data, 'Hello yo!') self.assertEqual(page.__name__, 'AnotherPage') diff --git a/docs/tutorials/bfgwiki/src/basiclayout/tutorial/run.py b/docs/tutorials/bfgwiki/src/basiclayout/tutorial/run.py index 748b13eef..7d4220717 100644 --- a/docs/tutorials/bfgwiki/src/basiclayout/tutorial/run.py +++ b/docs/tutorials/bfgwiki/src/basiclayout/tutorial/run.py @@ -15,5 +15,7 @@ def app(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) + config.begin() config.load_zcml('configure.zcml') + config.end() return config.make_wsgi_app() diff --git a/docs/tutorials/bfgwiki/src/basiclayout/tutorial/tests.py b/docs/tutorials/bfgwiki/src/basiclayout/tutorial/tests.py index 849e75ad4..32821ce0e 100644 --- a/docs/tutorials/bfgwiki/src/basiclayout/tutorial/tests.py +++ b/docs/tutorials/bfgwiki/src/basiclayout/tutorial/tests.py @@ -1,13 +1,15 @@ import unittest +from repoze.bfg.configuration import Configurator from repoze.bfg import testing class ViewTests(unittest.TestCase): def setUp(self): - testing.setUp() + self.config = Configurator() + self.config.begin() def tearDown(self): - testing.tearDown() + self.config.end() def test_my_view(self): from tutorial.views import my_view diff --git a/docs/tutorials/bfgwiki/src/models/tutorial/run.py b/docs/tutorials/bfgwiki/src/models/tutorial/run.py index 748b13eef..7d4220717 100644 --- a/docs/tutorials/bfgwiki/src/models/tutorial/run.py +++ b/docs/tutorials/bfgwiki/src/models/tutorial/run.py @@ -15,5 +15,7 @@ def app(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) + config.begin() config.load_zcml('configure.zcml') + config.end() return config.make_wsgi_app() diff --git a/docs/tutorials/bfgwiki/src/models/tutorial/tests.py b/docs/tutorials/bfgwiki/src/models/tutorial/tests.py index 1c1faf855..1eee68e7f 100644 --- a/docs/tutorials/bfgwiki/src/models/tutorial/tests.py +++ b/docs/tutorials/bfgwiki/src/models/tutorial/tests.py @@ -1,5 +1,6 @@ import unittest +from repoze.bfg.configuration import Configurator from repoze.bfg import testing class PageModelTests(unittest.TestCase): @@ -49,10 +50,11 @@ class AppmakerTests(unittest.TestCase): class ViewTests(unittest.TestCase): def setUp(self): - testing.setUp() + self.config = Configurator() + self.config.begin() def tearDown(self): - testing.tearDown() + self.config.end() def test_my_view(self): from tutorial.views import my_view diff --git a/docs/tutorials/bfgwiki/src/viewdecorators/tutorial/run.py b/docs/tutorials/bfgwiki/src/viewdecorators/tutorial/run.py index 748b13eef..7d4220717 100644 --- a/docs/tutorials/bfgwiki/src/viewdecorators/tutorial/run.py +++ b/docs/tutorials/bfgwiki/src/viewdecorators/tutorial/run.py @@ -15,5 +15,7 @@ def app(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) + config.begin() config.load_zcml('configure.zcml') + config.end() return config.make_wsgi_app() diff --git a/docs/tutorials/bfgwiki/src/viewdecorators/tutorial/tests.py b/docs/tutorials/bfgwiki/src/viewdecorators/tutorial/tests.py index fa025787e..6853bc376 100644 --- a/docs/tutorials/bfgwiki/src/viewdecorators/tutorial/tests.py +++ b/docs/tutorials/bfgwiki/src/viewdecorators/tutorial/tests.py @@ -94,7 +94,7 @@ class AddPageTests(unittest.TestCase): request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) request.subpath = ['AnotherPage'] - response = self._callFUT(context, request) + self._callFUT(context, request) page = context['AnotherPage'] self.assertEqual(page.data, 'Hello yo!') self.assertEqual(page.__name__, 'AnotherPage') diff --git a/docs/tutorials/bfgwiki/src/views/tutorial/run.py b/docs/tutorials/bfgwiki/src/views/tutorial/run.py index 748b13eef..7d4220717 100644 --- a/docs/tutorials/bfgwiki/src/views/tutorial/run.py +++ b/docs/tutorials/bfgwiki/src/views/tutorial/run.py @@ -15,5 +15,7 @@ def app(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) + config.begin() config.load_zcml('configure.zcml') + config.end() return config.make_wsgi_app() diff --git a/docs/tutorials/bfgwiki/src/views/tutorial/tests.py b/docs/tutorials/bfgwiki/src/views/tutorial/tests.py index 1b2ea972a..bbf86633a 100644 --- a/docs/tutorials/bfgwiki/src/views/tutorial/tests.py +++ b/docs/tutorials/bfgwiki/src/views/tutorial/tests.py @@ -94,7 +94,7 @@ class AddPageTests(unittest.TestCase): request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) request.subpath = ['AnotherPage'] - response = self._callFUT(context, request) + self._callFUT(context, request) page = context['AnotherPage'] self.assertEqual(page.data, 'Hello yo!') self.assertEqual(page.__name__, 'AnotherPage') diff --git a/docs/tutorials/bfgwiki2/src/authorization/tutorial/run.py b/docs/tutorials/bfgwiki2/src/authorization/tutorial/run.py index 9ca9fe71e..332f0408f 100644 --- a/docs/tutorials/bfgwiki2/src/authorization/tutorial/run.py +++ b/docs/tutorials/bfgwiki2/src/authorization/tutorial/run.py @@ -23,5 +23,7 @@ def app(global_config, **settings): raise ValueError("No 'db_string' value in application configuration.") initialize_sql(db_string) config = Configurator(settings=settings) + config.begin() config.load_zcml('configure.zcml') + config.end() return config.make_wsgi_app() diff --git a/docs/tutorials/bfgwiki2/src/authorization/tutorial/tests.py b/docs/tutorials/bfgwiki2/src/authorization/tutorial/tests.py index 52d2fed86..7bc8e11ce 100644 --- a/docs/tutorials/bfgwiki2/src/authorization/tutorial/tests.py +++ b/docs/tutorials/bfgwiki2/src/authorization/tutorial/tests.py @@ -1,4 +1,6 @@ import unittest + +from repoze.bfg.configuration import Configurator from repoze.bfg import testing def _initTestingDB(): @@ -11,21 +13,22 @@ def _initTestingDB(): Base.metadata.create_all(engine) return DBSession -def _registerRoutes(): - testing.registerRoute(':pagename', 'view_page') - testing.registerRoute(':pagename/edit_page', 'edit_page') - testing.registerRoute('add_page/:pagename', 'add_page') +def _registerRoutes(config): + config.add_route('view_page', ':pagename') + config.add_route('edit_page', ':pagename/edit_page') + config.add_route('add_page', 'add_page/:pagename') class ViewWikiTests(unittest.TestCase): def setUp(self): - testing.setUp() + self.config = Configurator() + self.config.begin() def tearDown(self): - testing.tearDown() + self.config.end() def test_it(self): from tutorial.views import view_wiki - testing.registerRoute(':pagename', 'view_page') + self.config.add_route('view_page', ':pagename') request = testing.DummyRequest() response = view_wiki(request) self.assertEqual(response.location, 'http://example.com/FrontPage') @@ -33,11 +36,12 @@ class ViewWikiTests(unittest.TestCase): class ViewPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() - testing.setUp() + self.config = Configurator() + self.config.begin() def tearDown(self): self.session.remove() - testing.tearDown() + self.config.end() def _callFUT(self, request): from tutorial.views import view_page @@ -49,7 +53,7 @@ class ViewPageTests(unittest.TestCase): request.matchdict['pagename'] = 'IDoExist' page = Page('IDoExist', 'Hello CruelWorld IDoExist') self.session.add(page) - _registerRoutes() + _registerRoutes(self.config) info = self._callFUT(request) self.assertEqual(info['page'], page) self.assertEqual( @@ -67,18 +71,19 @@ class ViewPageTests(unittest.TestCase): class AddPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() - testing.setUp() + self.config = Configurator() + self.config.begin() def tearDown(self): self.session.remove() - testing.tearDown() + self.config.end() def _callFUT(self, request): from tutorial.views import add_page return add_page(request) def test_it_notsubmitted(self): - _registerRoutes() + _registerRoutes(self.config) request = testing.DummyRequest() request.matchdict = {'pagename':'AnotherPage'} info = self._callFUT(request) @@ -88,22 +93,23 @@ class AddPageTests(unittest.TestCase): def test_it_submitted(self): from tutorial.models import Page - _registerRoutes() + _registerRoutes(self.config) request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) request.matchdict = {'pagename':'AnotherPage'} - response = self._callFUT(request) + self._callFUT(request) page = self.session.query(Page).filter_by(name='AnotherPage').one() self.assertEqual(page.data, 'Hello yo!') class EditPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() - testing.setUp() + self.config = Configurator() + self.config.begin() def tearDown(self): self.session.remove() - testing.tearDown() + self.config.end() def _callFUT(self, request): from tutorial.views import edit_page @@ -111,7 +117,7 @@ class EditPageTests(unittest.TestCase): def test_it_notsubmitted(self): from tutorial.models import Page - _registerRoutes() + _registerRoutes(self.config) request = testing.DummyRequest() request.matchdict = {'pagename':'abc'} page = Page('abc', 'hello') @@ -122,7 +128,7 @@ class EditPageTests(unittest.TestCase): def test_it_submitted(self): from tutorial.models import Page - _registerRoutes() + _registerRoutes(self.config) request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) request.matchdict = {'pagename':'abc'} diff --git a/docs/tutorials/bfgwiki2/src/basiclayout/tutorial/run.py b/docs/tutorials/bfgwiki2/src/basiclayout/tutorial/run.py index 9ca9fe71e..332f0408f 100644 --- a/docs/tutorials/bfgwiki2/src/basiclayout/tutorial/run.py +++ b/docs/tutorials/bfgwiki2/src/basiclayout/tutorial/run.py @@ -23,5 +23,7 @@ def app(global_config, **settings): raise ValueError("No 'db_string' value in application configuration.") initialize_sql(db_string) config = Configurator(settings=settings) + config.begin() config.load_zcml('configure.zcml') + config.end() return config.make_wsgi_app() diff --git a/docs/tutorials/bfgwiki2/src/basiclayout/tutorial/tests.py b/docs/tutorials/bfgwiki2/src/basiclayout/tutorial/tests.py index 0c44baf16..2ef74cce7 100644 --- a/docs/tutorials/bfgwiki2/src/basiclayout/tutorial/tests.py +++ b/docs/tutorials/bfgwiki2/src/basiclayout/tutorial/tests.py @@ -1,4 +1,5 @@ import unittest +from repoze.bfg.configuration import Configurator from repoze.bfg import testing def _initTestingDB(): @@ -8,11 +9,12 @@ def _initTestingDB(): class TestMyView(unittest.TestCase): def setUp(self): - testing.setUp() + self.config = Configurator() + self.config.begin() _initTestingDB() def tearDown(self): - testing.tearDown() + self.config.end() def test_it(self): from tutorial.views import my_view diff --git a/docs/tutorials/bfgwiki2/src/models/tutorial/run.py b/docs/tutorials/bfgwiki2/src/models/tutorial/run.py index 9ca9fe71e..332f0408f 100644 --- a/docs/tutorials/bfgwiki2/src/models/tutorial/run.py +++ b/docs/tutorials/bfgwiki2/src/models/tutorial/run.py @@ -23,5 +23,7 @@ def app(global_config, **settings): raise ValueError("No 'db_string' value in application configuration.") initialize_sql(db_string) config = Configurator(settings=settings) + config.begin() config.load_zcml('configure.zcml') + config.end() return config.make_wsgi_app() diff --git a/docs/tutorials/bfgwiki2/src/models/tutorial/tests.py b/docs/tutorials/bfgwiki2/src/models/tutorial/tests.py index 0c44baf16..2ef74cce7 100644 --- a/docs/tutorials/bfgwiki2/src/models/tutorial/tests.py +++ b/docs/tutorials/bfgwiki2/src/models/tutorial/tests.py @@ -1,4 +1,5 @@ import unittest +from repoze.bfg.configuration import Configurator from repoze.bfg import testing def _initTestingDB(): @@ -8,11 +9,12 @@ def _initTestingDB(): class TestMyView(unittest.TestCase): def setUp(self): - testing.setUp() + self.config = Configurator() + self.config.begin() _initTestingDB() def tearDown(self): - testing.tearDown() + self.config.end() def test_it(self): from tutorial.views import my_view diff --git a/docs/tutorials/bfgwiki2/src/views/tutorial/run.py b/docs/tutorials/bfgwiki2/src/views/tutorial/run.py index 9ca9fe71e..332f0408f 100644 --- a/docs/tutorials/bfgwiki2/src/views/tutorial/run.py +++ b/docs/tutorials/bfgwiki2/src/views/tutorial/run.py @@ -23,5 +23,7 @@ def app(global_config, **settings): raise ValueError("No 'db_string' value in application configuration.") initialize_sql(db_string) config = Configurator(settings=settings) + config.begin() config.load_zcml('configure.zcml') + config.end() return config.make_wsgi_app() diff --git a/docs/tutorials/bfgwiki2/src/views/tutorial/tests.py b/docs/tutorials/bfgwiki2/src/views/tutorial/tests.py index 52d2fed86..7bc8e11ce 100644 --- a/docs/tutorials/bfgwiki2/src/views/tutorial/tests.py +++ b/docs/tutorials/bfgwiki2/src/views/tutorial/tests.py @@ -1,4 +1,6 @@ import unittest + +from repoze.bfg.configuration import Configurator from repoze.bfg import testing def _initTestingDB(): @@ -11,21 +13,22 @@ def _initTestingDB(): Base.metadata.create_all(engine) return DBSession -def _registerRoutes(): - testing.registerRoute(':pagename', 'view_page') - testing.registerRoute(':pagename/edit_page', 'edit_page') - testing.registerRoute('add_page/:pagename', 'add_page') +def _registerRoutes(config): + config.add_route('view_page', ':pagename') + config.add_route('edit_page', ':pagename/edit_page') + config.add_route('add_page', 'add_page/:pagename') class ViewWikiTests(unittest.TestCase): def setUp(self): - testing.setUp() + self.config = Configurator() + self.config.begin() def tearDown(self): - testing.tearDown() + self.config.end() def test_it(self): from tutorial.views import view_wiki - testing.registerRoute(':pagename', 'view_page') + self.config.add_route('view_page', ':pagename') request = testing.DummyRequest() response = view_wiki(request) self.assertEqual(response.location, 'http://example.com/FrontPage') @@ -33,11 +36,12 @@ class ViewWikiTests(unittest.TestCase): class ViewPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() - testing.setUp() + self.config = Configurator() + self.config.begin() def tearDown(self): self.session.remove() - testing.tearDown() + self.config.end() def _callFUT(self, request): from tutorial.views import view_page @@ -49,7 +53,7 @@ class ViewPageTests(unittest.TestCase): request.matchdict['pagename'] = 'IDoExist' page = Page('IDoExist', 'Hello CruelWorld IDoExist') self.session.add(page) - _registerRoutes() + _registerRoutes(self.config) info = self._callFUT(request) self.assertEqual(info['page'], page) self.assertEqual( @@ -67,18 +71,19 @@ class ViewPageTests(unittest.TestCase): class AddPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() - testing.setUp() + self.config = Configurator() + self.config.begin() def tearDown(self): self.session.remove() - testing.tearDown() + self.config.end() def _callFUT(self, request): from tutorial.views import add_page return add_page(request) def test_it_notsubmitted(self): - _registerRoutes() + _registerRoutes(self.config) request = testing.DummyRequest() request.matchdict = {'pagename':'AnotherPage'} info = self._callFUT(request) @@ -88,22 +93,23 @@ class AddPageTests(unittest.TestCase): def test_it_submitted(self): from tutorial.models import Page - _registerRoutes() + _registerRoutes(self.config) request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) request.matchdict = {'pagename':'AnotherPage'} - response = self._callFUT(request) + self._callFUT(request) page = self.session.query(Page).filter_by(name='AnotherPage').one() self.assertEqual(page.data, 'Hello yo!') class EditPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() - testing.setUp() + self.config = Configurator() + self.config.begin() def tearDown(self): self.session.remove() - testing.tearDown() + self.config.end() def _callFUT(self, request): from tutorial.views import edit_page @@ -111,7 +117,7 @@ class EditPageTests(unittest.TestCase): def test_it_notsubmitted(self): from tutorial.models import Page - _registerRoutes() + _registerRoutes(self.config) request = testing.DummyRequest() request.matchdict = {'pagename':'abc'} page = Page('abc', 'hello') @@ -122,7 +128,7 @@ class EditPageTests(unittest.TestCase): def test_it_submitted(self): from tutorial.models import Page - _registerRoutes() + _registerRoutes(self.config) request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) request.matchdict = {'pagename':'abc'} diff --git a/docs/whatsnew-1.2.rst b/docs/whatsnew-1.2.rst index c417ba3ae..4332c9948 100644 --- a/docs/whatsnew-1.2.rst +++ b/docs/whatsnew-1.2.rst @@ -41,7 +41,9 @@ The simplest possible :mod:`repoze.bfg` application is now: if __name__ == '__main__': config = Configurator() + config.begin() config.add_view(hello_world) + config.end() app = config.make_wsgi_app() simple_server.make_server('', 8080, app).serve_forever() @@ -141,10 +143,11 @@ Backwards Incompatibilites - When there is no "current registry" in the ``repoze.bfg.threadlocal.manager`` threadlocal data structure (this is the case when there is no "current request" or we're not in the - midst of a ``r.b.testing.setUp``-bounded unit test), the ``.get`` - method of the manager returns a data structure containing a *global* - registry. In previous releases, this function returned the global - Zope "base" registry: the result of + midst of a ``r.b.testing.setUp`` or + ``r.b.configuration.Configurator.begin`` bounded unit test), the + ``.get`` method of the manager returns a data structure containing a + *global* registry. In previous releases, this function returned the + global Zope "base" registry: the result of ``zope.component.getGlobalSiteManager``, which is an instance of the ``zope.component.registry.Component`` class. In this release, however, the global registry returns a globally importable instance @@ -153,8 +156,8 @@ Backwards Incompatibilites ``repoze.bfg.registry.global_registry``. Effectively, this means that when you call - ``repoze.bfg.threadlocal.get_current_registry`` when no request or - ``setUp`` bounded unit test is in effect, you will always get back + ``repoze.bfg.threadlocal.get_current_registry`` when no "real" + request or bounded unit test is in effect, you will always get back the global registry that lives in ``repoze.bfg.registry.global_registry``. It also means that :mod:`repoze.bfg` APIs that *call* ``get_current_registry`` will use diff --git a/repoze/bfg/chameleon_text.py b/repoze/bfg/chameleon_text.py index 9cb1c989a..6a9fb7c6c 100644 --- a/repoze/bfg/chameleon_text.py +++ b/repoze/bfg/chameleon_text.py @@ -43,7 +43,7 @@ class TextTemplateRenderer(object): def __init__(self, path): self.path = path - @reify + @reify # avoid looking up reload_templates before manager pushed def template(self): settings = get_settings() auto_reload = settings and settings['reload_templates'] diff --git a/repoze/bfg/configuration.py b/repoze/bfg/configuration.py index 74d91a73b..a741c1564 100644 --- a/repoze/bfg/configuration.py +++ b/repoze/bfg/configuration.py @@ -124,6 +124,7 @@ class Configurator(object): class. The debug logger is used by :mod:`repoze.bfg` itself to log warnings and authorization debugging information. """ + manager = manager # for testing injection def __init__(self, registry=None, package=None, settings=None, root_factory=None, authentication_policy=None, authorization_policy=None, renderers=DEFAULT_RENDERERS, @@ -274,6 +275,22 @@ class Configurator(object): # API + def begin(self, request=None): + """ Indicate that application or test configuration has begun. + This pushes a dictionary containing the registry implied by + this configurator and the :term:`request` implied by + ``request`` on to the :term:`thread local` stack consulted by + various ``repoze.bfg.threadlocal`` API functions.""" + self.manager.push({'registry':self.registry, 'request':request}) + + def end(self): + """ Indicate that application or test configuration has ended. + This pops the last value pushed on to the :term:`thread local` + stack (usually by the ``begin`` method) and returns that + value. + """ + return self.manager.pop() + def add_subscriber(self, subscriber, iface=None): """Add an event :term:`subscriber` for the event stream implied by the supplied ``iface`` interface. The @@ -291,7 +308,7 @@ class Configurator(object): self.registry.registerHandler(subscriber, iface) return subscriber - def make_wsgi_app(self, manager=manager): + def make_wsgi_app(self): """ Returns a :mod:`repoze.bfg` WSGI application representing the current configuration state and sends a ``repoze.bfg.interfaces.WSGIApplicationCreatedEvent`` event to @@ -302,11 +319,11 @@ class Configurator(object): # 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 WSGIApplicationCreatedEvent. - manager.push({'registry':self.registry, 'request':None}) + self.manager.push({'registry':self.registry, 'request':None}) try: self.registry.notify(WSGIApplicationCreatedEvent(app)) finally: - manager.pop() + self.manager.pop() return app def load_zcml(self, spec='configure.zcml', lock=threading.Lock()): @@ -323,12 +340,12 @@ class Configurator(object): package = sys.modules[package_name] lock.acquire() - manager.push({'registry':self.registry, 'request':None}) + self.manager.push({'registry':self.registry, 'request':None}) try: xmlconfig.file(filename, package, execute=True) finally: lock.release() - manager.pop() + self.manager.pop() return self.registry def add_view(self, view=None, name="", for_=None, permission=None, @@ -1560,12 +1577,14 @@ def make_app(root_factory, package=None, filename='configure.zcml', ``settings`` keyword parameter. """ settings = settings or options or {} + zcml_file = settings.get('configure_zcml', filename) config = Configurator(package=package, settings=settings, root_factory=root_factory) if getSiteManager is None: from zope.component import getSiteManager getSiteManager.sethook(get_current_registry) - zcml_file = settings.get('configure_zcml', filename) + config.begin() config.load_zcml(zcml_file) + config.end() return config.make_wsgi_app() diff --git a/repoze/bfg/paster_templates/alchemy/+package+/run.py_tmpl b/repoze/bfg/paster_templates/alchemy/+package+/run.py_tmpl index 462691048..0c8f31ec8 100644 --- a/repoze/bfg/paster_templates/alchemy/+package+/run.py_tmpl +++ b/repoze/bfg/paster_templates/alchemy/+package+/run.py_tmpl @@ -18,6 +18,7 @@ def app(global_config, **settings): It is usually called by the PasteDeploy framework during ``paster serve``. """ + zcml_file = settings.get('configure_zcml', 'configure.zcml') db_string = settings.get('db_string') if db_string is None: raise ValueError("No 'db_string' in application configuration.") @@ -26,7 +27,8 @@ def app(global_config, **settings): db_echo = True get_root = appmaker(db_string, db_echo) config = Configurator(settings=settings, root_factory=get_root) - zcml_file = settings.get('configure_zcml', 'configure.zcml') + config.begin() config.load_zcml(zcml_file) + config.end() return config.make_wsgi_app() diff --git a/repoze/bfg/paster_templates/routesalchemy/+package+/run.py_tmpl b/repoze/bfg/paster_templates/routesalchemy/+package+/run.py_tmpl index 3b392debd..066dbecc0 100644 --- a/repoze/bfg/paster_templates/routesalchemy/+package+/run.py_tmpl +++ b/repoze/bfg/paster_templates/routesalchemy/+package+/run.py_tmpl @@ -18,12 +18,14 @@ def app(global_config, **settings): It is usually called by the PasteDeploy framework during ``paster serve``. """ + zcml_file = settings.get('configure_zcml', 'configure.zcml') db_string = settings.get('db_string') if db_string is None: raise ValueError("No 'db_string' value in application configuration.") initialize_sql(db_string) config = Configurator(settings=settings) - zcml_file = settings.get('configure_zcml', 'configure.zcml') + config.begin() config.load_zcml(zcml_file) + config.end() return config.make_wsgi_app() diff --git a/repoze/bfg/paster_templates/routesalchemy/+package+/tests.py_tmpl b/repoze/bfg/paster_templates/routesalchemy/+package+/tests.py_tmpl index 5c1477189..ed7f1280b 100644 --- a/repoze/bfg/paster_templates/routesalchemy/+package+/tests.py_tmpl +++ b/repoze/bfg/paster_templates/routesalchemy/+package+/tests.py_tmpl @@ -1,4 +1,5 @@ import unittest +from repoze.bfg.configuration import Configurator from repoze.bfg import testing def _initTestingDB(): @@ -8,11 +9,12 @@ def _initTestingDB(): class TestMyView(unittest.TestCase): def setUp(self): - testing.setUp() + self.config = Configurator() + self.config.begin() _initTestingDB() def tearDown(self): - testing.tearDown() + self.config.end() def test_it(self): from {{package}}.views import my_view diff --git a/repoze/bfg/paster_templates/starter/+package+/run.py_tmpl b/repoze/bfg/paster_templates/starter/+package+/run.py_tmpl index de643f834..4505ed001 100644 --- a/repoze/bfg/paster_templates/starter/+package+/run.py_tmpl +++ b/repoze/bfg/paster_templates/starter/+package+/run.py_tmpl @@ -5,7 +5,9 @@ def app(global_config, **settings): """ This function returns a ``repoze.bfg`` application object. It is usually called by the PasteDeploy framework during ``paster serve``""" - config = Configurator(root_factory=get_root, settings=settings) zcml_file = settings.get('configure_zcml', 'configure.zcml') + config = Configurator(root_factory=get_root, settings=settings) + config.begin() config.load_zcml(zcml_file) + config.end() return config.make_wsgi_app() diff --git a/repoze/bfg/paster_templates/starter/+package+/tests.py_tmpl b/repoze/bfg/paster_templates/starter/+package+/tests.py_tmpl index d4798bce7..578a58d35 100644 --- a/repoze/bfg/paster_templates/starter/+package+/tests.py_tmpl +++ b/repoze/bfg/paster_templates/starter/+package+/tests.py_tmpl @@ -1,13 +1,15 @@ import unittest +from repoze.bfg.configuration import Configurator from repoze.bfg import testing class ViewTests(unittest.TestCase): def setUp(self): - testing.setUp() + self.config = Configurator() + self.config.begin() def tearDown(self): - testing.tearDown() + self.config.end() def test_my_view(self): from {{package}}.views import my_view diff --git a/repoze/bfg/paster_templates/zodb/+package+/run.py_tmpl b/repoze/bfg/paster_templates/zodb/+package+/run.py_tmpl index 3ad6a8970..22e2048e0 100644 --- a/repoze/bfg/paster_templates/zodb/+package+/run.py_tmpl +++ b/repoze/bfg/paster_templates/zodb/+package+/run.py_tmpl @@ -8,6 +8,7 @@ def app(global_config, **settings): It is usually called by the PasteDeploy framework during ``paster serve``. """ zodb_uri = settings.get('zodb_uri') + zcml_file = settings.get('configure_zcml', 'configure.zcml') if zodb_uri is None: raise ValueError("No 'zodb_uri' in application configuration.") @@ -15,6 +16,7 @@ def app(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) - zcml_file = settings.get('configure_zcml', 'configure.zcml') + config.begin() config.load_zcml(zcml_file) + config.end() return config.make_wsgi_app() diff --git a/repoze/bfg/paster_templates/zodb/+package+/tests.py_tmpl b/repoze/bfg/paster_templates/zodb/+package+/tests.py_tmpl index 5c7d27a37..30da5c9b3 100644 --- a/repoze/bfg/paster_templates/zodb/+package+/tests.py_tmpl +++ b/repoze/bfg/paster_templates/zodb/+package+/tests.py_tmpl @@ -1,13 +1,15 @@ import unittest +from repoze.bfg.configuration import Configurator from repoze.bfg import testing class ViewTests(unittest.TestCase): def setUp(self): - testing.setUp() + self.config = Configurator() + self.config.begin() def tearDown(self): - testing.tearDown() + self.config.end() def test_my_view(self): from {{package}}.views import my_view diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py index 630aa201c..caa56ffe2 100644 --- a/repoze/bfg/router.py +++ b/repoze/bfg/router.py @@ -125,11 +125,17 @@ class Router(object): response = view_callable(context, request) except Forbidden, why: - msg = why[0] + try: + msg = why[0] + except (IndexError, TypeError): + msg = '' environ['repoze.bfg.message'] = msg response = self.forbidden_view(context, request) except NotFound, why: - msg = why[0] + try: + msg = why[0] + except (IndexError, TypeError): + msg = '' environ['repoze.bfg.message'] = msg response = self.notfound_view(context, request) diff --git a/repoze/bfg/testing.py b/repoze/bfg/testing.py index 428f1c11f..434131321 100644 --- a/repoze/bfg/testing.py +++ b/repoze/bfg/testing.py @@ -21,6 +21,7 @@ from repoze.bfg.interfaces import ITraverser from repoze.bfg.interfaces import IView from repoze.bfg.interfaces import IViewPermission +from repoze.bfg.configuration import Configurator from repoze.bfg.exceptions import Forbidden from repoze.bfg.registry import Registry from repoze.bfg.security import Allowed @@ -592,17 +593,22 @@ def setUp(registry=None, request=None, hook_zca=True): .. note:: The ``hook_zca`` argument is new as of :mod:`repoze.bfg` 1.2. + + .. warning:: Although this method of tearing a test setup down + will never disappear, after :mod:`repoze.bfg` 1.2a6, + using the ``begin`` and ``end`` methods of a + ``Configurator`` are prefered to using + ``repoze.bfg.testing.setUp`` and + ``repoze.bfg.testing.tearDown``. See + :ref:`unittesting_chapter` for more information. """ manager.clear() if registry is None: registry = Registry('testing') - manager.push({'registry':registry, 'request':request}) + config = Configurator(registry=registry) + config.begin(request=request) if hook_zca: - try: - from zope.component import getSiteManager - getSiteManager.sethook(get_current_registry) - except ImportError: # pragma: no cover - pass + hook_zca_api() def tearDown(unhook_zca=True): """Undo the effects ``repoze.bfg.testing.setUp``. Use this @@ -619,20 +625,24 @@ def tearDown(unhook_zca=True): .. note:: The ``unhook_zca`` argument is new as of :mod:`repoze.bfg` 1.2. + .. warning:: Although this method of tearing a test setup down + will never disappear, after :mod:`repoze.bfg` 1.2a6, + using the ``begin`` and ``end`` methods of a + ``Configurator`` are prefered to using + ``repoze.bfg.testing.setUp`` and + ``repoze.bfg.testing.tearDown``. See + :ref:`unittesting_chapter` for more information. + """ if unhook_zca: - try: - from zope.component import getSiteManager - getSiteManager.reset() - except ImportError: # pragma: no cover - pass + unhook_zca_api() info = manager.pop() manager.clear() if info is not None: - reg = info['registry'] - if hasattr(reg, '__init__') and hasattr(reg, '__name__'): + registry = info['registry'] + if hasattr(registry, '__init__') and hasattr(registry, '__name__'): try: - reg.__init__(reg.__name__) + registry.__init__(registry.__name__) except TypeError: # calling __init__ is largely for the benefit of # people who want to use the global ZCA registry; @@ -648,3 +658,17 @@ def cleanUp(*arg, **kw): extensive production usage, it will never be removed.""" setUp(*arg, **kw) +def hook_zca_api(): + try: + from zope.component import getSiteManager + getSiteManager.sethook(get_current_registry) + except ImportError: # pragma: no cover + pass + +def unhook_zca_api(): + try: + from zope.component import getSiteManager + getSiteManager.reset() + except ImportError: # pragma: no cover + pass + diff --git a/repoze/bfg/tests/fixtureapp/views.py b/repoze/bfg/tests/fixtureapp/views.py index 82e92d618..d9bc0bb6e 100644 --- a/repoze/bfg/tests/fixtureapp/views.py +++ b/repoze/bfg/tests/fixtureapp/views.py @@ -5,9 +5,6 @@ def fixture_view(context, request): """ """ return Response('fixture') -def renderer_view(request): - return {'a':1} - class IDummy(Interface): pass diff --git a/repoze/bfg/tests/test_configuration.py b/repoze/bfg/tests/test_configuration.py index 2e2283ab4..a42e230f1 100644 --- a/repoze/bfg/tests/test_configuration.py +++ b/repoze/bfg/tests/test_configuration.py @@ -88,6 +88,36 @@ class ConfiguratorTests(unittest.TestCase): self.failUnless(config.registry.getUtility(IRendererFactory, '.pt')) self.failUnless(config.registry.getUtility(IRendererFactory, '.txt')) + def test_begin(self): + from repoze.bfg.configuration import Configurator + config = Configurator() + manager = DummyThreadLocalManager() + config.manager = manager + config.begin() + self.assertEqual(manager.pushed, + {'registry':config.registry, 'request':None}) + self.assertEqual(manager.popped, False) + + def test_begin_with_request(self): + from repoze.bfg.configuration import Configurator + config = Configurator() + request = object() + manager = DummyThreadLocalManager() + config.manager = manager + config.begin(request=request) + self.assertEqual(manager.pushed, + {'registry':config.registry, 'request':request}) + self.assertEqual(manager.popped, False) + + def test_end(self): + from repoze.bfg.configuration import Configurator + config = Configurator() + manager = DummyThreadLocalManager() + config.manager = manager + config.end() + self.assertEqual(manager.pushed, None) + self.assertEqual(manager.popped, True) + def test_ctor_with_package_registry(self): import sys from repoze.bfg.configuration import Configurator @@ -274,16 +304,12 @@ class ConfiguratorTests(unittest.TestCase): def test_make_wsgi_app(self): from repoze.bfg.router import Router from repoze.bfg.interfaces import IWSGIApplicationCreatedEvent - class ThreadLocalManager(object): - def push(self, d): - self.pushed = d - def pop(self): - self.popped = True - manager = ThreadLocalManager() + manager = DummyThreadLocalManager() config = self._makeOne() subscriber = self._registerEventListener(config, IWSGIApplicationCreatedEvent) - app = config.make_wsgi_app(manager=manager) + config.manager = manager + app = config.make_wsgi_app() self.assertEqual(app.__class__, Router) self.assertEqual(manager.pushed['registry'], config.registry) self.assertEqual(manager.pushed['request'], None) @@ -2702,6 +2728,12 @@ class DummyConfigurator(object): self.package = package self.settings = settings + def begin(self): + self.begun = True + + def end(self): + self.ended = True + def load_zcml(self, filename): self.zcml_file = filename @@ -2738,5 +2770,11 @@ class DummyMultiView: class DummyGetSiteManager(object): def sethook(self, hook): self.hook = hook - +class DummyThreadLocalManager(object): + pushed = None + popped = False + def push(self, d): + self.pushed = d + def pop(self): + self.popped = True diff --git a/repoze/bfg/tests/test_integration.py b/repoze/bfg/tests/test_integration.py index d210030af..da866f8d0 100644 --- a/repoze/bfg/tests/test_integration.py +++ b/repoze/bfg/tests/test_integration.py @@ -70,7 +70,7 @@ class TestFixtureApp(unittest.TestCase): config = Configurator() config.load_zcml('repoze.bfg.tests.fixtureapp:configure.zcml') twill.add_wsgi_intercept('localhost', 6543, config.make_wsgi_app) - if sys.platform is 'win32': + if sys.platform is 'win32': # pragma: no cover out = open('nul:', 'wb') else: out = open('/dev/null', 'wb') diff --git a/repoze/bfg/tests/test_router.py b/repoze/bfg/tests/test_router.py index 5352c6d79..c1d60ae9a 100644 --- a/repoze/bfg/tests/test_router.py +++ b/repoze/bfg/tests/test_router.py @@ -158,6 +158,62 @@ class TestRouter(unittest.TestCase): self.failIf('debug_notfound' in result[0]) self.assertEqual(len(logger.messages), 0) + def test_traverser_raises_notfound_class(self): + from repoze.bfg.exceptions import NotFound + environ = self._makeEnviron() + context = DummyContext() + self._registerTraverserFactory(context, raise_error=NotFound) + router = self._makeOne() + start_response = DummyStartResponse() + result = router(environ, start_response) + headers = start_response.headers + self.assertEqual(len(headers), 2) + status = start_response.status + self.assertEqual(status, '404 Not Found') + self.failUnless('<code></code>' in result[0], result) + + def test_traverser_raises_notfound_instance(self): + from repoze.bfg.exceptions import NotFound + environ = self._makeEnviron() + context = DummyContext() + self._registerTraverserFactory(context, raise_error=NotFound('foo')) + router = self._makeOne() + start_response = DummyStartResponse() + result = router(environ, start_response) + headers = start_response.headers + self.assertEqual(len(headers), 2) + status = start_response.status + self.assertEqual(status, '404 Not Found') + self.failUnless('<code>foo</code>' in result[0], result) + + def test_traverser_raises_forbidden_class(self): + from repoze.bfg.exceptions import Forbidden + environ = self._makeEnviron() + context = DummyContext() + self._registerTraverserFactory(context, raise_error=Forbidden) + router = self._makeOne() + start_response = DummyStartResponse() + result = router(environ, start_response) + headers = start_response.headers + self.assertEqual(len(headers), 2) + status = start_response.status + self.assertEqual(status, '401 Unauthorized') + self.failUnless('<code></code>' in result[0], result) + + def test_traverser_raises_forbidden_instance(self): + from repoze.bfg.exceptions import Forbidden + environ = self._makeEnviron() + context = DummyContext() + self._registerTraverserFactory(context, raise_error=Forbidden('foo')) + router = self._makeOne() + start_response = DummyStartResponse() + result = router(environ, start_response) + headers = start_response.headers + self.assertEqual(len(headers), 2) + status = start_response.status + self.assertEqual(status, '401 Unauthorized') + self.failUnless('<code>foo</code>' in result[0], result) + def test_call_no_view_registered_no_isettings(self): environ = self._makeEnviron() context = DummyContext() |
