summaryrefslogtreecommitdiff
path: root/docs/designdefense.rst
diff options
context:
space:
mode:
Diffstat (limited to 'docs/designdefense.rst')
-rw-r--r--docs/designdefense.rst373
1 files changed, 161 insertions, 212 deletions
diff --git a/docs/designdefense.rst b/docs/designdefense.rst
index 53b95b9d0..a7cc31d81 100644
--- a/docs/designdefense.rst
+++ b/docs/designdefense.rst
@@ -21,19 +21,20 @@ an acronym for "there is more than one way to do it").
it includes more than one way to resolve a URL to a :term:`view callable`:
via :term:`url dispatch` or :term:`traversal`. Multiple methods of
configuration exist: :term:`imperative configuration`, :term:`configuration
-decoration`, and :term:`ZCML`. It works with multiple different kinds of
-persistence and templating systems. And so on. However, the existence of
-most of these overlapping ways to do things are not without reason and
-purpose: we have a number of audiences to serve, and we believe that TIMTOWTI
-at the web framework level actually *prevents* a much more insidious and
-harmful set of duplication at higher levels in the Python web community.
+decoration`, and :term:`ZCML` (optionally via :term:`pyramid_zcml`). It works
+with multiple different kinds of persistence and templating systems. And so
+on. However, the existence of most of these overlapping ways to do things
+are not without reason and purpose: we have a number of audiences to serve,
+and we believe that TIMTOWTI at the web framework level actually *prevents* a
+much more insidious and harmful set of duplication at higher levels in the
+Python web community.
:app:`Pyramid` began its life as :mod:`repoze.bfg`, written by a team of
people with many years of prior :ref:`Zope` experience. The idea of
-:term:`traversal`, the usage of :term:`ZCML` and the way :term:`view lookup`
-works was stolen entirely from Zope. The authorization subsystem provided by
-:app:`Pyramid` is a derivative of Zope's. The idea that an application can
-be *extended* without forking is also a Zope derivative.
+:term:`traversal` and the way :term:`view lookup` works was stolen entirely
+from Zope. The authorization subsystem provided by :app:`Pyramid` is a
+derivative of Zope's. The idea that an application can be *extended* without
+forking is also a Zope derivative.
Implementations of these features were *required* to allow the :app:`Pyramid`
authors to build the bread-and-butter CMS-type systems for customers in the
@@ -164,12 +165,11 @@ 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,
-:term:`ZCML` of course. Sometimes. Or via imperative code. In this
-particular case, however, the registration of ``ISettings`` is made by the
-framework itself "under the hood": it's not present in any ZCML nor was it
-performed imperatively. This is extremely hard to comprehend. Problem
-number six.
+"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
@@ -323,17 +323,18 @@ the ZCA registry:
- Composability. A ZCA component registry can be populated imperatively, or
there's an existing mechanism to populate a registry via the use of a
- configuration file (ZCML). We didn't need to write a frontend from scratch
- to make use of configuration-file-driven registry population.
+ configuration file (ZCML, via :term:`pyramid_zcml`). We didn't need to
+ write a frontend from scratch to make use of configuration-file-driven
+ registry population.
- Pluggability. Use of the ZCA registry allows for framework extensibility
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 ZCML directive that registers several views "all
- at once", allowing app developers to use that ZCML directive as a "macro"
- in code that they write. This is somewhat of a differentiating feature
- from other (non-Zope) frameworks.
+ 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.
- Testability. Judicious use of the ZCA registry in framework code makes
testing that code slightly easier. Instead of using monkeypatching or
@@ -346,9 +347,8 @@ the ZCA registry:
for just these purposes. The ZCA registry contains optional C code for
this purpose which demonstrably has no (or very few) bugs.
-- Ecosystem. Many existing Zope packages can be used in
- :app:`Pyramid` with few (or no) changes due to our use of the ZCA
- registry and :term:`ZCML`.
+- Ecosystem. Many existing Zope packages can be used in :app:`Pyramid` with
+ few (or no) changes due to our use of the ZCA registry.
Conclusion
++++++++++
@@ -366,13 +366,12 @@ this is you, it's extremely hard to have a lot of sympathy for you. You'll
either need to get familiar with how we're using the ZCA registry or you'll
need to use only the documented APIs; that's why we document them as APIs.
-If you *extend* or *develop* :app:`Pyramid` (create new ZCML directives, use
-some of the more obscure "ZCML 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 understandable if you take the time to do some reading about
-it.
+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
+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
+understandable if you take the time to do some reading about it.
Pyramid Uses Interfaces Too Liberally
-------------------------------------
@@ -453,74 +452,11 @@ Pyramid "Encourages Use of ZCML"
:term:`Zope Component Architecture` registry that :app:`Pyramid` uses for
application configuration. Often people claim that Pyramid "needs ZCML".
-Quick answer: it doesn't. At least not anymore. In :mod:`repoze.bfg` (the
-predecessor to Pyramid) versions 1.0 and and 1.1, an application needed to
-possess a ZCML file for it to begin executing successfully. However,
-:mod:`repoze.bfg` 1.2 and greater (including :app:`Pyramid` 1.0) includes a
-completely imperative mode for all configuration. You will be able to make
-"single file" apps in this mode, which should help people who need to see
-everything done completely imperatively. For example, the very most basic
-:app:`Pyramid` "helloworld" program has become something like:
-
-.. code-block:: python
- :linenos:
-
- from webob import Response
- from paste.httpserver import serve
- from pyramid.config import Configurator
-
- 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)
-
-In this mode, no ZCML is required at all, nor any other sort of frameworky
-frontend to application configuration. Hopefully this mode will allow people
-who are used to doing everything imperatively feel more comfortable.
-
-Pyramid Uses ZCML; ZCML is XML and I Don't Like XML
----------------------------------------------------
-
-:term:`ZCML` is a configuration language in the XML syntax. Due to the
-"imperative configuration" feature (new in :mod:`repoze.bfg` 1.2), you don't
-need to use ZCML at all. But if you really do want to perform declarative
-configuration, perhaps because you want to build an extensible application,
-you may need to use and understand it.
-
-:term:`ZCML` contains elements that are mostly singleton tags that are
-called *declarations*. For an example:
-
-.. code-block:: xml
- :linenos:
-
- <route
- view=".views.my_view"
- path="/"
- name="root"
- />
-
-This declaration associates a :term:`view` with a route pattern.
-
-All :app:`Pyramid` declarations are singleton tags, unlike many other XML
-configuration systems. No XML *values* in ZCML are meaningful; it's always
-just XML tags and attributes. So in the very common case it's not really
-very much different than an otherwise "flat" configuration format like
-``.ini``, except a developer can *create* a directive that requires nesting
-(none of these exist in :app:`Pyramid` itself), and multiple "sections" can
-exist with the same "name" (e.g. two ``<route>`` declarations) must be able
-to exist simultaneously.
-
-You might think some other configuration file format would be better. But
-all configuration formats suck in one way or another. I personally don't
-think any of our lives would be markedly better if the declarative
-configuration format used by :app:`Pyramid` were YAML, JSON, or INI. It's
-all just plumbing that you mostly cut and paste once you've progressed 30
-minutes into your first project. Folks who tend to agitate for another
-configuration file format are folks that haven't yet spent that 30 minutes.
+It doesn't. In :app:`Pyramid` 1.0, ZCML doesn't ship as part of the core;
+instead it ships in the :term:`pyramid_zcml` add-on package, which is
+completely optional. No ZCML is required at all to use :app:`Pyramid`, nor
+any other sort of frameworky declarative frontend to application
+configuration.
.. _model_traversal_confusion:
@@ -541,14 +477,18 @@ Pyramid Does Traversal, And I Don't Like Traversal
In :app:`Pyramid`, :term:`traversal` is the act of resolving a URL path to a
:term:`resource` object in a resource tree. Some people are uncomfortable
-with this notion, and believe it is wrong.
-
-This is understandable. The people who believe it is wrong almost invariably
-have all of their data in a relational database. Relational databases aren't
+with this notion, and believe it is wrong. Thankfully, if you use
+:app:`Pyramid`, and you don't want to model your application in terms of a
+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
+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.
-Folks who deem traversal unilaterally "wrong" are neglecting to take into
-account that many persistence mechanisms *are* hierarchical. Examples
+However, folks who deem traversal unilaterally wrong are neglecting to take
+into account that many persistence mechanisms *are* hierarchical. Examples
include a filesystem, an LDAP database, a :term:`ZODB` (or another type of
graph) database, an XML document, and the Python module namespace. It is
often convenient to model the frontend to a hierarchical data store as a
@@ -566,28 +506,32 @@ resource tree is an excellent way to model this, even if the backend is a
relational database. In this situation, the resource tree a just a site
structure.
-But the point is ultimately moot. If you use :app:`Pyramid`, and you don't
-want to model your application in terms of a resource tree, you needn't use
-it at all. Instead, use :term:`URL dispatch` to map URL paths to views.
+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
+URL pattern matching.
+
+But the point is ultimately moot. If you don't want to use traversal, you
+needn't. Use URL dispatch instead.
Pyramid Does URL Dispatch, And I Don't Like URL Dispatch
--------------------------------------------------------
-In :app:`Pyramid`, :term:`url dispatch` is the act of resolving a
-URL path to a :term:`view` callable by performing pattern matching
-against some set of ordered route definitions. The route definitions
-are examined in order: the first pattern which matches is used to
-associate the URL with a view callable.
-
-Some people are uncomfortable with this notion, and believe it is
-wrong. These are usually people who are steeped deeply in
-:term:`Zope`. Zope does not provide any mechanism except
-:term:`traversal` to map code to URLs. This is mainly because Zope
-effectively requires use of :term:`ZODB`, which is a hierarchical
-object store. Zope also supports relational databases, but typically
-the code that calls into the database lives somewhere in the ZODB
-object graph (or at least is a :term:`view` related to a node in the
-object graph), and traversal is required to reach this code.
+In :app:`Pyramid`, :term:`url dispatch` is the act of resolving a URL path to
+a :term:`view` callable by performing pattern matching against some set of
+ordered route definitions. The route definitions are examined in order: the
+first pattern which matches is used to associate the URL with a view
+callable.
+
+Some people are uncomfortable with this notion, and believe it is wrong.
+These are usually people who are steeped deeply in :term:`Zope`. Zope does
+not provide any mechanism except :term:`traversal` to map code to URLs. This
+is mainly because Zope effectively requires use of :term:`ZODB`, which is a
+hierarchical object store. Zope also supports relational databases, but
+typically the code that calls into the database lives somewhere in the ZODB
+object graph (or at least is a :term:`view` related to a node in the object
+graph), and traversal is required to reach this code.
I'll argue that URL dispatch is ultimately useful, even if you want to use
traversal as well. You can actually *combine* URL dispatch and traversal in
@@ -604,20 +548,19 @@ present them with the default object view. There are other tricks you can
pull in these hybrid configurations if you're clever (and maybe masochistic)
too.
-Also, if you are a URL dispatch hater, if you should ever be asked to
-write an application that must use some legacy relational database
-structure, you might find that using URL dispatch comes in handy for
-one-off associations between views and URL paths. Sometimes it's just
-pointless to add a node to the object graph that effectively
-represents the entry point for some bit of code. You can just use a
-route and be done with it. If a route matches, a view associated with
-the route will be called; if no route matches, :app:`Pyramid` falls
-back to using traversal.
-
-But the point is ultimately moot. If you use :app:`Pyramid`, and
-you really don't want to use URL dispatch, you needn't use it at all.
-Instead, use :term:`traversal` exclusively to map URL paths to views,
-just like you do in :term:`Zope`.
+Also, if you are a URL dispatch hater, if you should ever be asked to write
+an application that must use some legacy relational database structure, you
+might find that using URL dispatch comes in handy for one-off associations
+between views and URL paths. Sometimes it's just pointless to add a node to
+the object graph that effectively represents the entry point for some bit of
+code. You can just use a route and be done with it. If a route matches, a
+view associated with the route will be called; if no route matches,
+:app:`Pyramid` falls back to using traversal.
+
+But the point is ultimately moot. If you use :app:`Pyramid`, and you really
+don't want to use URL dispatch, you needn't use it at all. Instead, use
+:term:`traversal` exclusively to map URL paths to views, just like you do in
+:term:`Zope`.
Pyramid Views Do Not Accept Arbitrary Keyword Arguments
-------------------------------------------------------
@@ -649,40 +592,24 @@ arguments to any method of a resource object found via traversal:
def aview(self, a, b, c=None):
return '%s %s %c' % (a, b, c)
-When this method is called as the result of being the published
-callable, the Zope request object's GET and POST namespaces are
-searched for keys which match the names of the positional and keyword
-arguments in the request, and the method is called (if possible) with
-its argument list filled with values mentioned therein. TurboGears
-and Pylons 1.X operate similarly.
-
-:app:`Pyramid` has neither of these features. :mod:`pyramid`
-view callables always accept only ``context`` and ``request`` (or just
-``request``), and no other arguments. The rationale: this argument
-specification matching done aggressively can be costly, and
-:app:`Pyramid` has performance as one of its main goals, so we've
-decided to make people obtain information by interrogating the request
-object for it in the view body instead of providing magic to do
-unpacking into the view argument list. The feature itself also just
-seems a bit like a gimmick. Getting the arguments you want explicitly
-from the request via getitem is not really very hard; it's certainly
-never a bottleneck for the author when he writes web apps.
-
-It is possible to replicate the Zope-like behavior in a view callable
-decorator, however, should you badly want something like it back. No
-such decorator currently exists. If you'd like to create one, Google
-for "zope mapply" and adapt the function you'll find to a decorator
-that pulls the argument mapping information out of the
-``request.params`` dictionary.
-
-A similar feature could be implemented to provide the Django-like
-behavior as a decorator by wrapping the view with a decorator that
-looks in ``request.matchdict``.
-
-It's possible at some point that :app:`Pyramid` will grow some form
-of argument matching feature (it would be simple to make it an
-always-on optional feature that has no cost unless you actually use
-it) for, but currently it has none.
+When this method is called as the result of being the published callable, the
+Zope request object's GET and POST namespaces are searched for keys which
+match the names of the positional and keyword arguments in the request, and
+the method is called (if possible) with its argument list filled with values
+mentioned therein. TurboGears and Pylons 1.X operate similarly.
+
+Out of the box, :app:`Pyramid` is configured to have none of these features.
+By default, :mod:`pyramid` view callables always accept only ``reqest`` and
+no other arguments. The rationale: this argument specification matching done
+aggressively can be costly, and :app:`Pyramid` has performance as one of its
+main goals, so we've decided to make people, by default, obtain information
+by interrogating the request object within the view callable body instead of
+providing magic to do unpacking into the view argument list.
+
+However, as of :app:`Pyramid` 1.0a9, user code can influence the way view
+callables are expected to be called, making it possible to compose a system
+out of view callables which are called with arbitrary arguments. See
+:ref:`using_a_view_mapper`.
Pyramid Provides Too Few "Rails"
--------------------------------
@@ -700,32 +627,54 @@ built using :app:`Pyramid` as a base. See also :ref:`apps_are_extensible`.
Pyramid Provides Too Many "Rails"
---------------------------------
-:app:`Pyramid` provides some features that other web frameworks do
-not. Most notably it has machinery which resolves a URL first to a
-:term:`context` before calling a view (which has the capability to
-accept the context in its argument list), and a declarative
-authorization system that makes use of this feature. Most other web
-frameworks besides :term:`Zope`, from which the pattern was stolen,
-have no equivalent core feature.
-
-We consider this an important feature for a particular class of
-applications (CMS-style applications, which the authors are often
-commissioned to write) that usually use :term:`traversal` against a
-persistent object graph. The object graph contains security
-declarations as :term:`ACL` objects.
-
-Having context-sensitive declarative security for individual objects
-in the object graph is simply required for this class of application.
-Other frameworks save for Zope just do not have this feature. This is
-one of the primary reasons that :app:`Pyramid` was actually
-written.
-
-If you don't like this, it doesn't mean you can't use
-:app:`Pyramid`. Just ignore this feature and avoid configuring an
-authorization or authentication policy and using ACLs. You can build
-"Pylons-1.X-style" applications using :app:`Pyramid` that use their own
-security model via decorators or plain-old-imperative logic in view
-code.
+: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:
+
+- An optional way to map URLs to code using :term:`traversal` which implies a
+ walk of a :term:`resource tree`.
+
+- The ability to aggregate Pyramid application configuration from multiple
+ sources using :meth:`pyramid.config.Configurator.include`.
+
+- View and subscriber registrations made using :term:`interface` objects
+ instead of class objects (e.g. :ref:`using_resource_interfaces`).
+
+- A declarative :term:`authorization` system.
+
+- Multiple separate I18N :term:`translation string` factories, each of which
+ 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
+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`
+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
+are owned and accessible by different sets of users. Interfaces are used to
+make unit testing and implementation substitutability easier.
+
+In a bespoke web application, usually there's a single canonical deployment,
+and therefore no possibility of multiple code forks. Extensibility is not
+required; the code is just changed in-place. Security requirements are often
+less granular. Using the features listed above will often be overkill for
+such an application.
+
+If you don't like these features, it doesn't mean you can't or shouldn't use
+:app:`Pyramid`. They are all optional, and a lot of time has been spent
+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
+requires extensibility because it must be deployed in multiple locations.
Pyramid Is Too Big
------------------
@@ -895,9 +844,9 @@ Pyramid Applications are Extensible; I Don't Believe In Application Extensibilit
Any :app:`Pyramid` application written obeying certain constraints is
*extensible*. This feature is discussed in the :app:`Pyramid` documentation
-chapters named :ref:`extending_chapter` and :ref:`advconf_narr`. It is made
-possible by the use of the :term:`Zope Component Architecture` and within
-:app:`Pyramid`.
+chapters named :ref:`extending_chapter` and :ref:`advconfig_narr`. It is
+made possible by the use of the :term:`Zope Component Architecture` and
+within :app:`Pyramid`.
"Extensible", in this context, means:
@@ -993,11 +942,11 @@ the :term:`Zope Component Architecture`, you can optionally use it to expose
other more domain-specific configuration plugpoints while developing an
application. The plugpoints you expose needn't be as coarse as the ones
provided automatically by :app:`Pyramid` itself. For example, you might
-compose your own :term:`ZCML` directive that configures a set of views for a
-prebaked purpose (e.g. ``restview`` or somesuch) , allowing other people to
-refer to that directive when they make declarations in the ``configure.zcml``
-of their customization package. There is a cost for this: the developer of
-an application that defines custom plugpoints for its deployers will need to
+compose your own directive that configures a set of views for a prebaked
+purpose (e.g. ``restview`` or somesuch) , allowing other people to refer to
+that directive when they make declarations in the ``includeme`` of their
+customization package. There is a cost for this: the developer of an
+application that defines custom plugpoints for its deployers will need to
understand the ZCA or he will need to develop his own similar extensibility
system.
@@ -1018,7 +967,7 @@ Challenge
:app:`Pyramid` performs automatic authorization checks only at :term:`view`
execution time. Zope 3 wraps context objects with a `security proxy
-<http://wiki.zope.org/zope3/WhatAreSecurityProxies>`, which causes Zope 3 to
+<http://wiki.zope.org/zope3/WhatAreSecurityProxies>`_, which causes Zope 3 to
do also security checks during attribute access. I like this, because it
means:
@@ -1459,7 +1408,7 @@ global*:
# this is executed if the request method was GET or the
# credentials were invalid
-The `Pylons 1.X <http://pylonshq.com>`_ web framework uses a similar
+The `Pylons 1.X <http://pylonsproject.org>`_ web framework uses a similar
strategy. It calls these things "Stacked Object Proxies", so, for purposes
of this discussion, I'll do so as well.
@@ -1604,10 +1553,10 @@ If you can understand this hello world program, you can use Pyramid:
app = config.make_wsgi_app()
serve(app)
-Pyramid has ~ 650 of documentation (printed), covering topics from the very
-basic to the most advanced. *Nothing* is left undocumented, quite literally.
-It also has an *awesome*, very helpful community. Visit the #repoze and/or
-#pylons IRC channels on freenode.net and see.
+Pyramid has ~ 650 pages of documentation (printed), covering topics from the
+very basic to the most advanced. *Nothing* is left undocumented, quite
+literally. It also has an *awesome*, very helpful community. Visit the
+#repoze and/or #pylons IRC channels on freenode.net and see.
Hate Zope
+++++++++