This article explains the new features in repoze.bfg version 1.2 as compared to the previous 1.1 release. It also documents backwards incompatibilities between the two versions and deprecations added to 1.2, as well as software dependency changes and notable documentation additions.
The major feature addition in 1.2 is an imperative configuration mode. This implies:
The simplest possible repoze.bfg 1.2 application is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from webob import Response from paste.httpserver import serve from repoze.bfg.configuration import Configurator def hello_world(request): return Response('Hello world!') if __name__ == '__main__': config = Configurator() config.begin() config.add_view(hello_world) config.end() app = config.make_wsgi_app() serve(app)
This feature tends to make repoze.bfg competitive with “microframeworks” such as Bottle and Tornado. repoze.bfg has a good deal of functionality that most microframeworks lack, so this is hopefully a “best of both worlds” feature.
Imperative configuration is also useful while writing tests.
For an introduction to imperative configuration mode, see Application Configuration.
Jython compatibility (at least when repoze.bfg.jinja2 is used as the templating engine; Chameleon does not work under Jython).
Read logging configuration from PasteDeploy config file loggers section (and related) when paster bfgshell is invoked.
The AuthTktAuthenticationPolicy now accepts two additional constructor arguments: path and http_only.
The repoze.bfg.testing.setUp() function now accepts three extra optional keyword arguments: registry, request and hook_zca.
If the registry argument is not None, the argument will be treated as the registry that is set as the “current registry” (it will be returned by repoze.bfg.threadlocal.get_current_registry()) for the duration of the test. If the registry argument is None (the default), a new registry is created and used for the duration of the test.
The value of the request argument is used as the “current request” (it will be returned by repoze.bfg.threadlocal.get_current_request()) for the duration of the test; it defaults to None.
If hook_zca is True (the default), the zope.component.getSiteManager() function will be hooked with a function that returns the value of registry (or the default-created registry if registry is None) instead of the registry returned by zope.component.getGlobalSiteManager(), causing the Zope Component Architecture API (getSiteManager, getAdapter, getUtility, and so on) to use the testing registry instead of the global ZCA registry.
The repoze.bfg.testing.tearDown() function now accepts an unhook_zca argument. If this argument is True (the default), zope.component.getSiteManager.reset() will be called. This will cause the result of the zope.component.getSiteManager() function to be the global ZCA registry (the result of zope.component.getGlobalSiteManager()) once again.
When the repoze.bfg.exceptions.NotFound or repoze.bfg.exceptions.Forbidden error is raised from within a custom root factory or the factory of a route, the appropriate response is sent to the requesting user agent (the result of the notfound view or the forbidden view, respectively). When these errors are raised from within a root factory, the context passed to the notfound or forbidden view will be None. Also, the request will not be decorated with view_name, subpath, context, etc. as would normally be the case if traversal had been allowed to take place.
repoze.bfg.testing.DummyModel now accepts a new constructor keyword argument: __provides__. If this constructor argument is provided, it should be an interface or a tuple of interfaces. The resulting model will then provide these interfaces (they will be attached to the constructed model via zope.interface.alsoProvides()).
Read logging configuration from PasteDeploy config file loggers section (and related) when paster bfgshell is invoked.
View registration via ZCML now accepts an attribute named context. This is an alias for the older argument named for; it is preferred over for, but for will continue to be supported “forever”.
Unit tests which use zope.testing.cleanup.cleanUp() for the purpose of isolating tests from one another may now begin to fail due to lack of isolation between tests.
Here’s why: In repoze.bfg 1.1 and prior, the registry returned by repoze.bfg.threadlocal.get_current_registry() when no other registry had been pushed on to the threadlocal stack was the zope.component.globalregistry.base global registry (aka the result of zope.component.getGlobalSiteManager()). In repoze.bfg 1.2+, however, the registry returned in this situation is the new module-scope repoze.bfg.registry.global_registry object. The zope.testing.cleanup.cleanUp() function clears the zope.component.globalregistry.base global registry unconditionally. However, it does not know about the repoze.bfg.registry.global_registry object, so it does not clear it.
If you use the zope.testing.cleanup.cleanUp() function in the setUp of test cases in your unit test suite instead of using the (more correct as of 1.1) repoze.bfg.testing.setUp(), you will need to replace all calls to zope.testing.cleanup.cleanUp() with a call to repoze.bfg.testing.setUp().
If replacing all calls to zope.testing.cleanup.cleanUp() with a call to repoze.bfg.testing.setUp() is infeasible, you can put the below-mentioned bit of code somewhere that is executed exactly once (not for each test in a test suite). Placing this in the __init__.py of your package or the __init__.py of a tests subpackage would be a reasonable place):
import zope.testing.cleanup
from repoze.bfg.testing import setUp
zope.testing.cleanup.addCleanUp(setUp)
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 repoze.bfg.testing.setUp() or repoze.bfg.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 of the repoze.bfg.registry.Registry class. This registry instance can always be imported as repoze.bfg.registry.global_registry.
Effectively, this means that when you call 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 repoze.bfg APIs that call repoze.bfg.threadlocal.get_current_registry() will use this registry.
This change was made because repoze.bfg now expects the registry it uses to have a slightly different API than a bare instance of zope.component.registry.Components.
View registration no longer registers a repoze.bfg.interfaces.IViewPermission adapter (it is no longer checked by the framework; since 1.1, views have been responsible for providing their own security).
The repoze.bfg.router.make_app() callable no longer accepts an authentication_policy nor an authorization_policy argument. These features were deprecated in version 1.0 and have been removed.
The route ZCML directive now no longer accepts the request_type, view_request_type, view_header, view_accept, view_xhr, view_path_info, view_request_method, view_request_param, or view_containment attributes. If you need the features exposed by these attributes, add a view associated with a route using the route_name attribute of the view ZCML directive instead.
Because the repoze.bfg package now includes implementations of the adapter, subscriber and utility ZCML directives, it is now an error to have <include package="repoze.zcml" file="meta.zcml"/> in the ZCML of a repoze.bfg application. A ZCML conflict error will be raised if your ZCML does so. This shouldn’t be an issue for “normal” installations; it has always been the responsibility of the repoze.bfg.includes ZCML to include this file in the past; it now just doesn’t.
The repoze.bfg.testing.zcml_configure() API was removed. Use the repoze.bfg.configuration.Configurator.load_zcml() API instead.
The repoze.bfg.templating module has been removed; it had been deprecated in 1.1 and hasn’t possessed any APIs since before 1.0.
Remove magical feature of repoze.bfg.url.model_url() which prepended a fully-expanded urldispatch route URL before a the model’s path if it was noticed that the request had matched a route. This feature was ill-conceived, and didn’t work in all scenarios.
When “hybrid mode” (both traversal and urldispatch) is in use, default to finding route-related views even if a non-route-related view registration has been made with a more specific context. The default used to be to find views with a more specific context first. Use the new use_global_views argument to the route definition to get back the older behavior.
If you disuse the legacy repoze.bfg.router.make_app() function in favor of repoze.bfg.configuration.Configurator.make_wsgi_app(), and you also want to use the “global” ZCA API (getUtility, getAdapter, getSiteManager, etc), you will need to “hook” the ZCA before calling methods of the configurator using the sethook method of the getSiteManager API, e.g.:
from zope.component import getSiteManager
from repoze.bfg.configuration import Configurator
from repoze.bfg.threadlocal import get_current_registry
from mypackage.models import get_root
def app(global_config, **settings):
config = Configurator(root_factory=get_root, settings=settings)
getSiteManager.sethook(get_current_registry)
zcml_file = settings.get('configure_zcml', 'configure.zcml')
config.load_zcml(zcml_file)
return config.make_wsgi_app()
The repoze.bfg.router.make_app() function does this on your behalf for backward compatibility purposes.
The repoze.bfg.router.make_app() function is now nominally deprecated. Its import and usage does not throw a warning, nor will it probably ever disappear. However, using a repoze.bfg.configuration.Configurator class is now the preferred way to generate a WSGI application.
Note that repoze.bfg.router.make_app() calls zope.component.getSiteManager.sethook( repoze.bfg.threadlocal.get_current_registry) on the caller’s behalf, hooking ZCA global API lookups, for backwards compatibility purposes. If you disuse make_app, your calling code will need to perform this call itself, at least if your application uses the ZCA global API (getSiteManager, getAdapter, etc).