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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 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 http://localhost:8080/ 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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 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 : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 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: Traceback (most recent call last): File "app.py", line 12, in <module> 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: <class 'pyramid.exceptions.ConfigurationError'>: 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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 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?: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Traceback ( most recent call last ): File "app.py" , line 17 , in < module > 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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # 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 : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Traceback ( most recent call last ): File "app.py" , line 12 , in < module > 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: 1 2 3 4 5 6 7 8 9 10 # 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 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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 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 : 1 2 3 4 5 6 7 8 9 10 # 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: 1 2 3 4 5 6 7 8 9 10 # 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: 1 2 3 4 5 6 7 8 9 10 11 # 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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # 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 : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Traceback ( most recent call last ): File "app.py" , line 15 , in < module > 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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # 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".