Yesterday I blogged about the view predicates feature of repoze.bfg 1.1 . BFG 1.1 was released several months ago, and since that time, we've had a good number of alpha releases of the subsequent major release, 1.2. The most recent version in the 1.2 line as of this writing is 1.2a9. 1.2 final is due out within the next few weeks.
The most important new feature in the BFG 1.2 line is "imperative configuration". "Imperative configuration" is a funny, loaded term so let's take it one word at a time.
Imperative: this term contrasts a requirement in previous releases that configuration be at least partially declarative. In BFG releases prior to 1.2, it was necessary to have at least two files for any given application: a Python file representing the code and a ZCML file.
Configuration: "configuration" in terms of BFG means the stuff that wires up specific URLs to specific views, or that wires up up event subscribers to event emissions, etc. The stuff that turns the BFG framework into a particular application deployment.
The declarative configuration "story" provided by BFG is stolen almost
entirely from Zope. The
zope.configuration package is used to
process declarations made in ZCML files. These declarations mutate an
"application registry", adding various registrations as the ZCML files
are processed. At the end of processing, the application registry
represents all the stuff needed to run a particular application.
In BFG 1.0 and 1.1, you were required to have at least one ZCML file
per application. This ZCML file, in turn, needed to contain at least
one statement in it: one which kicked off a "scan", which is a process
that scans a package or module for view configuration decorators.
Alternately, you were permitted to disuse view decorators entirely and
configure view mappings via
The requirement that a ZCML file exist in 1.0 and 1.1 was a holdover from BFG's Zope heritage. In BFG 1.2+, it is possible to create an application entirely in Python without any ZCML (or even any decorators) in sight. For example:
from webob import Response from paste.httpserver import serve from repoze.bfg.configuration import Configurator def hello_world(request): return Response('Hello world!') def goodbye_world(request): return Response('Goodbye world!') 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)
If you put this stuff in a Python file, and invoke it with an
interpreter that has the
repoze.bfg software installed, you'll get a
running helloword application. The
add_view method of the
configurator does effectively the same thing as a ZCML
declaration; in fact the ZCML view directive code calls into this method
when a ZCML
<view> declaration is found.
While it's sort of neat to be able to run an application as a single
file, it is sort of a "gee whiz" feature that isn't very useful in
practice. Every sizeable application will eventually need to split
itself across multiple files. But the act of making this possible
really helped me clean up the code a lot; rather than coding for
configurability via ZCML, and then adding weird sort of stubs for
testability, the Configurator can be used to configure both the
application and test setup for an application in exactly the same
way. Likewise, lots and lots of code got centralized into the
configuration module (a lot removed from code that drove various
ZCML directives), and we were able to document the resulting
much more effectively and cleanly.
The end result? Well, now, truly, if you don't want to use ZCML, you needn't. Ever. But even so we didn't wind up with a system that leads you towards making a false choice between "convention over configuration" and ZCML: both the declarative and the imperative configuration modes are still extremely explicit (they are really mirror copies of each other, as the declarative code uses the imperative API). The API used by imperative configurators is not "bare" ZCA registrations: it's just more domain specific, like helper ZCML directives in Zope. And hopefully we've brought down to earth the kinds of configuration tasks you can potentially perform, because they're now all enumerated in the Configurator API documentation and via various narrative documentation chapters. All of the older declarative/ZCML configuration still works as it did in 1.0/1.1, so there's no backwards compatibility concerns either. ZCML will always be a reasonable way to configure a BFG app.
I think 1.2 is the best of the bunch as far as BFG releases go, because, although BFG still has an obvious Zope heritage, 1.2 unglosses over some of the things that Zope has been glossing over for a long time, such as the relationship from the code in a ZCML directive to code that needs to be invoked imperatively. BFG also eschews some dogma-driven assumptions Zope has been making for a long time. When I say this I am reminded of this passage from the ZCML chapter of Stephan Richter's Zope 3 Developer's Handbook :
While the developer is certainly the one that writes the initial cut of the configuration, this user is not the real target audience. Once the product is written, you would expect a system administrator to interact much more frequently with the configuration, adding and removing functionality or adjust the configuration of the server setup. System administrators are often not developers, so that it would be unfortunate to write the configuration in the programming language, here Python. But an administrator is familiar with configuration scripts, shell code and XML to some extend. Therefore an easy to read syntax that is similar to other configuration files is of advantage.
I think it's safe to say that this assumption has turned out to be just false. Sysadmins never change ZCML files. I have never, ever, not once in the last seven years, had a sysadmin with enough context that allowed him to change a ZCML file in any meaningful way. Only developers change ZCML files. So let's just un-dogma-ify this assumption, and put programmers back in charge to whatever extent they'd like to be. Demystification of this stuff is the only reasonable way forward, even if it means admitting we made really big design mistakes in the past.