summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2010-12-24 17:09:32 -0500
committerChris McDonough <chrism@plope.com>2010-12-24 17:09:32 -0500
commit10fd8fe6bc26a7241542353032540cd4415ee9cc (patch)
treec43f7f2bfc1dca30a6f230279baeb23dee5c75e8 /docs
parent8fdcf54bee2cf9609bf4a6a2a0e13a9632ad295b (diff)
downloadpyramid-10fd8fe6bc26a7241542353032540cd4415ee9cc.tar.gz
pyramid-10fd8fe6bc26a7241542353032540cd4415ee9cc.tar.bz2
pyramid-10fd8fe6bc26a7241542353032540cd4415ee9cc.zip
- Added "Advanced Configuration" narrative chapter which documents how to
deal with configuration conflicts, two-phase configuration, ``include`` and ``commit``.
Diffstat (limited to 'docs')
-rw-r--r--docs/index.rst1
-rw-r--r--docs/latexindex.rst1
-rw-r--r--docs/narr/advconfig.rst373
3 files changed, 375 insertions, 0 deletions
diff --git a/docs/index.rst b/docs/index.rst
index 343fb28ba..3830a83f9 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -58,6 +58,7 @@ Narrative documentation in chapter form explaining how to use
narr/environment
narr/testing
narr/hooks
+ narr/advconfig
narr/declarative
narr/extending
narr/assets
diff --git a/docs/latexindex.rst b/docs/latexindex.rst
index 713c9841f..2d4644416 100644
--- a/docs/latexindex.rst
+++ b/docs/latexindex.rst
@@ -51,6 +51,7 @@ Narrative Documentation
narr/environment
narr/testing
narr/hooks
+ narr/advconfig
narr/declarative
narr/extending
narr/assets
diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst
new file mode 100644
index 000000000..a4692e4c6
--- /dev/null
+++ b/docs/narr/advconfig.rst
@@ -0,0 +1,373 @@
+.. index::
+ pair: advanced; configuration
+
+.. _advconfig_narr:
+
+Advanced Configuration
+======================
+
+To support application extensibility, the :app:`Pyramid`
+:term:`Configurator`, by default, detects configuration conflicts and allows
+you to include configuration imperatively from other packages or modules. It
+also, by default, performs configuration in two separate phases. This allows
+you to ignore relative configuration statement ordering in some
+circumstances.
+
+.. index::
+ single: imperative configuration
+
+.. _conflict_detection:
+
+Conflict Detection
+------------------
+
+Here's a familiar example of one of the simplest :app:`Pyramid` applications,
+configured imperatively:
+
+.. code-block:: python
+ :linenos:
+
+ from paste.httpserver import serve
+ 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)
+ app = config.make_wsgi_app()
+ serve(app, host='0.0.0.0')
+
+When you start this application, all will be OK. However, what happens if we
+try to add another view to the configuration with the same set of
+:term:`predicate` arguments as one we've already added?
+
+.. code-block:: python
+ :linenos:
+
+ from paste.httpserver import serve
+ from pyramid.config import Configurator
+ from pyramid.response import Response
+
+ def hello_world(request):
+ return Response('Hello world!')
+
+ def goodbye_world(request):
+ return Response('Goodbye world!')
+
+ if __name__ == '__main__':
+ config = Configurator()
+
+ config.add_view(hello_world, name='hello')
+
+ # conflicting view configuration
+ config.add_view(goodbye_world, name='hello')
+
+ app = config.make_wsgi_app()
+ serve(app, host='0.0.0.0')
+
+The application now has two conflicting view configuration statements. When
+we try to start it again, it won't start. Instead, we'll receive a traceback
+that ends something like this:
+
+.. code-block:: guess
+ :linenos:
+
+ Traceback (most recent call last):
+ File "app.py", line 12, in <module>
+ app = config.make_wsgi_app()
+ File "pyramid/config.py", line 839, in make_wsgi_app
+ self.commit()
+ File "pyramid/pyramid/config.py", line 473, in commit
+ self._ctx.execute_actions()
+ File "zope/configuration/config.py", line 600, in execute_actions
+ for action in resolveConflicts(self.actions):
+ File "zope/configuration/config.py", line 1507, in resolveConflicts
+ raise ConfigurationConflictError(conflicts)
+ zope.configuration.config.ConfigurationConflictError:
+ Conflicting configuration actions
+ For: ('view', None, '', None, <InterfaceClass pyramid.interfaces.IView>,
+ None, None, None, None, None, False, None, None, None)
+ ('app.py', 14, '<module>', 'config.add_view(hello_world)')
+ ('app.py', 17, '<module>', 'config.add_view(hello_world)')
+
+This traceback is trying to tell us:
+
+- We've got conflicting information for a set of view configuration
+ statements (The ``For:`` line).
+
+- There are two statements which conflict, shown beneath the ``For:`` line:
+ ``config.add_view(hello_world. 'hello')`` on line 14 of ``app.py``, and
+ ``config.add_view(goodbye_world, 'hello')`` on line 17 of ``app.py``.
+
+These two configuration statements are in conflict because we've tried to
+tell the system that the set of :term:`predicate` values for both view
+configurations are exactly the same. Both the ``hello_world`` and
+``goodbye_world`` views are configured to respond under the same set of
+circumstances. This circumstance: the :term:`view name` (represented by the
+``name=`` predicate) is ``hello``.
+
+This presents an ambiguity that :app:`Pyramid` cannot resolve. Rather than
+allowing the circumstance to go unreported, by default Pyramid raises a
+:exc:`ConfigurationConflictError` error and prevents the application from
+running.
+
+Conflict detection happens for any kind of configuration: imperative
+configuration, :term:`ZCML` configuration, or configuration that results from
+the execution of a :term:`scan`.
+
+Manually Resolving Conflicts
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are a number of ways to manually resolve conflicts: the "right" way, by
+strategically using :meth:`pyramid.config.Configurator.commit`, or by using
+an "autocommitting" configurator.
+
+The Right Thing
++++++++++++++++
+
+The most correct way to resolve conflicts is to "do the needful": change your
+configuration code to not have conflicting configuration statements. The
+details of how this is done depends entirely on the configuration statements
+made by your application. Use the detail provided in the
+:exc:`ConfigurationConflictError` to track down the offending conflicts and
+modify your configuration code accordingly.
+
+If you're getting a conflict while trying to extend an existing application,
+and that application has a function which performs configuration like this
+one:
+
+.. code-block:: python
+ :linenos:
+
+ def add_routes(config):
+ config.add_route(...)
+
+Don't call this function directly with ``config`` as an argument. Instead,
+use :meth:`pyramid.config.Configuration.include`:
+
+.. code-block:: python
+ :linenos:
+
+ config.include(add_routes)
+
+Using :meth:`~pyramid.config.Configuration.include` instead of calling the
+function directly provides a modicum of automated conflict resolution, with
+the configuration statements you define in the calling code overriding those
+of the included function. See also :ref:`automatic_conflict_resolution` and
+:ref:`including_configuration`.
+
+Using ``config.commit()``
++++++++++++++++++++++++++
+
+You can manually commit a configuration by using the
+:meth:`pyramid.config.Configurator.commit` method between configuration
+calls. For example, we can make the application we examined previously which
+generated a conflict exception
+
+.. code-block:: python
+ :linenos:
+
+ from paste.httpserver import serve
+ from pyramid.config import Configurator
+ from pyramid.response import Response
+
+ def hello_world(request):
+ return Response('Hello world!')
+
+ def goodbye_world(request):
+ return Response('Goodbye world!')
+
+ if __name__ == '__main__':
+ config = Configurator()
+
+ config.add_view(hello_world, name='hello')
+
+ # conflicting view configuration
+ config.add_view(goodbye_world, name='hello')
+
+ app = config.make_wsgi_app()
+ serve(app, host='0.0.0.0')
+
+We can prevent the two ``add_view`` calls from conflicting by issuing a call
+to :meth:`~pyramid.config.Configurator.commit` between them:
+
+.. code-block:: python
+ :linenos:
+
+ from paste.httpserver import serve
+ from pyramid.config import Configurator
+ from pyramid.response import Response
+
+ def hello_world(request):
+ return Response('Hello world!')
+
+ def goodbye_world(request):
+ return Response('Goodbye world!')
+
+ if __name__ == '__main__':
+ config = Configurator()
+
+ config.add_view(hello_world, name='hello')
+
+ config.commit() # commit any pending configuration actions
+
+ # no-longer-conflicting view configuration
+ config.add_view(goodbye_world, name='hello')
+
+ app = config.make_wsgi_app()
+ serve(app, host='0.0.0.0')
+
+In the above example we've issued a call to
+:meth:`~pyramid.config.Configurator.commit` between the two ``add_view``
+calls. :meth:`~pyramid.config.Configurator.commit` will cause any pending
+configuration statements.
+
+Calling :meth:`~pyramid.config.Configurator.commit` is safe at any time. It
+executes all pending configuration actions and leaves the configuration
+action list "clean".
+
+.. _autocommitting_configurator:
+
+Using An Autocommitting Configurator
+++++++++++++++++++++++++++++++++++++
+
+You can also use a heavy hammer to circumvent conflict detection by using a
+configurator constructor parameter: ``autocommit=True``. For example:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.config import Configurator
+
+ if __name__ == '__main__':
+ config = Configurator(autocommit=True)
+
+When the ``autocommit`` parameter passed to the Configurator is ``True``,
+conflict detection (and :ref:`twophase_config`) is disabled.
+:meth:`pyramid.config.Configurator.commit` has no effect when ``autocommit``
+is ``True`` either.
+
+If you use a Configurator in code that performs unit testing, it's usually a
+good idea to use an autocommitting Configurator, because you are usually
+unconcerned about conflict detection or two-phase configuration in test code.
+
+.. _automatic_conflict_resolution:
+
+Automatic Conflict Resolution
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If your code uses the :meth:`pyramid.config.Configurator.include` method to
+include external configuration, some conflicts are automatically resolved.
+Configuration statements that are made as the result of an "include" will be
+overridden by configuration statements that happen within the caller of
+the "include" method. See also
+
+Automatic conflict resolution supports this goal: if a user wants to reuse a
+Pyramid application, and they want to customize the configuration of this
+application without hacking its code "from outside", they can "include" the
+package and override only some of its configuration statements within the
+code that does the include. No conflicts will be generated by configuration
+statements within the code which does the including, even if configuration
+statements in the included code would conflict if it was moved "up" to the
+calling code.
+
+Methods Which Provide Conflict Detection
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+These are the methods of the configurator which provide conflict detection:
+
+:meth:`~pyramid.config.Configurator.add_view`,
+:meth:`~pyramid.config.Configurator.add_route`,
+:meth:`~pyramid.config.Configurator.add_renderer`,
+:meth:`~pyramid.config.Configurator.set_request_factory`,
+:meth:`~pyramid.config.Configurator.set_renderer_globals_factory`
+:meth:`~pyramid.config.Configurator.set_locale_negotiator` and
+:meth:`~pyramid.config.Configurator.set_default_permission`.
+
+Some other methods of the configurator also indirectly provide conflict
+detection, because they're implemented in terms of conflict-aware methods:
+
+- :meth:`~pyramid.config.Configurator.add_handler`, a frontend for
+ ``add_route`` and ``add_view``.
+
+- :meth:`~pyramid.config.Configurator.add_route` does a second type of
+ conflict detection when a ``view`` parameter is passed (it calls
+ ``add_view``).
+
+- :meth:`~pyramid.config.Configurator.static_view`, a frontend for
+ ``add_route`` and ``add_view``.
+
+.. _including_configuration:
+
+Including Configuration from External Sources
+---------------------------------------------
+
+Some application programmers will factor their configuration code in such a
+way that it is easy to reuse and override configuration statements. For
+example, such a developer might factor out a function used to add routes to
+his application:
+
+.. code-block:: python
+ :linenos:
+
+ def add_routes(config):
+ config.add_route(...)
+
+Rather than calling this function directly with ``config`` as an argument.
+Instead, use :meth:`pyramid.config.Configuration.include`:
+
+.. code-block:: python
+ :linenos:
+
+ config.include(add_routes)
+
+Using ``include`` rather than calling the function directly will allow
+:ref:`automatic_conflict_resolution` to work.
+
+.. _twophase_config:
+
+Two-Phase Configuration
+-----------------------
+
+When a non-autocommitting :term:`Configurator` is used to do configuration
+(the default), configuration execution happens in two phases. In the first
+phase, "eager" configuration actions (actions that must happen before all
+others, such as registering a renderer) are executed, and *discriminators*
+are computed for each of the actions that depend on the result of the eager
+actions. In the second phase, the discriminators of all actions are compared
+to do conflict detection.
+
+Due to this, for configuration methods that have no internal ordering
+constraints, execution order of configuration method calls is not important.
+For example, the relative ordering of
+:meth:`pyramid.config.Configurator.add_view` and
+:meth:`pyramid.config.Configurator.add_renderer` is unimportant when a
+non-autocommitting configurator is used. This code snippet:
+
+.. code-block:: python
+ :linenos:
+
+ config.add_view('some.view', renderer='path_to_custom/renderer.rn')
+ config.add_renderer('.rn', SomeCustomRendererFactory)
+
+Has the same result as:
+
+.. code-block:: python
+ :linenos:
+
+ config.add_renderer('.rn', SomeCustomRendererFactory)
+ config.add_view('some.view', renderer='path_to_custom/renderer.rn')
+
+Even though the view statement depends on the registration of a custom
+renderer, due to two-phase configuration, the order in which the
+configuration statements are issued is not important.
+
+The same is untrue when you use an *autocommitting* configurator (see
+:ref:`autocommitting_configurator`). When an autocommitting configurator is
+used, two-phase configuration is disabled, and configuration statements must
+be ordered in dependency order.
+
+