1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
|
.. _extending_chapter:
Extending an Existing :app:`Pyramid` Application
================================================
If a :app:`Pyramid` developer has obeyed certain constraints while building an
application, a third party should be able to change the application's behavior
without needing to modify its source code. The behavior of a :app:`Pyramid`
application that obeys certain constraints can be *overridden* or *extended*
without modification.
We'll define some jargon here for the benefit of identifying the parties
involved in such an effort.
Developer
The original application developer.
Integrator
Another developer who wishes to reuse the application written by the original
application developer in an unanticipated context. They may also wish to
modify the original application without changing the original application's
source code.
The Difference Between "Extensible" and "Pluggable" Applications
----------------------------------------------------------------
Other web frameworks, such as :term:`Django`, advertise that they allow
developers to create "pluggable applications". They claim that if you create
an application in a certain way, it will be integratable in a sensible,
structured way into another arbitrarily-written application or project created
by a third-party developer.
:app:`Pyramid`, as a platform, does not claim to provide such a feature. The
platform provides no guarantee that you can create an application and package
it up such that an arbitrary integrator can use it as a subcomponent in a
larger Pyramid application or project. Pyramid does not mandate the
constraints necessary for such a pattern to work satisfactorily. Because
Pyramid is not very "opinionated", developers are able to use wildly different
patterns and technologies to build an application. A given Pyramid application
may happen to be reusable by a particular third party integrator because the
integrator and the original developer may share similar base technology choices
(such as the use of a particular relational database or ORM). But the same
application may not be reusable by a different developer, because they have
made different technology choices which are incompatible with the original
developer's.
As a result, the concept of a "pluggable application" is left to layers built
above Pyramid, such as a "CMS" layer or "application server" layer. Such
layers are apt to provide the necessary "opinions" (such as mandating a storage
layer, a templating system, and a structured, well-documented pattern of
registering that certain URLs map to certain bits of code) which makes the
concept of a "pluggable application" possible. "Pluggable applications", thus,
should not plug into Pyramid itself but should instead plug into a system
written atop Pyramid.
Although it does not provide for "pluggable applications", Pyramid *does*
provide a rich set of mechanisms which allows for the extension of a single
existing application. Such features can be used by frameworks built using
Pyramid as a base. All Pyramid applications may not be *pluggable*, but all
Pyramid applications are *extensible*.
.. index::
single: extensible application
.. _building_an_extensible_app:
Rules for Building an Extensible Application
--------------------------------------------
There is only one rule you need to obey if you want to build a maximally
extensible :app:`Pyramid` application: as a developer, you should factor any
overridable :term:`imperative configuration` you've created into functions
which can be used via :meth:`pyramid.config.Configurator.include`, rather than
inlined as calls to methods of a :term:`Configurator` within the ``main``
function in your application's ``__init__.py``. For example, rather than:
.. code-block:: python
:linenos:
from pyramid.config import Configurator
if __name__ == '__main__':
config = Configurator()
config.add_view('myapp.views.view1', name='view1')
config.add_view('myapp.views.view2', name='view2')
You should move the calls to ``add_view`` outside of the (non-reusable) ``if
__name__ == '__main__'`` block, and into a reusable function:
.. code-block:: python
:linenos:
from pyramid.config import Configurator
if __name__ == '__main__':
config = Configurator()
config.include(add_views)
def add_views(config):
config.add_view('myapp.views.view1', name='view1')
config.add_view('myapp.views.view2', name='view2')
Doing this allows an integrator to maximally reuse the configuration statements
that relate to your application by allowing them to selectively include or
exclude the configuration functions you've created from an "override package".
Alternatively you can use :term:`ZCML` for the purpose of making configuration
extensible and overridable. :term:`ZCML` declarations that belong to an
application can be overridden and extended by integrators as necessary in a
similar fashion. If you use only :term:`ZCML` to configure your application,
it will automatically be maximally extensible without any manual effort. See
:term:`pyramid_zcml` for information about using ZCML.
Fundamental Plugpoints
~~~~~~~~~~~~~~~~~~~~~~
The fundamental "plug points" of an application developed using :app:`Pyramid`
are *routes*, *views*, and *assets*. Routes are declarations made using the
:meth:`pyramid.config.Configurator.add_route` method. Views are declarations
made using the :meth:`pyramid.config.Configurator.add_view` method. Assets are
files that are accessed by :app:`Pyramid` using the :term:`pkg_resources` API
such as static files and templates via a :term:`asset specification`. Other
directives and configurator methods also deal in routes, views, and assets.
For example, the ``add_handler`` directive of the ``pyramid_handlers`` package
adds a single route and some number of views.
.. index::
single: extending an existing application
Extending an Existing Application
---------------------------------
The steps for extending an existing application depend largely on whether the
application does or does not use configuration decorators or imperative code.
If the Application Has Configuration Decorations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You've inherited a :app:`Pyramid` application which you'd like to extend or
override that uses :class:`pyramid.view.view_config` decorators or other
:term:`configuration decoration` decorators.
If you just want to *extend* the application, you can run a :term:`scan`
against the application's package, then add additional configuration that
registers more views or routes.
.. code-block:: python
:linenos:
if __name__ == '__main__':
config.scan('someotherpackage')
config.add_view('mypackage.views.myview', name='myview')
If you want to *override* configuration in the application, you *may* need to
run :meth:`pyramid.config.Configurator.commit` after performing the scan of the
original package, then add additional configuration that registers more views
or routes which perform overrides.
.. code-block:: python
:linenos:
if __name__ == '__main__':
config.scan('someotherpackage')
config.commit()
config.add_view('mypackage.views.myview', name='myview')
Once this is done, you should be able to extend or override the application
like any other (see :ref:`extending_the_application`).
You can alternatively just prevent a :term:`scan` from happening by omitting
any call to the :meth:`pyramid.config.Configurator.scan` method. This will
cause the decorators attached to objects in the target application to do
nothing. At this point, you will need to convert all the configuration done in
decorators into equivalent imperative configuration or ZCML, and add that
configuration or ZCML to a separate Python package as described in
:ref:`extending_the_application`.
.. _extending_the_application:
Extending the Application
~~~~~~~~~~~~~~~~~~~~~~~~~
To extend or override the behavior of an existing application, you will need to
create a new package which includes the configuration of the old package, and
you'll perhaps need to create implementations of the types of things you'd like
to override (such as views), to which they are referred within the original
package.
The general pattern for extending an existing application looks something like
this:
- Create a new Python package. The easiest way to do this is to create a new
:app:`Pyramid` application using the scaffold mechanism. See
:ref:`creating_a_project` for more information.
- In the new package, create Python files containing views and other overridden
elements, such as templates and static assets as necessary.
- Install the new package into the same Python environment as the original
application (e.g., ``$VENV/bin/python setup.py develop`` or
``$VENV/bin/python setup.py install``).
- Change the ``main`` function in the new package's ``__init__.py`` to include
the original :app:`Pyramid` application's configuration functions via
:meth:`pyramid.config.Configurator.include` statements or a :term:`scan`.
- Wire the new views and assets created in the new package up using imperative
registrations within the ``main`` function of the ``__init__.py`` file of the
new application. This wiring should happen *after* including the
configuration functions of the old application. These registrations will
extend or override any registrations performed by the original application.
See :ref:`overriding_views`, :ref:`overriding_routes`, and
:ref:`overriding_resources`.
.. index::
pair: overriding; views
.. _overriding_views:
Overriding Views
~~~~~~~~~~~~~~~~
The :term:`view configuration` declarations that you make which *override*
application behavior will usually have the same :term:`view predicate`
attributes as the original that you wish to override. These ``<view>``
declarations will point at "new" view code in the override package that you've
created. The new view code itself will usually be copy-and-paste copies of
view callables from the original application with slight tweaks.
For example, if the original application has the following ``configure_views``
configuration method:
.. code-block:: python
:linenos:
def configure_views(config):
config.add_view('theoriginalapp.views.theview', name='theview')
You can override the first view configuration statement made by
``configure_views`` within the override package, after loading the original
configuration function:
.. code-block:: python
:linenos:
from pyramid.config import Configurator
from originalapp import configure_views
if __name == '__main__':
config = Configurator()
config.include(configure_views)
config.add_view('theoverrideapp.views.theview', name='theview')
In this case, the ``theoriginalapp.views.theview`` view will never be executed.
Instead, a new view, ``theoverrideapp.views.theview`` will be executed when
request circumstances dictate.
A similar pattern can be used to *extend* the application with ``add_view``
declarations. Just register a new view against some other set of predicates to
make sure the URLs it implies are available on some other page rendering.
.. index::
pair: overriding; routes
.. _overriding_routes:
Overriding Routes
~~~~~~~~~~~~~~~~~
Route setup is currently typically performed in a sequence of ordered calls to
:meth:`~pyramid.config.Configurator.add_route`. Because these calls are
ordered relative to each other, and because this ordering is typically
important, you should retain their relative ordering when performing an
override. Typically this means *copying* all the ``add_route`` statements into
the override package's file and changing them as necessary. Then exclude any
``add_route`` statements from the original application.
.. index::
pair: overriding; assets
.. _overriding_resources:
Overriding Assets
~~~~~~~~~~~~~~~~~
Assets are files on the filesystem that are accessible within a Python
*package*. An entire chapter is devoted to assets: :ref:`assets_chapter`.
Within this chapter is a section named :ref:`overriding_assets_section`. This
section of that chapter describes in detail how to override package assets with
other assets by using the :meth:`pyramid.config.Configurator.override_asset`
method. Add such ``override_asset`` calls to your override package's
``__init__.py`` to perform overrides.
|