summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt55
-rw-r--r--docs/api/configuration.rst4
-rw-r--r--docs/glossary.rst12
-rw-r--r--docs/narr/MyProject/myproject/run.py2
-rw-r--r--docs/narr/MyProject/myproject/tests.py6
-rw-r--r--docs/narr/configuration.rst67
-rw-r--r--docs/narr/project.rst2
-rw-r--r--docs/narr/threadlocals.rst14
-rw-r--r--docs/narr/unittesting.rst122
-rw-r--r--docs/tutorials/bfgwiki/src/authorization/tutorial/run.py2
-rw-r--r--docs/tutorials/bfgwiki/src/authorization/tutorial/tests.py2
-rw-r--r--docs/tutorials/bfgwiki/src/basiclayout/tutorial/run.py2
-rw-r--r--docs/tutorials/bfgwiki/src/basiclayout/tutorial/tests.py6
-rw-r--r--docs/tutorials/bfgwiki/src/models/tutorial/run.py2
-rw-r--r--docs/tutorials/bfgwiki/src/models/tutorial/tests.py6
-rw-r--r--docs/tutorials/bfgwiki/src/viewdecorators/tutorial/run.py2
-rw-r--r--docs/tutorials/bfgwiki/src/viewdecorators/tutorial/tests.py2
-rw-r--r--docs/tutorials/bfgwiki/src/views/tutorial/run.py2
-rw-r--r--docs/tutorials/bfgwiki/src/views/tutorial/tests.py2
-rw-r--r--docs/tutorials/bfgwiki2/src/authorization/tutorial/run.py2
-rw-r--r--docs/tutorials/bfgwiki2/src/authorization/tutorial/tests.py44
-rw-r--r--docs/tutorials/bfgwiki2/src/basiclayout/tutorial/run.py2
-rw-r--r--docs/tutorials/bfgwiki2/src/basiclayout/tutorial/tests.py6
-rw-r--r--docs/tutorials/bfgwiki2/src/models/tutorial/run.py2
-rw-r--r--docs/tutorials/bfgwiki2/src/models/tutorial/tests.py6
-rw-r--r--docs/tutorials/bfgwiki2/src/views/tutorial/run.py2
-rw-r--r--docs/tutorials/bfgwiki2/src/views/tutorial/tests.py44
-rw-r--r--docs/whatsnew-1.2.rst15
-rw-r--r--repoze/bfg/chameleon_text.py2
-rw-r--r--repoze/bfg/configuration.py31
-rw-r--r--repoze/bfg/paster_templates/alchemy/+package+/run.py_tmpl4
-rw-r--r--repoze/bfg/paster_templates/routesalchemy/+package+/run.py_tmpl4
-rw-r--r--repoze/bfg/paster_templates/routesalchemy/+package+/tests.py_tmpl6
-rw-r--r--repoze/bfg/paster_templates/starter/+package+/run.py_tmpl4
-rw-r--r--repoze/bfg/paster_templates/starter/+package+/tests.py_tmpl6
-rw-r--r--repoze/bfg/paster_templates/zodb/+package+/run.py_tmpl4
-rw-r--r--repoze/bfg/paster_templates/zodb/+package+/tests.py_tmpl6
-rw-r--r--repoze/bfg/router.py10
-rw-r--r--repoze/bfg/testing.py52
-rw-r--r--repoze/bfg/tests/fixtureapp/views.py3
-rw-r--r--repoze/bfg/tests/test_configuration.py54
-rw-r--r--repoze/bfg/tests/test_integration.py2
-rw-r--r--repoze/bfg/tests/test_router.py56
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()