summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2009-12-17 16:00:02 +0000
committerChris McDonough <chrism@agendaless.com>2009-12-17 16:00:02 +0000
commitbc857e7e6e71a4001f03c608a18bac7dab36ccff (patch)
treea34b0b761a92791bb60b2fbfafabe79e4a675682
parent9d73300fcef0c0cd4af9c439a900d15fa4651914 (diff)
downloadpyramid-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.
-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()