Chris McDonough
2011-09-13 96d456324abed737330a392f272c961d36916063
get rid of the airquotes everywhere
1 files modified
261 ■■■■ changed files
docs/designdefense.rst 261 ●●●● patch | view | raw | blame | history
docs/designdefense.rst
@@ -7,7 +7,7 @@
lodged.  To give context to discussions that follow, we detail some of the
design decisions and trade-offs here.  In some cases, we acknowledge that the
framework can be made better and we describe future steps which will be taken
to improve it; in some cases we just file the challenge as "noted", as
to improve it; in some cases we just file the challenge as noted, as
obviously you can't please everyone all of the time.
Pyramid Provides More Than One Way to Do It
@@ -74,10 +74,9 @@
developer.  It was also strange for the Pylons and BFG development
communities to be in competition for the same set of users, given how similar
the two frameworks were.  So the Pylons and BFG teams began to work together
to form a plan to "merge".  The features missing from BFG (notably
:term:`view handler` classes, flash messaging, and other minor missing bits),
were added, to provide familiarity to ex-Pylons users.  The result is
:app:`Pyramid`.
to form a plan to merge.  The features missing from BFG (notably :term:`view
handler` classes, flash messaging, and other minor missing bits), were added,
to provide familiarity to ex-Pylons users.  The result is :app:`Pyramid`.
The Python web framework space is currently notoriously balkanized.  We're
truly hoping that the amalgamation of components in :app:`Pyramid` will
@@ -97,7 +96,7 @@
and unavoidable, at least if we aim to prevent pointless duplication at
higher levels.  If we've done our job well enough, the various audiences will
be able to coexist and cooperate rather than firing at each other across some
imaginary web framework "DMZ".
imaginary web framework DMZ.
Pyramid Uses A Zope Component Architecture ("ZCA") Registry
-----------------------------------------------------------
@@ -114,12 +113,12 @@
Problems
++++++++
The "global" API that may be used to access data in a ZCA "component
registry" is not particularly pretty or intuitive, and sometimes it's just
plain obtuse.  Likewise, the conceptual load on a casual source code reader
of code that uses the ZCA global API is somewhat high.  Consider a ZCA
neophyte reading the code that performs a typical "unnamed utility" lookup
using the :func:`zope.component.getUtility` global API:
The global API that may be used to access data in a ZCA component registry
is not particularly pretty or intuitive, and sometimes it's just plain
obtuse.  Likewise, the conceptual load on a casual source code reader of code
that uses the ZCA global API is somewhat high.  Consider a ZCA neophyte
reading the code that performs a typical "unnamed utility" lookup using the
:func:`zope.component.getUtility` global API:
.. ignore-next-block
.. code-block:: python
@@ -130,7 +129,7 @@
   settings = getUtility(ISettings)
After this code runs, ``settings`` will be a Python dictionary.  But it's
unlikely that any "civilian" would know that just by reading the code.  There
unlikely that any civilian would know that just by reading the code.  There
are a number of comprehension issues with the bit of code above that are
obvious.
@@ -142,7 +141,7 @@
problem number one.
Second, what's this ``ISettings`` thing?  It's an :term:`interface`.  Is that
important here?  Not really, we're just using it as a "key" for some lookup
important here?  Not really, we're just using it as a key for some lookup
based on its identity as a marker: it represents an object that has the
dictionary API, but that's not very important in this context.  That's
problem number two.
@@ -165,12 +164,12 @@
variable.  Using an API that consults a thread local makes understanding how
it works non-local.
You've now bought in to the fact that there's a registry that is just
"hanging around".  But how does the registry get populated?  Why, via code
that calls directives like ``config.add_view``.  In this particular case,
however, the registration of ``ISettings`` is made by the framework itself
"under the hood": it's not present in any user configuration.  This is
extremely hard to comprehend.  Problem number six.
You've now bought in to the fact that there's a registry that is just hanging
around.  But how does the registry get populated?  Why, via code that calls
directives like ``config.add_view``.  In this particular case, however, the
registration of ``ISettings`` is made by the framework itself under the hood:
it's not present in any user configuration.  This is extremely hard to
comprehend.  Problem number six.
Clearly there's some amount of cognitive load here that needs to be borne by
a reader of code that extends the :app:`Pyramid` framework due to its use of
@@ -225,7 +224,7 @@
needn't understand the ZCA API either.
Hiding the ZCA API from application developers and code readers is a form of
enhancing "domain specificity".  No application developer wants to need to
enhancing domain specificity.  No application developer wants to need to
understand the small, detailed mechanics of how a web framework does its
thing.  People want to deal in concepts that are closer to the domain they're
working in: for example, web developers want to know about *users*, not
@@ -266,7 +265,7 @@
it.  So, for the purposes of being kind to third-party :app:`Pyramid`
framework developers in, we've drawn some lines in the sand.
In all "core" code, We've made use of ZCA global API functions such as
In all core code, We've made use of ZCA global API functions such as
``zope.component.getUtility`` and ``zope.component.getAdapter`` the exception
instead of the rule.  So instead of:
@@ -308,13 +307,13 @@
  mapping is done.
- Features.  The ZCA component registry essentially provides what can be
  considered something like a "superdictionary", which allows for more
  complex lookups than retrieving a value based on a single key.  Some of
  this lookup capability is very useful for end users, such as being able to
  register a view that is only found when the context is some class of
  object, or when the context implements some :term:`interface`.
  considered something like a superdictionary, which allows for more complex
  lookups than retrieving a value based on a single key.  Some of this lookup
  capability is very useful for end users, such as being able to register a
  view that is only found when the context is some class of object, or when
  the context implements some :term:`interface`.
- Singularity.  There's only one "place" where "application configuration"
- Singularity.  There's only one place where "application configuration"
  lives in a :app:`Pyramid` application: in a component registry.  The
  component registry answers questions made to it by the framework at runtime
  based on the configuration of *an application*.  Note: "an application" is
@@ -332,8 +331,8 @@
  via a well-defined and widely understood plugin architecture.  As long as
  framework developers and extenders understand the ZCA registry, it's
  possible to extend :app:`Pyramid` almost arbitrarily.  For example, it's
  relatively easy to build a directive that registers several views "all at
  once", allowing app developers to use that directive as a "macro" in code
  relatively easy to build a directive that registers several views all at
  once, allowing app developers to use that directive as a "macro" in code
  that they write.  This is somewhat of a differentiating feature from other
  (non-Zope) frameworks.
@@ -368,7 +367,7 @@
need to use only the documented APIs; that's why we document them as APIs.
If you *extend* or *develop* :app:`Pyramid` (create new directives, use some
of the more obscure "hooks" as described in :ref:`hooks_chapter`, or work on
of the more obscure hooks as described in :ref:`hooks_chapter`, or work on
the :app:`Pyramid` core code), you will be faced with needing to understand
at least some ZCA concepts.  In some places it's used unabashedly, and will
be forever.  We know it's quirky, but it's also useful and fundamentally
@@ -417,13 +416,13 @@
``application/json`` (or, for that matter, ``application/*``) in its
``Accept`` request header.
"Under the hood", these features make no use of interfaces.
Under the hood, these features make no use of interfaces.
Many "prebaked" predicates exist.  However, use of only "prebaked" predicates,
Many prebaked predicates exist.  However, use of only prebaked predicates,
however, doesn't entirely meet Ian's criterion.  He would like to be able to
match a request using a lambda or another function which interrogates the
request imperatively.  In :mod:`repoze.bfg` version 1.2, we acommodate this by
allowing people to define "custom" view predicates:
request imperatively.  In :mod:`repoze.bfg` version 1.2, we acommodate this
by allowing people to define custom view predicates:
.. code-block:: python
   :linenos:
@@ -480,10 +479,10 @@
resource tree, you needn't use it at all.  Instead, use :term:`URL dispatch`
to map URL paths to views.
The idea that some folks believe traversal is unilaterally "wrong" is
The idea that some folks believe traversal is unilaterally wrong is
understandable.  The people who believe it is wrong almost invariably have
all of their data in a relational database.  Relational databases aren't
naturally hierarchical, so "traversing" one like a tree is not possible.
naturally hierarchical, so traversing one like a tree is not possible.
However, folks who deem traversal unilaterally wrong are neglecting to take
into account that many persistence mechanisms *are* hierarchical.  Examples
@@ -507,7 +506,7 @@
Traversal also offers better composability of applications than URL dispatch,
because it doesn't rely on a fixed ordering of URL matching.  You can compose
a set of disparate functionality (and add to it later) around a mapping of
view to resource more predictably than trying to get "the right" ordering of
view to resource more predictably than trying to get the right ordering of
URL pattern matching.
But the point is ultimately moot.  If you don't want to use traversal, you
@@ -565,9 +564,9 @@
Many web frameworks (Zope, TurboGears, Pylons 1.X, Django) allow for their
variant of a :term:`view callable` to accept arbitrary keyword or positional
arguments, which are "filled in" using values present in the ``request.POST``
or ``request.GET`` dictionaries or by values present in the "route match
dictionary".  For example, a Django view will accept positional arguments
arguments, which are filled in using values present in the ``request.POST``
or ``request.GET`` dictionaries or by values present in the route match
dictionary.  For example, a Django view will accept positional arguments
which match information in an associated "urlconf" such as
``r'^polls/(?P<poll_id>\d+)/$``:
@@ -612,7 +611,7 @@
Pyramid Provides Too Few "Rails"
--------------------------------
By design, :app:`Pyramid` is not a particularly "opinionated" web framework.
By design, :app:`Pyramid` is not a particularly opinionated web framework.
It has a relatively parsimonious feature set.  It contains no built in ORM
nor any particular database bindings.  It contains no form generation
framework.  It has no administrative web user interface.  It has no built in
@@ -627,7 +626,7 @@
:app:`Pyramid` provides some features that other web frameworks do not.
These are features meant for use cases that might not make sense to you if
you're building a simple "bespoke" web application:
you're building a simple bespoke web application:
- An optional way to map URLs to code using :term:`traversal` which implies a
  walk of a :term:`resource tree`.
@@ -641,19 +640,19 @@
- A declarative :term:`authorization` system.
- Multiple separate I18N :term:`translation string` factories, each of which
  can name its own "domain".
  can name its own domain.
These features are important to the authors of :app:`Pyramid`.  The
:app:`Pyramid` authors are often commissioned to build CMS-style
applications.  Such applications are often "frameworky" because they have
more than one deployment.  Each deployment requires a slightly different
applications.  Such applications are often frameworky because they have more
than one deployment.  Each deployment requires a slightly different
composition of sub-applications, and the framework and sub-applications often
need to be *extensible*.  Because the application has more than one
deployment, pluggability and extensibility is important, as maintaining
multiple forks of the application, one per deployment, is extremely
undesirable.  Because it's easier to extend a system that uses
:term:`traversal` "from the outside" than it is to do the same in a system
that uses :term:`URL dispatch`, each deployment uses a :term:`resource tree`
:term:`traversal` from the outside than it is to do the same in a system that
uses :term:`URL dispatch`, each deployment uses a :term:`resource tree`
composed of a persistent tree of domain model objects, and uses
:term:`traversal` to map :term:`view callable` code to resources in the tree.
The resource tree contains very granular security declarations, as resources
@@ -671,7 +670,7 @@
making sure you don't need to know about them up-front.  You can build
"Pylons-1.X-style" applications using :app:`Pyramid` that are purely bespoke
by ignoring the features above.  You may find these features handy later
after building a "bespoke" web application that suddenly becomes popular and
after building a bespoke web application that suddenly becomes popular and
requires extensibility because it must be deployed in multiple locations.
Pyramid Is Too Big
@@ -763,7 +762,7 @@
expect that models are ORM models, controllers are classes that have methods
that map to URLs, and views are templates.  :app:`Pyramid` indeed has each of
these concepts, and each probably *works* almost exactly like your existing
"MVC" web framework. We just don't use the "MVC" terminology, as we can't
"MVC" web framework. We just don't use the MVC terminology, as we can't
square its usage in the web framework space with historical reality.
People very much want to give web applications the same properties as common
@@ -805,7 +804,7 @@
tortuously couching concepts in the most generic terms possible in order to
account for the use of the term "MVC" by current web frameworks.  I doubt
such a broad definition would ever be agreed to by the original authors of
the MVC pattern.  But *even so*, it seems most "MVC" web frameworks fail to
the MVC pattern.  But *even so*, it seems most MVC web frameworks fail to
meet even this falsely generic definition.
For example, do your templates (views) always query models directly as is
@@ -855,8 +854,8 @@
  ZCA, the original developer does not need to think terribly hard
  about the mechanics of introducing such a plugpoint.
Many developers seem to believe that creating extensible applications is "not
worth it".  They instead suggest that modifying the source of a given
Many developers seem to believe that creating extensible applications is not
worth it.  They instead suggest that modifying the source of a given
application for each deployment to override behavior is more reasonable.
Much discussion about version control branching and merging typically ensues.
@@ -864,8 +863,8 @@
majority of web applications only have a single deployment, and thus needn't
be extensible at all.  However, some web applications have multiple
deployments, and some have *many* deployments.  For example, a generic
"content management" system (CMS) may have basic functionality that needs to
be extended for a particular deployment.  That CMS system may be deployed for
content management system (CMS) may have basic functionality that needs to be
extended for a particular deployment.  That CMS system may be deployed for
many organizations at many places.  Some number of deployments of this CMS
may be deployed centrally by a third party and managed as a group.  It's
useful to be able to extend such a system for each deployment via preordained
@@ -942,7 +941,7 @@
system.
Ultimately, any argument about whether the extensibility features lent to
applications by :app:`Pyramid` are "good" or "bad" is mostly pointless. You
applications by :app:`Pyramid` are good or bad is mostly pointless. You
needn't take advantage of the extensibility features provided by a particular
:app:`Pyramid` application in order to affect a modification for a particular
set of its deployments.  You can ignore the application's extensibility
@@ -977,33 +976,33 @@
+++++++
:app:`Pyramid` was developed by folks familiar with Zope 2, which has a
"through the web" security model.  This "TTW" security model was the
precursor to Zope 3's security proxies.  Over time, as the :app:`Pyramid`
developers (working in Zope 2) created such sites, we found authorization
checks during code interpretation extremely useful in a minority of projects.
But much of the time, TTW authorization checks usually slowed down the
development velocity of projects that had no delegation requirements.  In
particular, if we weren't allowing "untrusted" users to write arbitrary
Python code to be executed by our application, the burden of "through the
web" security checks proved too costly to justify.  We (collectively) haven't
written an application on top of which untrusted developers are allowed to
write code in many years, so it seemed to make sense to drop this model by
default in a new web framework.
"through the web" security model.  This TTW security model was the precursor
to Zope 3's security proxies.  Over time, as the :app:`Pyramid` developers
(working in Zope 2) created such sites, we found authorization checks during
code interpretation extremely useful in a minority of projects.  But much of
the time, TTW authorization checks usually slowed down the development
velocity of projects that had no delegation requirements.  In particular, if
we weren't allowing untrusted users to write arbitrary Python code to be
executed by our application, the burden of through the web security checks
proved too costly to justify.  We (collectively) haven't written an
application on top of which untrusted developers are allowed to write code in
many years, so it seemed to make sense to drop this model by default in a new
web framework.
And since we tend to use the same toolkit for all web applications, it's just
never been a concern to be able to use the same set of restricted-execution
code under two web different frameworks.
Justifications for disabling security proxies by default notwithstanding,
given that Zope 3 security proxies are "viral" by nature, the only
requirement to use one is to make sure you wrap a single object in a security
proxy and make sure to access that object normally when you want proxy
security checks to happen.  It is possible to override the :app:`Pyramid`
"traverser" for a given application (see :ref:`changing_the_traverser`).  To
get Zope3-like behavior, it is possible to plug in a different traverser
which returns Zope3-security-proxy-wrapped objects for each traversed object
(including the :term:`context` and the :term:`root`).  This would have the
effect of creating a more Zope3-like environment without much effort.
given that Zope 3 security proxies are viral by nature, the only requirement
to use one is to make sure you wrap a single object in a security proxy and
make sure to access that object normally when you want proxy security checks
to happen.  It is possible to override the :app:`Pyramid` traverser for a
given application (see :ref:`changing_the_traverser`).  To get Zope3-like
behavior, it is possible to plug in a different traverser which returns
Zope3-security-proxy-wrapped objects for each traversed object (including the
:term:`context` and the :term:`root`).  This would have the effect of
creating a more Zope3-like environment without much effort.
.. _http_exception_hierarchy:
@@ -1058,7 +1057,7 @@
- Allows developers to mutate the traversal name stack while traversing (they
  can add and remove path elements).
- Attempts to use an adaptation to obtain the "next" element in the path from
- Attempts to use an adaptation to obtain the next element in the path from
  the currently traversed object, falling back to ``__bobo_traverse__``,
  ``__getitem__`` and eventually ``__getattr__``.
@@ -1086,7 +1085,7 @@
  when one traversal method failed.  It is also slow.
- The *entire traverser* should be replaceable, not just elements of the
  traversal machinery.  Pyramid has a few "big" components rather than a
  traversal machinery.  Pyramid has a few big components rather than a
  plethora of small ones.  If the entire traverser is replaceable, it's an
  antipattern to make portions of the default traverser replaceable.  Doing
  so is a "knobs on knobs" pattern, which is unfortunately somewhat endemic
@@ -1099,13 +1098,13 @@
  to either replace the larger component entirely or turn knobs on the
  default implementation of the larger component, no one understands when (or
  whether) they should ever override the larger component entrirely.  This
  results, over time, in a "rusting together" of the larger "replaceable"
  results, over time, in a rusting together of the larger "replaceable"
  component and the framework itself, because people come to depend on the
  availability of the default component in order just to turn its knobs. The
  default component effectively becomes part of the framework, which entirely
  subverts the goal of making it replaceable.  In Pyramid, typically if a
  component is replaceable, it will itself have no knobs (it will be "solid
  state").  If you want to influence behavior controlled by that component,
  component is replaceable, it will itself have no knobs (it will be solid
  state).  If you want to influence behavior controlled by that component,
  you will replace the component instead of turning knobs attached to the
  component.
@@ -1322,10 +1321,10 @@
time, and application complexity.
If microframework authors do admit that the circumstance isn't contrived,
they might then argue that "real" damage will never happen as the result of
the double-execution (or triple-execution, etc) of module scope code.  You
would be wise to disbelieve this assertion.  The potential outcomes of
multiple execution are too numerous to predict because they involve delicate
they might then argue that real damage will never happen as the result of the
double-execution (or triple-execution, etc) of module scope code.  You would
be wise to disbelieve this assertion.  The potential outcomes of multiple
execution are too numerous to predict because they involve delicate
relationships between application and framework code as well as chronology of
code execution.  It's literally impossible for a framework author to know
what will happen in all circumstances.  But even if given the gift of
@@ -1380,7 +1379,7 @@
  Aha!  Don't these decorators have the same problems?  No.  These decorators
  do not populate an external Python module when they are executed.  They
  only mutate the functions (and classes and methods) they're attached to.
  These mutations must later be found during a "scan" process that has a
  These mutations must later be found during a scan process that has a
  predictable and structured import phase.  Module-localized mutation is
  actually the best-case circumstance for double-imports; if a module only
  mutates itself and its contents at import time, if it is imported twice,
@@ -1416,7 +1415,7 @@
    if __name__ == '__main__':
        app.run()
If you run this application and visit the URL ``/admin``, you will see
If you run this application and visit the URL ``/admin``, you will see the
"admin" page.  This is the intended result.  However, what if you rearrange
the order of the function definitions in the file?
@@ -1465,7 +1464,7 @@
matches first.  A 404 error is raised.  This is not what we wanted; it just
happened due to the order in which we defined our view functions.
This is because "Groundhog" routes are added to the routing map in import
This is because Groundhog routes are added to the routing map in import
order, and matched in the same order when a request comes in.  Bottle, like
Groundhog, as of this writing, matches routes in the order in which they're
defined at Python execution time.  Flask, on the other hand, does not order
@@ -1480,10 +1479,10 @@
controlling route ordering, especially in applications that require
extensibility.
If your microframework orders route matching based on "complexity", you'll
need to understand what that "complexity" ordering is and attempt to inject a
"less complex" route to have it get matched before any "more complex" one to
ensure that it's tried first.
If your microframework orders route matching based on complexity, you'll need
to understand what is meant by "complexity", and you'll need to attempt to
inject a "less complex" route to have it get matched before any "more
complex" one to ensure that it's tried first.
If your microframework orders its route matching based on relative
import/execution of function decorator definitions, you will need to ensure
@@ -1498,7 +1497,7 @@
:ref:`you_dont_own_modulescope`.
Pyramid uses neither decorator import time ordering nor does it attempt to
divine the relative "complexity" of one route to another in order to define a
divine the relative complexity of one route to another in order to define a
route match ordering.  In Pyramid, you have to maintain relative route
ordering imperatively via the chronology of multiple executions of the
:meth:`pyramid.config.Configurator.add_route` method.  The order in which you
@@ -1514,9 +1513,8 @@
"Stacked Object Proxies" Are Too Clever / Thread Locals Are A Nuisance
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
In another manifestation of "import fascination", some microframeworks use
the ``import`` statement to get a handle to an object which *is not logically
global*:
Some microframeworks use the ``import`` statement to get a handle to an
object which *is not logically global*:
.. code-block:: python
    :linenos:
@@ -1541,7 +1539,7 @@
Import statements in Python (``import foo``, ``from bar import baz``) are
most frequently performed to obtain a reference to an object defined globally
within an external Python module.  However, in "normal" programs, they are
within an external Python module.  However, in normal programs, they are
never used to obtain a reference to an object that has a lifetime measured by
the scope of the body of a function.  It would be absurd to try to import,
for example, a variable named ``i`` representing a loop counter defined in
@@ -1561,11 +1559,11 @@
running the framework.  A request object created by a web framework actually
has more similarity to the ``i`` loop counter in our example above than it
has to any comparable importable object defined in the Python standard
library or in "normal" library code.
library or in normal library code.
However, systems which use stacked object proxies promote locally scoped
objects such as ``request`` out to module scope, for the purpose of being
able to offer users a "nice" spelling involving ``import``.  They, for what I
able to offer users a nice spelling involving ``import``.  They, for what I
consider dubious reasons, would rather present to their users the canonical
way of getting at a ``request`` as ``from framework import request`` instead
of a saner ``from myframework.threadlocals import get_request; request =
@@ -1612,7 +1610,7 @@
As a result, for single-file apps, we currently don't bother to offer a
``run()`` shortcut; we tell folks to import their WSGI server of choice and
run it "by hand".  For the people who want a server abstraction layer, we
run it by hand.  For the people who want a server abstraction layer, we
suggest that they use PasteDeploy.  In PasteDeploy-based systems, the onus
for making sure that the server can interface with a WSGI application is
placed on the server developer, not the web framework developer, making it
@@ -1652,13 +1650,13 @@
include that can be used outside of one (such as
:meth:`~pyramid.config.Configurator.add_view`, etc).
Pyramid has a conflict detection system that will throw an error if two
included externals try to add "the same" configuration in a conflicting
way (such as both externals trying to add a route using the same name,
or both externals trying to add a view with the same set of predicates).
It's awful tempting to call this set of features something that can be
used to compose a system out of "pluggable applications".  But in
reality, there are a number of problems with claiming this:
Pyramid has a conflict detection system that will throw an error if two
included externals try to add the same configuration in a conflicting way
(such as both externals trying to add a route using the same name, or both
externals trying to add a view with the same set of predicates).  It's awful
tempting to call this set of features something that can be used to compose a
system out of "pluggable applications".  But in reality, there are a number
of problems with claiming this:
- The terminology is strained. Pyramid really has no notion of a 
  plurality of "applications", just a way to compose configuration 
@@ -1669,31 +1667,28 @@
  the boundaries of one "application" (in the sense of configuration 
  from an external that adds routes, views, etc) from another. 
- Pyramid doesn't provide enough "rails" to make it possible to
  integrate truly honest-to-god, download-an-app-from-a-random-place
  and-plug-it-in-to-create-a-system "pluggable" applications.
  Because Pyramid itself isn't opinionated (it doesn't mandate a
  particular kind of database, it offers multiple ways to map URLs
  to code, etc), it's unlikely that someone who creates something
  "application-like" will be able to casually redistribute it
  to J. Random Pyramid User and have it "just work" by asking him
  to config.include a function from the package.
  This is particularly true of very high level components such
  as blogs, wikis, twitter clones, commenting systems, etc.
  The "integrator" (the Pyramid developer who has downloaded a
  package advertised as a "pluggable app") will almost certainly
  have made different choices about e.g. what type of persistence
  system he's using, and for the integrator to appease the
  requirements of the "pluggable application", he may be required
  to set up a different database, make changes to his own code
  to prevent his application from "shadowing" the pluggable
  app (or vice versa), and any other number of arbitrary
  changes.
- Pyramid doesn't provide enough "rails" to make it possible to integrate
  truly honest-to-god, download-an-app-from-a-random-place
  and-plug-it-in-to-create-a-system "pluggable" applications.  Because
  Pyramid itself isn't opinionated (it doesn't mandate a particular kind of
  database, it offers multiple ways to map URLs to code, etc), it's unlikely
  that someone who creates something application-like will be able to
  casually redistribute it to J. Random Pyramid User and have it just work by
  asking him to config.include a function from the package.  This is
  particularly true of very high level components such as blogs, wikis,
  twitter clones, commenting systems, etc.  The integrator (the Pyramid
  developer who has downloaded a package advertised as a "pluggable app")
  will almost certainly have made different choices about e.g. what type of
  persistence system he's using, and for the integrator to appease the
  requirements of the "pluggable application", he may be required to set up a
  different database, make changes to his own code to prevent his application
  from shadowing the pluggable app (or vice versa), and any other number of
  arbitrary changes.
For this reason, we claim that Pyramid has "extensible" applications, 
not pluggable applications.  Any Pyramid application can be extended 
without forking it as long as its configuration statements have been 
composed into things that can be pulled in via "config.include".
composed into things that can be pulled in via ``config.include``.
It's also perfectly reasonable for a single developer or team to create a set
of interoperating components which can be enabled or disabled by using
@@ -1709,10 +1704,10 @@
Truly pluggable applications need to be created at a much higher level than a
web framework, as no web framework can offer enough constraints to really
make them "work out of the box".  They really need to plug into an
application, instead.  It would be a noble goal to build an application with
Pyramid that provides these constraints and which truly does offer a way to
plug in applications (Joomla, Plone, Drupal come to mind).
make them work out of the box.  They really need to plug into an application,
instead.  It would be a noble goal to build an application with Pyramid that
provides these constraints and which truly does offer a way to plug in
applications (Joomla, Plone, Drupal come to mind).
Pyramid Has Zope Things In It, So It's Too Complex
--------------------------------------------------