What's New In :mod:`repoze.bfg` 1.2 =================================== This article explains the new features in :mod:`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. Major Feature Additions ----------------------- The major feature addition in 1.2 is an :term:`imperative configuration` mode. This implies: - A :mod:`repoze.bfg` application can now be contained within a single Python file. - Developers are no longer required to use or understand :term:`ZCML` to create a :mod:`repoze.bfg` application (ZCML is, however still supported). The simplest possible :mod:`repoze.bfg` 1.2 application is: .. code-block:: python :linenos: 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 :mod:`repoze.bfg` competitive with "microframeworks" such as `Bottle `_ and `Tornado `_. :mod:`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 :ref:`configuration_narr`. Minor Feature Additions ----------------------- - 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 :func:`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 :func:`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 :func:`repoze.bfg.threadlocal.get_current_request`) for the duration of the test; it defaults to ``None``. If ``hook_zca`` is ``True`` (the default), the :func:`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 :func:`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 :func:`repoze.bfg.testing.tearDown` function now accepts an ``unhook_zca`` argument. If this argument is ``True`` (the default), :func:`zope.component.getSiteManager.reset` will be called. This will cause the result of the :func:`zope.component.getSiteManager` function to be the global ZCA registry (the result of :func:`zope.component.getGlobalSiteManager`) once again. - When the :exc:`repoze.bfg.exceptions.NotFound` or :exc:`repoze.bfg.exceptions.Forbidden` error is raised from within a custom :term:`root factory` or the factory of a :term:`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 :term:`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. - :class:`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 :func:`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". Backwards Incompatibilites -------------------------- - Unit tests which use :func:`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 :func:`repoze.bfg.threadlocal.get_current_registry` when no other registry had been pushed on to the threadlocal stack was the :data:`zope.component.globalregistry.base` global registry (aka the result of :func:`zope.component.getGlobalSiteManager()`). In :mod:`repoze.bfg` 1.2+, however, the registry returned in this situation is the new module-scope :data:`repoze.bfg.registry.global_registry` object. The :func:`zope.testing.cleanup.cleanUp` function clears the :data:`zope.component.globalregistry.base` global registry unconditionally. However, it does not know about the :data:`repoze.bfg.registry.global_registry` object, so it does not clear it. If you use the :func:`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) :func:`repoze.bfg.testing.setUp`, you will need to replace all calls to :func:`zope.testing.cleanup.cleanUp` with a call to :func:`repoze.bfg.testing.setUp`. If replacing all calls to :func:`zope.testing.cleanup.cleanUp` with a call to :func:`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 :data:`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 :func:`repoze.bfg.testing.setUp` or :meth:`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 :func:`zope.component.getGlobalSiteManager`, which is an instance of the :class:`zope.component.registry.Component` class. In this release, however, the global registry returns a globally importable instance of the :class:`repoze.bfg.registry.Registry` class. This registry instance can always be imported as :data:`repoze.bfg.registry.global_registry`. Effectively, this means that when you call :func:`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 :data:`repoze.bfg.registry.global_registry`. It also means that :mod:`repoze.bfg` APIs that *call* :func:`repoze.bfg.threadlocal.get_current_registry` will use this registry. This change was made because :mod:`repoze.bfg` now expects the registry it uses to have a slightly different API than a bare instance of :class:`zope.component.registry.Components`. - View registration no longer registers a :class:`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 :func:`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 ```` 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 :mod:`repoze.bfg.includes` ZCML to include this file in the past; it now just doesn't. - The :func:`repoze.bfg.testing.zcml_configure` API was removed. Use the :meth:`repoze.bfg.configuration.Configurator.load_zcml` API instead. - The :mod:`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 :meth:`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. Deprecations and Behavior Differences ------------------------------------- - If you disuse the legacy :func:`repoze.bfg.router.make_app` function in favor of :meth:`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 :func:`repoze.bfg.router.make_app` function does this on your behalf for backward compatibility purposes. - The :func:`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 :class:`repoze.bfg.configuration.Configurator` class is now the preferred way to generate a WSGI application. Note that :func:`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). Dependency Changes ------------------ - A dependency on the ``martian`` package has been removed (its functionality is replaced internally). - A dependency on the ``repoze.zcml`` package has been removed (its functionality is replaced internally). - A dependency on the ``sourcecodegen`` package has been removed. - :mod:`repoze.bfg` now depends on :term:`WebOb` 0.9.7 or better. - Doc-deprecated most helper functions in the ``repoze.bfg.testing`` module. These helper functions likely won't be removed any time soon, nor will they generate a warning any time soon, due to their heavy use in the wild, but equivalent behavior exists in methods of a :class:`repoze.bfg.configuration.Configurator`. Documentation Enhancements -------------------------- - This documentation has been published in `printed form `_ under the title *The repoze.bfg Web Application Framework, Version 1.2*. It was reworked and restructured extensively for this publication. `Show your support for BFG by buying a copy! `_. - The documentation now uses the "request-only" view calling convention in most examples (as opposed to the ``context, request`` convention). This is a documentation-only change; the ``context, request`` convention is also supported and documented, and will be "forever". - :mod:`repoze.bfg.configuration` API documentation has been added. - A narrative documentation chapter entitled "Creating Your First ``repoze.bfg`` Application" has been added. This chapter details usage of the new :class:`repoze.bfg.configuration.Configurator` class, and demonstrates a simplified "imperative-mode" configuration; doing :mod:`repoze.bfg` application configuration imperatively was previously much more difficult. - The "ZCML Hooks" chapter has been renamed to "Hooks" (:ref:`hooks_chapter`); it documents how to override hooks now via imperative configuration and ZCML. - The explanation about how to supply an alternate "response factory" has been removed from the "Hooks" chapter. This feature may be removed in a later release (it still works now, it's just not documented). - Add a section entitled "Test Set Up and Tear Down" to the unittesting chapter (:ref:`unittesting_chapter`). - Remove explanation of changing the request type in a new request event subscriber in the "Events" narrative documentation chapter, as other predicates are now usually an easier way to get this done. - Added "Thread Locals" narrative chapter to documentation, and added a API chapter documenting the :mod:`repoze.bfg.threadlocals` module. - Added a "Special Exceptions" section to the "Views" narrative documentation chapter explaining the effect of raising :exc:`repoze.bfg.exceptions.NotFound` and :exc:`repoze.bfg.exceptions.Forbidden` from within view code. - Add a narrative documentation chapter named :ref:`zca_chapter`. - Created new top-level documentation section named :ref:`zcml_directives`. This section contains detailed ZCML directive information, some of which was removed from various narrative chapters. Licensing Changes ----------------- - The publication of the documentation as a printed book required an explicit documentation licensing change: the documentation (the content in the "docs" directory) is now offered under the `Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License `_. This is only a documentation licensing change: the :mod:`repoze.bfg` software itself (everything except the contents of the "docs" directory) is still offered under `a BSD-like license `_.