diff options
Diffstat (limited to 'docs/designdefense.rst')
| -rw-r--r-- | docs/designdefense.rst | 373 |
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 +++++++++ |
