Hi all,

I just merged a branch I've been working on for some time to the Pyramid
master branch on Github.  The branch was named "twophase", and its goal
was to provide imperative configuration extensibility and two-phase
configuration much like that currently offered by ZCML.

As a result, the ``pyramid.configuration`` module has been deprecated
and exists now only to support backwards compatibility.  In its place a
new ``pyramid.config`` module exists with fundamentally the same API.
Paster template authors beware.

What does this change get us?  Imperative extensibility was the primary
goal (the ability to write "extensible" applications by including
configuration from other non-local sources).  This was always a feature
offered to us by ZCML, but now folks don't have to use ZCML to get it.

Here's an example of using imperative configuration to include
configuration from a non-local source.  If the ``configure`` function
below lives in a module named ``myapp.myconfig``:

     # myapp.myconfig module

     def my_view(request):
         from pyramid.response import Response
         return Response('OK')

      def configure(config):
          config.add_view(my_view)

You might cause it be included within your Pyramid application like so:

     from pyramid.config import Configurator

     def main(global_config, **settings):
         config = Configurator()
         config.include('myapp.myconfig.configure')
         return config.make_wsgi_app()

When application extensibility of this kind is used, often configuration
conflicts are an issue.  When you reuse some configuration from another
module, it's more likely that the configuration in that module will
conflict with configuration statements you execute "locally" or other
configuration statements executed as the result of including some other
configuration.  For this reason, *configuration conflict detection* is
now a feature by default.

Included configuration statements will be overridden by local
configuration statements if an included callable causes a configuration
conflict by registering something with the same configuration
parameters.  So for instance, in the below, the local "add_view" call
will "win" even though both views are registered for the same thing:

     from pyramid.config import Configurator

     def someview(request):
         from pyramid.response import Response
         return Response('OK')

     def main(global_config, **settings):
         config = Configurator()
         config.add_view(someview)
         config.include('myapp.myconfig.configure')
         return config.make_wsgi_app()

However, if two *included* configuration callables register the same
configuration parameters, a ConfigurationConflictError will now occur.
For example:

     # myapp.myconfig module

     def my_view(request):
         from pyramid.response import Response
         return Response('OK')

      def configure(config):
          config.add_view(my_view)

     # myapp.myconfig2 module

     def my_view(request):
         from pyramid.response import Response
         return Response('OK')

      def configure(config):
          config.add_view(my_view)

     # your application's __init__

     from pyramid.config import Configurator

     def main(global_config, **settings):
         config = Configurator()
         config.include('myapp.myconfig.configure')
         config.include('myapp.myconfig2.configure')
         return config.make_wsgi_app()

When ``make_wsgi_app`` is called, the configuration will be "committed",
and a conflict will be detected, because both myapp.myconfig.configure
and myapp.myconfig2.configure registered a view with the same
configuration parameters.  To avoid a conflict, and make
"myapp.myconfig2.configure" "win", you can call "commit" between the
calls to "include":

     # your application's __init__

     from pyramid.config import Configurator

     def main(global_config, **settings):
         config = Configurator()
         config.include('myapp.myconfig.configure')
         config.commit()
         config.include('myapp.myconfig2.configure')
         return config.make_wsgi_app()

Calling "commit" executes all pending configuration and clears the
configuration stack.  It is safe to call commit() at any time.
"make_wsgi_app" calls commit on your behalf usually, but you can issue
commits at any time.

A conflict error will now occur if you register two configuration
statements for the same set of arguments locally too:

     from pyramid.config import Configurator

     def someview(request):
         from pyramid.response import Response
         return Response('OK')

     def someotherview(request):
         from pyramid.response import Response
         return Response('OK')

     def main(global_config, **settings):
         config = Configurator()
         config.add_view(someview)
         config.add_view(someotherview)
         return config.make_wsgi_app()

Because both calls to "add_view" above are registered for the same set
of circumstances, a ConfigurationConflictError will occur when
"make_wsgi_app" is run.  To avoid this, use commit between those calls:

     from pyramid.config import Configurator

     def someview(request):
         from pyramid.response import Response
         return Response('OK')

     def someotherview(request):
         from pyramid.response import Response
         return Response('OK')

     def main(global_config, **settings):
         config = Configurator()
         config.add_view(someview)
         config.commit()
         config.add_view(someotherview)
         return config.make_wsgi_app()

By default, the pyramid.config.Configurator behaves like this, deferring
configuration actions until "commit" or "make_wsgi_app" is called.  If
you want the older behavior back (no conflicts, every configuration
method executes immediately, later configuration statements override
earlier ones), you can use an "autocommitting" configurator:

     from pyramid.config import Configurator

     def someview(request):
         from pyramid.response import Response
         return Response('OK')

     def someotherview(request):
         from pyramid.response import Response
         return Response('OK')

     def main(global_config, **settings):
         config = Configurator(autocommit=True)
         config.add_view(someview)
         config.add_view(someotherview)
         return config.make_wsgi_app()

The "someotherview" registered above will "win" in this circumstance.

I've used "add_view" in all the examples above, but all methods of the
configurator make use of this feature (add_route, add_handler,
set_session_factory, etc).

If you can imagine a future where folks factor reusable configuration
into these configuration callables, and those callables are included by
your application, you can imagine a future where applications can be
composed of other applications more cleanly than today.  This isn't
exactly a "reusable application" story (reusable applications require
more "rails" than just this can provide), but it does allow
configuration to live cleanly outside of the main() function, and allows
for composability of multiple applications written by the same person or
organization fairly cleanly without ZCML.

- C


-- 
You received this message because you are subscribed to the Google Groups 
"pylons-devel" group.
To post to this group, send email to pylons-de...@googlegroups.com.
To unsubscribe from this group, send email to 
pylons-devel+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/pylons-devel?hl=en.

Reply via email to