diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-12-17 16:00:02 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-12-17 16:00:02 +0000 |
| commit | bc857e7e6e71a4001f03c608a18bac7dab36ccff (patch) | |
| tree | a34b0b761a92791bb60b2fbfafabe79e4a675682 | |
| parent | 9d73300fcef0c0cd4af9c439a900d15fa4651914 (diff) | |
| download | pyramid-bc857e7e6e71a4001f03c608a18bac7dab36ccff.tar.gz pyramid-bc857e7e6e71a4001f03c608a18bac7dab36ccff.tar.bz2 pyramid-bc857e7e6e71a4001f03c608a18bac7dab36ccff.zip | |
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.
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() |
