.. _whirlwind-adv-conf: A Whirlwind Tour of Advanced Pyramid Configuration Tactics ========================================================== Concepts: Configuration, Directives, and Statements ---------------------------------------------------- This article attempts to demonstrate some of Pyramid's more advanced startup-time configuration features. The stuff below talks about "configuration", which is a shorthand word I'll use to mean the state that is changed when a developer adds views, routes, subscribers, and other bits. A developer adds configuration by calling configuration *directives*. For example, ``config.add_route()`` is a configuration directive. A particular *use* of ``config.add_route()`` is a configuration *statement*. In the below code block, the execution of the ``add_route()`` directive is a configuration statement. Configuration statements change pending configuration state:: config = pyramid.config.Configurator() config.add_route('home', '/') Here are a few core concepts related to Pyramid startup configuration: #. Due to the way the configuration statements work, statement ordering is usually irrelevant. For example, calling ``add_view``, then ``add_route`` has the same outcome as calling ``add_route``, then ``add_view``. There are some important exceptions to this, but in general, unless the documentation for a given configuration directive states otherwise, you don't need to care in what order your code adds configuration statements. #. When a configuration statement is executed, it usually doesn't do much configuration immediately. Instead, it generates a *discriminator* and produces a *callback*. The discriminator is a hashable value that represents the configuration statement uniquely amongst all other configuration statements. The callback, when eventually called, actually performs the work related to the configuration statement. Pyramid adds the discriminator and the callback into a list of pending actions that may later be *committed*. #. Pending configuration actions can be committed at any time. At commit time, Pyramid compares each of the discriminators generated by a configuration statement to every other discriminator generated by other configuration statements in the pending actions list. If two or more configuration statements have generated the same discriminator, this is a *conflict*. Pyramid will attempt to resolve the conflict automatically; if it cannot, startup will exit with an error. If all conflicts are resolved, each callback associated with a configuration statement is executed. Per-action sanity-checking is also performed as the result of a commit. #. Pending actions can be committed more than once during startup in order to avoid a configuration state that contains conflicts. This is useful if you need to perform configuration overrides in a brute-force, deployment-specific way. #. An application can be created via configuration statements (for example, calls to ``add_route`` or ``add_view``) composed from logic defined in multiple locations. The configuration statements usually live within Python functions. Those functions can live anywhere, as long as they can be imported. If the ``config.include()`` API is used to stitch these configuration functions together, some configuration conflicts can be automatically resolved. #. Developers can add *directives* which participate in Pyramid's phased configuration process. These directives can be made to work exactly like "built-in" directives like ``add_route`` and ``add_view``. #. Application configuration is never added as the result of someone or something just happening to import a Python module. Adding configuration is always more explicit than that. Let's see some of those concepts in action. Here's one of the simplest possible Pyramid applications:: # app.py from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') if __name__ == '__main__': config = Configurator() config.add_route('home', '/') config.add_view(hello_world, route_name='home') app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() If we run this application via ``python app.py``, we'll get a ``Hello world!`` printed when we visit ``_ in a browser. Not very exciting. What happens when we reorder our configuration statements? We'll change the relative ordering of ``add_view()`` and ``add_route()`` configuration statements. Instead of adding a route, then a view, we'll add a view then a route:: # app.py from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') if __name__ == '__main__': config = Configurator() config.add_view(hello_world, route_name='home') # moved this up config.add_route('home', '/') app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() If you start this application, you'll note that, like before, visiting ``/`` serves up ``Hello world!``. In other words, it works exactly like it did before we switched the ordering around. You might not expect this configuration to work, because we're referencing the name of a route (``home``) within our add_view statement (``config.add_view(hello_world, route_name='home')`` that hasn't been added yet. When we execute ``add_view``, ``add_route('home', '/')`` has not yet been executed. This out-of-order execution works because Pyramid defers configuration execution until a *commit* is performed as the result of ``config.make_wsgi_app()`` being called. Relative ordering between ``config.add_route()`` and ``config.add_view()`` calls is not important. Pyramid implicitly commits the configuration state when ``make_wsgi_app()`` gets called; only when it's committed is the configuration state sanity-checked. In particular, in this case, we're relying on the fact that Pyramid makes sure that all route configuration happens before any view configuration at commit time. If a view references a nonexistent route, an error will be raised at commit time rather than at configuration statement execution time. Sanity Checks ------------- We can see this sanity-checking feature in action in a failure case. Let's change our application, commenting out our call to ``config.add_route()`` temporarily within ``app.py``:: # app.py from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') if __name__ == '__main__': config = Configurator() config.add_view(hello_world, route_name='home') # moved this up # config.add_route('home', '/') # we temporarily commented this line app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() When we attempt to run this Pyramid application, we get a traceback: .. code-block:: text Traceback (most recent call last): File "app.py", line 12, in app = config.make_wsgi_app() File "/home/chrism/projects/pyramid/pyramid/config/__init__.py", line 955, in make_wsgi_app self.commit() File "/home/chrism/projects/pyramid/pyramid/config/__init__.py", line 629, in commit self.action_state.execute_actions(introspector=self.introspector) File "/home/chrism/projects/pyramid/pyramid/config/__init__.py", line 1083, in execute_actions tb) File "/home/chrism/projects/pyramid/pyramid/config/__init__.py", line 1075, in execute_actions callable(*args, **kw) File "/home/chrism/projects/pyramid/pyramid/config/views.py", line 1124, in register route_name) pyramid.exceptions.ConfigurationExecutionError: : No route named home found for view registration in: Line 10 of file app.py: config.add_view(hello_world, route_name='home') It's telling us that we attempted to add a view which references a nonexistent route. Configuration directives sometimes introduce sanity-checking to startup, as demonstrated here. Configuration Conflicts ----------------------- Let's change our application once again. We'll undo our last change, and add a configuration statement that attempts to add another view:: # app.py from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') def hi_world(request): # added return Response('Hi world!') if __name__ == '__main__': config = Configurator() config.add_route('home', '/') config.add_view(hello_world, route_name='home') config.add_view(hi_world, route_name='home') # added app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() If you notice above, we're now calling ``add_view`` twice with two different view callables. Each call to ``add_view`` names the same route name. What happens when we try to run this program now?:: Traceback (most recent call last): File "app.py", line 17, in app = config.make_wsgi_app() File "/home/chrism/projects/pyramid/pyramid/config/__init__.py", line 955, in make_wsgi_app self.commit() File "/home/chrism/projects/pyramid/pyramid/config/__init__.py", line 629, in commit self.action_state.execute_actions(introspector=self.introspector) File "/home/chrism/projects/pyramid/pyramid/config/__init__.py", line 1064, in execute_actions for action in resolveConflicts(self.actions): File "/home/chrism/projects/pyramid/pyramid/config/__init__.py", line 1192, in resolveConflicts raise ConfigurationConflictError(conflicts) pyramid.exceptions.ConfigurationConflictError: Conflicting configuration actions For: ('view', None, '', 'home', 'd41d8cd98f00b204e9800998ecf8427e') Line 14 of file app.py: config.add_view(hello_world, route_name='home') Line 15 of file app.py: config.add_view(hi_world, route_name='home') This traceback is telling us that there was a *configuration conflict* between two configuration statements: the ``add_view`` statement on line 14 of app.py and the ``add_view`` statement on line 15 of app.py. This happens because the *discriminator* generated by ``add_view`` statement on line 14 turned out to be the same as the discriminator generated by the ``add_view`` statement on line 15. The discriminator is printed above the line conflict output: ``For: ('view', None, '', 'home', 'd41d8cd98f00b204e9800998ecf8427e')`` . .. note:: The discriminator itself has to be opaque in order to service all of the use cases required by ``add_view``. It's not really meant to be parsed by a human, and is kinda really printed only for consumption by core Pyramid developers. We may consider changing things in future Pyramid versions so that it doesn't get printed when a conflict exception happens. Why is this exception raised? Pyramid couldn't work out what you wanted to do. You told it to serve up more than one view for exactly the same set of request-time circumstances ("when the route name matches ``home``, serve this view"). This is an impossibility: Pyramid needs to serve one view or the other in this circumstance; it can't serve both. So rather than trying to guess what you meant, Pyramid raises a configuration conflict error and refuses to start. Resolving Conflicts ------------------- Obviously it's necessary to be able to resolve configuration conflicts. Sometimes these conflicts are done by mistake, so they're easy to resolve. You just change the code so that the conflict is no longer present. We can do that pretty easily:: # app.py from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') def hi_world(request): return Response('Hi world!') if __name__ == '__main__': config = Configurator() config.add_route('home', '/') config.add_view(hello_world, route_name='home') config.add_view(hi_world, route_name='home', request_param='use_hi') app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() In the above code, we've gotten rid of the conflict. Now the ``hello_world`` view will be called by default when ``/`` is visited without a query string, but if ``/`` is visted when the URL contains a ``use_hi`` query string, the ``hi_world`` view will be executed instead. In other words, visiting ``/`` in the browser produces ``Hello world!``, but visiting ``/?use_hi=1`` produces ``Hi world!``. There's an alternative way to resolve conflicts that doesn't change the semantics of the code as much. You can issue a ``config.commit()`` statement to flush pending configuration actions before issuing more. To see this in action, let's change our application back to the way it was before we added the ``request_param`` predicate to our second ``add_view`` statement:: # app.py from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') def hi_world(request): # added return Response('Hi world!') if __name__ == '__main__': config = Configurator() config.add_route('home', '/') config.add_view(hello_world, route_name='home') config.add_view(hi_world, route_name='home') # added app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() If we try to run this application as-is, we'll wind up with a configuration conflict error. We can actually sort of brute-force our way around that by adding a manual call to ``commit`` between the two ``add_view`` statements which conflict:: # app.py from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') def hi_world(request): # added return Response('Hi world!') if __name__ == '__main__': config = Configurator() config.add_route('home', '/') config.add_view(hello_world, route_name='home') config.commit() # added config.add_view(hi_world, route_name='home') # added app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() If we run this application, it will start up. And if we visit ``/`` in our browser, we'll see ``Hi world!``. Why doesn't this application throw a configuration conflict error at the time it starts up? Because we flushed the pending configuration action impled by the first call to ``add_view`` by calling ``config.commit()`` explicitly. When we called the ``add_view`` the second time, the discriminator of the first call to ``add_view`` was no longer in the pending actions list to conflict with. The conflict was resolved because the pending actions list got flushed. Why do we see ``Hi world!`` in our browser instead of ``Hello world!``? Because the call to ``config.make_wsgi_app()`` implies a second commit. The second commit caused the second ``add_view`` configuration callback to be called, and this callback overwrote the view configuration added by the first commit. Calling ``config.commit()`` is a brute-force way to resolve configuration conflicts. Including Configuration from Other Modules ------------------------------------------ Now that we have played around a bit with configuration that exists all in the same module, let's add some code to ``app.py`` that causes configuration that lives in another module to be *included*. We do that by adding a call to ``config.include()`` within ``app.py``:: # app.py from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') if __name__ == '__main__': config = Configurator() config.add_route('home', '/') config.add_view(hello_world, route_name='home') config.include('another.moreconfiguration') # added app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() We added the line ``config.include('another.moreconfiguration')`` above. If we try to run the application now, we'll receive a traceback:: Traceback (most recent call last): File "app.py", line 12, in config.include('another') File "/home/chrism/projects/pyramid/pyramid/config/__init__.py", line 744, in include c = self.maybe_dotted(callable) File "/home/chrism/projects/pyramid/pyramid/config/__init__.py", line 844, in maybe_dotted return self.name_resolver.maybe_resolve(dotted) File "/home/chrism/projects/pyramid/pyramid/path.py", line 318, in maybe_resolve return self._resolve(dotted, package) File "/home/chrism/projects/pyramid/pyramid/path.py", line 325, in _resolve return self._zope_dottedname_style(dotted, package) File "/home/chrism/projects/pyramid/pyramid/path.py", line 368, in _zope_dottedname_style found = __import__(used) ImportError: No module named another That's exactly as we expected, because we attempted to *include* a module that doesn't yet exist. Let's add a module named ``another.py`` right next to our ``app.py`` module:: # another.py from pyramid.response import Response def goodbye(request): return Response('Goodbye world!') def moreconfiguration(config): config.add_route('goodbye', '/goodbye') config.add_view(goodbye, route_name='goodbye') Now what happens when we run the application via ``python app.py``? It starts. And, like before, if we visit ``/`` in a browser, it still show ``Hello world!``. But, unlike before, now if we visit ``/goodbye`` in a browser, it will show us ``Goodbye world!``. When we called ``include('another.moreconfiguration')`` within app.py, Pyramid interpreted this call as "please find the function named ``moreconfiguration`` in a module or package named ``another`` and call it with a configurator as the only argument". And that's indeed what happened: the ``moreconfiguration`` function within ``another.py`` was called; it accepted a configurator as its first argument and added a route and a view, which is why we can now visit ``/goodbye`` in the browser and get a response. It's the same effective outcome as if we had issued the ``add_route`` and ``add_view`` statements for the "goodbye" view from within ``app.py``. An application can be created via configuration statements composed from multiple locations. You might be asking yourself at this point "So what?! That's just a function call hidden under an API that resolves a module name to a function. I could just import the moreconfiguration function from ``another`` and call it directly with the configurator!" You're mostly right. However, ``config.include()`` does more than that. Please stick with me, we'll get to it. The :func:`includeme` Convention -------------------------------- Now, let's change our ``app.py`` slightly. We'll change the ``config.include()`` line in ``app.py`` to include a slightly different name:: # app.py from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') if __name__ == '__main__': config = Configurator() config.add_route('home', '/') config.add_view(hello_world, route_name='home') config.include('another') # <-- changed app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() And we'll edit ``another.py``, changing the name of the ``moreconfiguration`` function to ``includeme``:: # another.py from pyramid.response import Response def goodbye(request): return Response('Goodbye world!') def includeme(config): # <-- previously named moreconfiguration config.add_route('goodbye', '/goodbye') config.add_view(goodbye, route_name='goodbye') When we run the application, it works exactly like our last iteration. You can visit ``/`` and ``/goodbye`` and get the exact same results. Why is this so? We didn't tell Pyramid the name of our new ``includeme`` function like we did before for ``moreconfiguration`` by saying ``config.include('another.includeme')``, we just pointed it at the module in which ``includeme`` lived by saying ``config.include('another')``. This is a Pyramid convenience shorthand: if you tell Pyramid to include a Python *module* or *package*, it will assume that you're telling it to include the ``includeme`` function from within that module/package. Effectively, ``config.include('amodule')`` always means ``config.include('amodule.includeme')``. Nested Includes --------------- Something which is included can also do including. Let's add a file named ``yetanother.py`` next to app.py:: # yetanother.py from pyramid.response import Response def whoa(request): return Response('Whoa') def includeme(config): config.add_route('whoa', '/whoa') config.add_view(whoa, route_name='whoa') And let's change our ``another.py`` file to include it:: # another.py from pyramid.response import Response def goodbye(request): return Response('Goodbye world!') def includeme(config): # <-- previously named moreconfiguration config.add_route('goodbye', '/goodbye') config.add_view(goodbye, route_name='goodbye') config.include('yetanother') When we start up this application, we can visit ``/``, ``/goodbye`` and ``/whoa`` and see responses on each. ``app.py`` includes ``another.py`` which includes ``yetanother.py``. You can nest configuration includes within configuration includes ad infinitum. It's turtles all the way down. Automatic Resolution via Includes --------------------------------- As we saw previously, it's relatively easy to manually resolve configuration conflicts that are produced by mistake. But sometimes configuration conflicts are not injected by mistake. Sometimes they're introduced on purpose in the desire to override one configuration statement with another. Pyramid anticipates this need in two ways: by offering automatic conflict resolution via ``config.include()``, and the ability to manually commit configuration before a conflict occurs. Let's change our ``another.py`` to contain a ``hi_world`` view function, and we'll change its ``includeme`` to add that view that should answer when ``/`` is visited:: # another.py from pyramid.response import Response def goodbye(request): return Response('Goodbye world!') def hi_world(request): # added return Response('Hi world!') def includeme(config): config.add_route('goodbye', '/goodbye') config.add_view(goodbye, route_name='goodbye') config.add_view(hi_world, route_name='home') # added When we attempt to start the application, it will start without a conflict error. This is strange, because we have what appears to be the same configuration that caused a conflict error before when all of the same configuration statements were made in ``app.py``. In particular, ``hi_world`` and ``hello_world`` are both being registered as the view that should be called when the ``home`` route is executed. When the application runs, when you visit ``/`` in your browser, you will see ``Hello world!`` (not ``Hi world!``). The registration for the ``hello_world`` view in ``app.py`` "won" over the registration for the ``hi_world`` view in ``another.py``. Here's what's going on: Pyramid was able to automatically *resolve* a conflict for us. Configuration statements which generate the same discriminator will conflict. But if one of those configuration statements was performed as the result of being included "below" the other one, Pyramid will make an assumption: it's assuming that the thing doing the including (``app.py``) wants to *override* configuration statements done in the thing being included (``another.py``). In the above code configuration, even though the discriminator generated by ``config.add_view(hello_world, route_name='home')`` in ``app.py`` conflicts with the discriminator generated by ``config.add_view(hi_world, route_name='home')`` in ``another.py``, Pyramid assumes that the former should override the latter, because ``app.py`` *includes* ``another.py``. Note that the same conflict resolution behavior does not occur if you simply import ``another.includeme`` from within app.py and call it, passing it a ``config`` object. This is why using ``config.include`` is different than just factoring your configuration into functions and arranging to call those functions at startup time directly. Using ``config.include()`` makes automatic conflict resolution work properly. Custom Configuration Directives ------------------------------- A developer needn't satisfy himself with only the directives provided by Pyramid like ``add_route`` and ``add_view``. He can add directives to the Configurator. This makes it easy for him to allow other developers to add application-specific configuration. For example, let's pretend you're creating an extensible application, and you'd like to allow developers to change the "site name" of your application (the site name is used in some web UI somewhere). Let's further pretend you'd like to do this by allowing people to call a ``set_site_name`` directive on the Configurator. This is a bit of a contrived example, because it would probably be a bit easier in this particular case just to use a deployment setting, but humor me for the purpose of this example. Let's change our app.py to look like this:: # app.py from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') if __name__ == '__main__': config = Configurator() config.add_route('home', '/') config.add_view(hello_world, route_name='home') config.include('another') config.set_site_name('foo') app = config.make_wsgi_app() print app.registry.site_name server = make_server('0.0.0.0', 8080, app) server.serve_forever() And change our ``another.py`` to look like this:: # another.py from pyramid.response import Response def goodbye(request): return Response('Goodbye world!') def hi_world(request): return Response('Hi world!') def set_site_name(config, site_name): def callback(): config.registry.site_name = site_name discriminator = ('set_site_name',) config.action(discriminator, callable=callback) def includeme(config): config.add_route('goodbye', '/goodbye') config.add_view(goodbye, route_name='goodbye') config.add_view(hi_world, route_name='home') config.add_directive('set_site_name', set_site_name) When this application runs, you'll see printed to the console ``foo``. You'll notice in the ``app.py`` above, we call ``config.set_site_name``. This is not a Pyramid built-in directive. It was added as the result of the call to ``config.add_directive`` in ``another.includeme``. We added a function that uses the ``config.action`` method to register a discriminator and a callback for a *custom* directive. Let's change ``app.py`` again, adding a second call to ``set_site_name``:: # app.py from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') if __name__ == '__main__': config = Configurator() config.add_route('home', '/') config.add_view(hello_world, route_name='home') config.include('another') config.set_site_name('foo') config.set_site_name('bar') # added this app = config.make_wsgi_app() print app.registry.site_name server = make_server('0.0.0.0', 8080, app) server.serve_forever() When we try to start the application, we'll get this traceback:: Traceback (most recent call last): File "app.py", line 15, in app = config.make_wsgi_app() File "/home/chrism/projects/pyramid/pyramid/config/__init__.py", line 955, in make_wsgi_app self.commit() File "/home/chrism/projects/pyramid/pyramid/config/__init__.py", line 629, in commit self.action_state.execute_actions(introspector=self.introspector) File "/home/chrism/projects/pyramid/pyramid/config/__init__.py", line 1064, in execute_actions for action in resolveConflicts(self.actions): File "/home/chrism/projects/pyramid/pyramid/config/__init__.py", line 1192, in resolveConflicts raise ConfigurationConflictError(conflicts) pyramid.exceptions.ConfigurationConflictError: Conflicting configuration actions For: ('site-name',) Line 13 of file app.py: config.set_site_name('foo') Line 14 of file app.py: config.set_site_name('bar') We added a custom directive that made use of Pyramid's configuration conflict detection. When we tried to set the site name twice, Pyramid detected a conflict and told us. Just like built-in directives, Pyramid custom directives will also participate in automatic conflict resolution. Let's see that in action by moving our first call to ``set_site_name`` into another included function. As a result, our ``app.py`` will look like this:: # app.py from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') def moarconfig(config): config.set_site_name('foo') if __name__ == '__main__': config = Configurator() config.add_route('home', '/') config.add_view(hello_world, route_name='home') config.include('another') config.include('.moarconfig') config.set_site_name('bar') app = config.make_wsgi_app() print app.registry.site_name server = make_server('0.0.0.0', 8080, app) server.serve_forever() If we start this application up, we'll see ``bar`` printed to the console. No conflict will be raised, even though we have two calls to ``set_site_name`` being executed. This is because our custom directive is making use of automatic conflict resolution: Pyramid determines that the call to ``set_site_name('bar')`` should "win" because it's "closer to the top of the application" than the other call which sets it to "bar". Why This Is Great ----------------- Now for some general descriptions of what makes the way all of this works great. You'll note that a mere import of a module in our tiny application doesn't cause any sort of configuration state to be added, nor do any of our existing modules rely on some configuration having occurred before they can be imported. Application configuration is never added as the result of someone or something just happening to import a module. This seems like an obvious design choice, but it's not true of all web frameworks. Some web frameworks rely on a particular import ordering: you might not be able to successfully import your application code until some other module has been initialized via an import. Some web frameworks depend on configuration happening as a side effect of decorator execution: as a result, you might be *required* to import all of your application's modules for it to be configured in its entirety. Our application relies on neither: importing our code requires no prior import to have happened, and no configuration is done as the side effect of importing any of our code. This explicitness helps you build larger systems because you're never left guessing about the configuration state: you are entirely in charge at all times. Most other web frameworks don't have a conflict detection system, and when they're fed two configuration statements that are logically conflicting, they'll choose one or the other silently, leaving you sometimes to wonder why you're not seeing the output you expect. Likewise, the execution ordering of configuration statements in most other web frameworks matters deeply; Pyramid doesn't make you care much about it. A third party developer can override parts of an existing application's configuration as long as that application's original developer anticipates it minimally by factoring his configuration statements into a function that is *includable*. He doesn't necessarily have to anticipate *what* bits of his application might be overridden, just that *something* might be overridden. This is unlike other web frameworks, which, if they allow for application extensibility at all, indeed tend to force the original application developer to think hard about what might be overridden. Under other frameworks, an application developer that wants to provide application extensibility is usually required to write ad-hoc code that allows a user to override various parts of his application such as views, routes, subscribers, and templates. In Pyramid, he is not required to do this: everything is overridable, and he just refers anyone who wants to change the way it works to the Pyramid docs. The ``config.include()`` system even allows a third-party developer who wants to change an application to not think about the mechanics of overriding at all; he just adds statements before or after including the original developer's configuration statements, and he relies on automatic conflict resolution to work things out for him. Configuration logic can be included from anywhere, and split across multiple packages and filesystem locations. There is no special set of Pyramid-y "application" directories containing configuration that must exist all in one place. Other web frameworks introduce packages or directories that are "more special than others" to offer similar features. To extend an application written using other web frameworks, you sometimes have to add to the set of them by changing a central directory structure. The system is *meta-configurable*. You can extend the set of configuration directives offered to users by using ``config.add_directive()``. This means that you can effectively extend Pyramid itself without needing to rewrite or redocument a solution from scratch: you just tell people the directive exists and tell them it works like every other Pyramid directive. You'll get all the goodness of conflict detection and resolution too. All of the examples in this article use the "imperative" Pyramid configuration API, where a user calls methods on a Configurator object to perform configuration. For developer convenience, Pyramid also exposes a declarative configuration mechanism, usually by offering a function, class, and method decorator that is activated via a *scan*. Such decorators simply attach a callback to the object they're decorating, and during the scan process these callbacks are called: the callbacks just call methods on a configurator on the behalf of the user as if he had typed them himself. These decorators participate in Pyramid's configuration scheme exactly like imperative method calls. For more information about ``config.include()`` and creating extensible applications, see :ref:`advconfig_narr` and :ref:`extending_chapter` in the Pyramid narrative documenation. For more information about creating directives, see :ref:`extconfig_narr`.