| | |
| | | Defending Pyramid's Design |
| | | ========================== |
| | | |
| | | From time to time, challenges to various aspects of :app:`Pyramid` |
| | | design are 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 obviously you can't please |
| | | everyone all of the time. |
| | | |
| | | Pyramid Has Zope Things In It, So It's Too Complex |
| | | -------------------------------------------------- |
| | | |
| | | On occasion, someone will feel compelled to post a mailing |
| | | list message that reads something like this: |
| | | |
| | | .. code-block:: text |
| | | |
| | | had a quick look at pyramid ... too complex to me and not really |
| | | understand for which benefits.. I feel should consider whether it's time |
| | | for me to step back to django .. I always hated zope (useless ?) |
| | | complexity and I love simple way of thinking |
| | | |
| | | (Paraphrased from a real email, actually.) |
| | | |
| | | Let's take this criticism point-by point. |
| | | |
| | | Too Complex |
| | | +++++++++++ |
| | | |
| | | - If you can understand this hello world program, you can use Pyramid: |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | from paste.httpserver import serve |
| | | from pyramid.config import Configurator |
| | | from pyramid.response import Response |
| | | |
| | | def hello_world(request): |
| | | return Response('Hello world!') |
| | | |
| | | if __name__ == '__main__': |
| | | config = Configurator() |
| | | config.add_view(hello_world) |
| | | app = config.make_wsgi_app() |
| | | serve(app) |
| | | |
| | | - Pyramid is 5,000 lines of runtime code. Pylons 1.0 has about 3,000 lines |
| | | of runtime code. Django has about 60,000 lines of runtime code. You'd |
| | | practically need to bend the laws of space and time for Django to be |
| | | simpler than Pyramid. |
| | | |
| | | - It has 600 or more pages of documentation (printed), covering topics from |
| | | the very basic to the most advanced. *Nothing* is left undocumented, quite |
| | | literally. |
| | | |
| | | - It has an *awesome*, very helpful community. Visit the #repoze and/or |
| | | #pylons IRC channels on freenode.net and see. |
| | | |
| | | Hate Zope |
| | | +++++++++ |
| | | |
| | | I'm sorry you feel that way. The Zope brand has certainly taken its share of |
| | | lumps over the years, and has a reputation for being insular and mysterious. |
| | | But the word "Zope" is literally quite meaningless without qualification. |
| | | What *part* of Zope do you hate? "Zope" is a brand, not a technology. |
| | | |
| | | If it's Zope2-the-web-framework, Pyramid is not that. The primary designers |
| | | and developers of Pyramid, if anyone, should know. We wrote Pyramid's |
| | | predecessor (:mod:`repoze.bfg`), in part, because *we* knew that Zope 2 had |
| | | usability issues and limitations. :mod:`repoze.bfg` (and now :app:`Pyramid`) |
| | | was written to address these issues. |
| | | |
| | | If it's Zope3-the-web-framework, Pyramid is *definitely* not that. Making |
| | | use of lots of Zope 3 technologies is territory already staked out by the |
| | | :term:`Grok` project. Save for the obvious fact that they're both web |
| | | frameworks, :mod:`Pyramid` is very, very different than Grok. Grok exposes |
| | | lots of Zope technologies to end users. On the other hand, if you need to |
| | | understand a Zope-only concept while using Pyramid, then we've failed on some |
| | | very basic axis. |
| | | |
| | | If it's just the word Zope: it's, charitably, only guilt by association. You |
| | | need to understand that just because a piece of software internally uses some |
| | | package named ``zope.foo``, it doesn't turn the piece of software that uses |
| | | it into "Zope". There is a lot of *great* software written that has the word |
| | | Zope in its name. Zope is not some sort of monolithic thing, and a lot of |
| | | its software is usable externally. |
| | | |
| | | Zope Is Useless |
| | | +++++++++++++++ |
| | | |
| | | It's not really the job of this document to defend Zope. But Zope has been |
| | | around for over 10 years and has an incredibly large, active community. If |
| | | you don't believe this, http://taichino.appspot.com/pypi_ranking/authors is |
| | | an eye-opening reality check. |
| | | |
| | | Love Simplicity |
| | | +++++++++++++++ |
| | | |
| | | Years of effort have gone into honing this package and its documentation to |
| | | make it as simple as humanly possible for developers to use. Everything is a |
| | | tradeoff, of course, and people have their own ideas about what "simple" is. |
| | | You may have a style difference if you believe Pyramid is complex. Its |
| | | developers obviously disagree. |
| | | From time to time, challenges to various aspects of :app:`Pyramid` design are |
| | | 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 |
| | | obviously you can't please everyone all of the time. |
| | | |
| | | Pyramid Uses A Zope Component Architecture ("ZCA") Registry |
| | | ----------------------------------------------------------- |
| | | |
| | | :app:`Pyramid` uses a :term:`Zope Component Architecture` (ZCA) |
| | | "component registry" as its :term:`application registry` under the |
| | | hood. This is a point of some contention. :app:`Pyramid` is of a |
| | | :term:`Zope` pedigree, so it was natural for its developers to use a |
| | | ZCA registry at its inception. However, we understand that using a |
| | | ZCA registry has issues and consequences, which we've attempted to |
| | | address as best we can. Here's an introspection about |
| | | :app:`Pyramid` use of a ZCA registry, and the trade-offs its usage |
| | | :app:`Pyramid` uses a :term:`Zope Component Architecture` (ZCA) "component |
| | | registry" as its :term:`application registry` under the hood. This is a |
| | | point of some contention. :app:`Pyramid` is of a :term:`Zope` pedigree, so |
| | | it was natural for its developers to use a ZCA registry at its inception. |
| | | However, we understand that using a ZCA registry has issues and consequences, |
| | | which we've attempted to address as best we can. Here's an introspection |
| | | about :app:`Pyramid` use of a ZCA registry, and the trade-offs its usage |
| | | involves. |
| | | |
| | | 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: |
| | | 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 |
| | |
| | | from zope.component import getUtility |
| | | 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 are a number of comprehension issues with the bit of code |
| | | above that are obvious. |
| | | 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 |
| | | are a number of comprehension issues with the bit of code above that are |
| | | obvious. |
| | | |
| | | First, what's a "utility"? Well, for the purposes of this discussion, |
| | | and for the purpose of the code above, it's just not very important. |
| | | If you really want to know, you can read `this |
| | | <http://www.muthukadan.net/docs/zca.html#utility>`_. However, still, |
| | | readers of such code need to understand the concept in order to parse |
| | | it. This is problem number one. |
| | | First, what's a "utility"? Well, for the purposes of this discussion, and |
| | | for the purpose of the code above, it's just not very important. If you |
| | | really want to know, you can read `this |
| | | <http://www.muthukadan.net/docs/zca.html#utility>`_. However, still, readers |
| | | of such code need to understand the concept in order to parse it. This is |
| | | 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 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. |
| | | 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 |
| | | 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. |
| | | |
| | | Third of all, what does the ``getUtility`` function do? It's |
| | | performing a lookup for the ``ISettings`` "utility" that should |
| | | return.. well, a utility. Note how we've already built up a |
| | | dependency on the understanding of an :term:`interface` and the |
| | | concept of "utility" to answer this question: a bad sign so far. Note |
| | | also that the answer is circular, a *really* bad sign. |
| | | Third of all, what does the ``getUtility`` function do? It's performing a |
| | | lookup for the ``ISettings`` "utility" that should return.. well, a utility. |
| | | Note how we've already built up a dependency on the understanding of an |
| | | :term:`interface` and the concept of "utility" to answer this question: a bad |
| | | sign so far. Note also that the answer is circular, a *really* bad sign. |
| | | |
| | | Fourth, where does ``getUtility`` look to get the data? Well, the |
| | | "component registry" of course. What's a component registry? Problem |
| | | number four. |
| | | Fourth, where does ``getUtility`` look to get the data? Well, the "component |
| | | registry" of course. What's a component registry? Problem number four. |
| | | |
| | | Fifth, assuming you buy that there's some magical registry hanging |
| | | around, where *is* this registry? *Homina homina*... "around"? |
| | | That's sort of the best answer in this context (a more specific answer |
| | | would require knowledge of internals). Can there be more than one |
| | | registry? Yes. So *which* registry does it find the registration in? |
| | | Well, the "current" registry of course. In terms of |
| | | :app:`Pyramid`, the current registry is a thread local variable. |
| | | Using an API that consults a thread local makes understanding how it |
| | | works non-local. |
| | | Fifth, assuming you buy that there's some magical registry hanging around, |
| | | where *is* this registry? *Homina homina*... "around"? That's sort of the |
| | | best answer in this context (a more specific answer would require knowledge |
| | | of internals). Can there be more than one registry? Yes. So *which* |
| | | registry does it find the registration in? Well, the "current" registry of |
| | | course. In terms of :app:`Pyramid`, the current registry is a thread local |
| | | 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. |
| | | 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. |
| | | |
| | | 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 the ZCA, even if he or she is already an expert |
| | | Python programmer and whom is an expert in the domain of web |
| | | applications. This is suboptimal. |
| | | 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 |
| | | the ZCA, even if he or she is already an expert Python programmer and whom is |
| | | an expert in the domain of web applications. This is suboptimal. |
| | | |
| | | Ameliorations |
| | | +++++++++++++ |
| | | |
| | | First, the primary amelioration: :app:`Pyramid` *does not expect |
| | | application developers to understand ZCA concepts or any of its APIs*. |
| | | If an *application* developer needs to understand a ZCA concept or API |
| | | during the creation of a :app:`Pyramid` application, we've failed |
| | | on some axis. |
| | | First, the primary amelioration: :app:`Pyramid` *does not expect application |
| | | developers to understand ZCA concepts or any of its APIs*. If an |
| | | *application* developer needs to understand a ZCA concept or API during the |
| | | creation of a :app:`Pyramid` application, we've failed on some axis. |
| | | |
| | | Instead, the framework hides the presence of the ZCA registry behind |
| | | special-purpose API functions that *do* use ZCA APIs. Take for |
| | | example the ``pyramid.security.authenticated_userid`` function, |
| | | which returns the userid present in the current request or ``None`` if |
| | | no userid is present in the current request. The application |
| | | developer calls it like so: |
| | | special-purpose API functions that *do* use ZCA APIs. Take for example the |
| | | ``pyramid.security.authenticated_userid`` function, which returns the userid |
| | | present in the current request or ``None`` if no userid is present in the |
| | | current request. The application developer calls it like so: |
| | | |
| | | .. ignore-next-block |
| | | .. code-block:: python |
| | |
| | | return None |
| | | return policy.authenticated_userid(request) |
| | | |
| | | Using such wrappers, we strive to always hide the ZCA API from |
| | | application developers. Application developers should just never know |
| | | about the ZCA API: they should call a Python function with some object |
| | | germane to the domain as an argument, and it should returns a result. |
| | | A corollary that follows is that any reader of an application that has |
| | | been written using :app:`Pyramid` needn't understand the ZCA API |
| | | either. |
| | | Using such wrappers, we strive to always hide the ZCA API from application |
| | | developers. Application developers should just never know about the ZCA API: |
| | | they should call a Python function with some object germane to the domain as |
| | | an argument, and it should returns a result. A corollary that follows is |
| | | that any reader of an application that has been written using :app:`Pyramid` |
| | | 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 understand the minutiae of the 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 *utilities*. :app:`Pyramid` uses |
| | | the ZCA as an implementation detail, not as a feature which is exposed |
| | | to end users. |
| | | Hiding the ZCA API from application developers and code readers is a form of |
| | | enhancing "domain specificity". No application developer wants to need to |
| | | understand the minutiae of the 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 |
| | | *utilities*. :app:`Pyramid` uses the ZCA as an implementation detail, not as |
| | | a feature which is exposed to end users. |
| | | |
| | | However, unlike application developers, *framework developers*, |
| | | including people who want to override :app:`Pyramid` functionality |
| | | via preordained framework plugpoints like traversal or view lookup |
| | | *must* understand the ZCA registry API. |
| | | However, unlike application developers, *framework developers*, including |
| | | people who want to override :app:`Pyramid` functionality via preordained |
| | | framework plugpoints like traversal or view lookup *must* understand the ZCA |
| | | registry API. |
| | | |
| | | :app:`Pyramid` framework developers were so concerned about |
| | | conceptual load issues of the ZCA registry API for framework |
| | | developers that a `replacement registry implementation |
| | | <http://svn.repoze.org/repoze.component/trunk>`_ named |
| | | :mod:`repoze.component` was actually developed. Though this package |
| | | has a registry implementation which is fully functional and |
| | | well-tested, and its API is much nicer than the ZCA registry API, work |
| | | on it was largely abandoned and it is not used in :app:`Pyramid`. |
| | | We continued to use a ZCA registry within :app:`Pyramid` because it |
| | | ultimately proved a better fit. |
| | | :app:`Pyramid` framework developers were so concerned about conceptual load |
| | | issues of the ZCA registry API for framework developers that a `replacement |
| | | registry implementation <http://svn.repoze.org/repoze.component/trunk>`_ |
| | | named :mod:`repoze.component` was actually developed. Though this package |
| | | has a registry implementation which is fully functional and well-tested, and |
| | | its API is much nicer than the ZCA registry API, work on it was largely |
| | | abandoned and it is not used in :app:`Pyramid`. We continued to use a ZCA |
| | | registry within :app:`Pyramid` because it ultimately proved a better fit. |
| | | |
| | | .. note:: We continued using ZCA registry rather than disusing it in |
| | | favor of using the registry implementation in |
| | |
| | | that allowed for this functionality seemed like it was just |
| | | reinventing the wheel. |
| | | |
| | | Making framework developers and extenders understand the ZCA registry |
| | | API is a trade-off. We (the :app:`Pyramid` developers) like the |
| | | features that the ZCA registry gives us, and we have long-ago borne |
| | | the weight of understanding what it does and how it works. The |
| | | authors of :app:`Pyramid` understand the ZCA deeply and can read |
| | | code that uses it as easily as any other code. |
| | | Making framework developers and extenders understand the ZCA registry API is |
| | | a trade-off. We (the :app:`Pyramid` developers) like the features that the |
| | | ZCA registry gives us, and we have long-ago borne the weight of understanding |
| | | what it does and how it works. The authors of :app:`Pyramid` understand the |
| | | ZCA deeply and can read code that uses it as easily as any other code. |
| | | |
| | | But we recognize that developers who my want to extend the framework |
| | | are not as comfortable with the ZCA registry API as the original |
| | | developers are with it. So, for the purposes of being kind to |
| | | third-party :app:`Pyramid` framework developers in, we've drawn |
| | | some lines in the sand. |
| | | But we recognize that developers who my want to extend the framework are not |
| | | as comfortable with the ZCA registry API as the original developers are with |
| | | 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 ``zope.component.getUtility`` and ``zope.component.getAdapter`` |
| | | the exception instead of the rule. So instead of: |
| | | 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: |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | from pyramid.interfaces import IAuthenticationPolicy |
| | | from zope.component import getUtility |
| | | policy = getUtility(IAuthenticationPolicy) |
| | | from pyramid.interfaces import IAuthenticationPolicy |
| | | from zope.component import getUtility |
| | | policy = getUtility(IAuthenticationPolicy) |
| | | |
| | | :app:`Pyramid` code will usually do: |
| | | :app:`Pyramid` code will usually do: |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | from pyramid.interfaces import IAuthenticationPolicy |
| | | from pyramid.threadlocal import get_current_registry |
| | | registry = get_current_registry() |
| | | policy = registry.getUtility(IAuthenticationPolicy) |
| | | from pyramid.interfaces import IAuthenticationPolicy |
| | | from pyramid.threadlocal import get_current_registry |
| | | registry = get_current_registry() |
| | | policy = registry.getUtility(IAuthenticationPolicy) |
| | | |
| | | While the latter is more verbose, it also arguably makes it more |
| | | obvious what's going on. All of the :app:`Pyramid` core code uses |
| | | this pattern rather than the ZCA global API. |
| | | |
| | | #) We've turned the component registry used by :app:`Pyramid` into |
| | | something that is accessible using the plain old dictionary API |
| | | (like the :mod:`repoze.component` API). For example, the snippet |
| | | of code in the problem section above was: |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | from pyramid.interfaces import ISettings |
| | | from zope.component import getUtility |
| | | settings = getUtility(ISettings) |
| | | |
| | | In a better world, we might be able to spell this as: |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | from pyramid.threadlocal import get_current_registry |
| | | |
| | | registry = get_current_registry() |
| | | settings = registry['settings'] |
| | | |
| | | In this world, we've removed the need to understand utilities and |
| | | interfaces, because we've disused them in favor of a plain dictionary |
| | | lookup. We *haven't* removed the need to understand the concept of a |
| | | *registry*, but for the purposes of this example, it's simply a |
| | | dictionary. We haven't killed off the concept of a thread local |
| | | either. Let's kill off thread locals, pretending to want to do this |
| | | in some code that has access to the :term:`request`: |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | registry = request.registry |
| | | settings = registry['settings'] |
| | | |
| | | In *this* world, we've reduced the conceptual problem to understanding |
| | | attributes and the dictionary API. Every Python programmer knows |
| | | these things, even framework programmers. |
| | | |
| | | While :app:`Pyramid` still uses some suboptimal unnamed utility |
| | | registrations, future versions of it will where possible disuse these |
| | | things in favor of straight dictionary assignments and lookups, as |
| | | demonstrated above, to be kinder to new framework developers. We'll |
| | | continue to seek ways to reduce framework developer cognitive load. |
| | | While the latter is more verbose, it also arguably makes it more obvious |
| | | what's going on. All of the :app:`Pyramid` core code uses this pattern |
| | | rather than the ZCA global API. |
| | | |
| | | Rationale |
| | | +++++++++ |
| | | |
| | | Here are the main rationales involved in the :app:`Pyramid` |
| | | decision to use the ZCA registry: |
| | | Here are the main rationales involved in the :app:`Pyramid` decision to use |
| | | the ZCA registry: |
| | | |
| | | - Pedigree. A nontrivial part of the answer to this question is |
| | | "pedigree". Much of the design of :app:`Pyramid` is stolen |
| | | directly from :term:`Zope`. Zope uses the ZCA registry to do a |
| | | number of tricks. :app:`Pyramid` mimics these tricks, and, |
| | | because the ZCA registry works well for that set of tricks, |
| | | :app:`Pyramid` uses it for the same purposes. For example, the |
| | | way that :app:`Pyramid` maps a :term:`request` to a :term:`view |
| | | callable` is lifted almost entirely from Zope. The ZCA registry |
| | | plays an important role in the particulars of how this request to |
| | | view mapping is done. |
| | | - Pedigree. A nontrivial part of the answer to this question is "pedigree". |
| | | Much of the design of :app:`Pyramid` is stolen directly from :term:`Zope`. |
| | | Zope uses the ZCA registry to do a number of tricks. :app:`Pyramid` mimics |
| | | these tricks, and, because the ZCA registry works well for that set of |
| | | tricks, :app:`Pyramid` uses it for the same purposes. For example, the way |
| | | that :app:`Pyramid` maps a :term:`request` to a :term:`view callable` using |
| | | :term:`traversal` is lifted almost entirely from Zope. The ZCA registry |
| | | plays an important role in the particulars of how this request to view |
| | | 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`. |
| | | - 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`. |
| | | |
| | | - 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 not the same as "a |
| | | process", multiple independently configured copies of the same |
| | | :app:`Pyramid` application are capable of running in the same |
| | | - 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 |
| | | not the same as "a process", multiple independently configured copies of |
| | | the same :app:`Pyramid` application are capable of running in the same |
| | | process space. |
| | | |
| | | - 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. |
| | | - 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. |
| | | |
| | | - 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. |
| | | - 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. |
| | | |
| | | - Testability. Judicious use of the ZCA registry in framework code |
| | | makes testing that code slightly easier. Instead of using |
| | | monkeypatching or other facilities to register mock objects for |
| | | testing, we inject dependencies via ZCA registrations and then use |
| | | lookups in the code find our mock objects. |
| | | - Testability. Judicious use of the ZCA registry in framework code makes |
| | | testing that code slightly easier. Instead of using monkeypatching or |
| | | other facilities to register mock objects for testing, we inject |
| | | dependencies via ZCA registrations and then use lookups in the code find |
| | | our mock objects. |
| | | |
| | | - Speed. The ZCA registry is very fast for a specific set of complex |
| | | lookup scenarios that :app:`Pyramid` uses, having been optimized |
| | | through the years for just these purposes. The ZCA registry |
| | | contains optional C code for this purpose which demonstrably has no |
| | | (or very few) bugs. |
| | | - Speed. The ZCA registry is very fast for a specific set of complex lookup |
| | | scenarios that :app:`Pyramid` uses, having been optimized through the years |
| | | 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 |
| | |
| | | |
| | | If you only *develop applications* using :app:`Pyramid`, there's not much to |
| | | complain about here. You just should never need to understand the ZCA |
| | | registry or even know about its presence: use documented :app:`Pyramid` APIs |
| | | instead. However, you may be an application developer who doesn't read API |
| | | documentation because it's unmanly. Instead you read the raw source code, and |
| | | because you haven't read the documentation, you don't know what functions, |
| | | classes, and methods even *form* the :app:`Pyramid` API. As a result, you've |
| | | now written code that uses internals and you've painted yourself into a |
| | | conceptual corner as a result of needing to wrestle with some ZCA-using |
| | | implementation detail. If 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. |
| | | registry API: use documented :app:`Pyramid` APIs instead. However, you may |
| | | be an application developer who doesn't read API documentation because it's |
| | | unmanly. Instead you read the raw source code, and because you haven't read |
| | | the documentation, you don't know what functions, classes, and methods even |
| | | *form* the :app:`Pyramid` API. As a result, you've now written code that |
| | | uses internals and you've painted yourself into a conceptual corner as a |
| | | result of needing to wrestle with some ZCA-using implementation detail. If |
| | | 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`, |
| | |
| | | <http://www.coactivate.org/projects/topp-engineering/blog/2008/10/20/what-bothers-me-about-the-component-architecture/>`_, |
| | | Ian Bicking asserts that the way :mod:`repoze.bfg` used a Zope interface to |
| | | represent an HTTP request method added too much indirection for not enough |
| | | gain. We agreed in general, and for this reason, :mod:`repoze.bfg` version 1.1 |
| | | (and subsequent versions including :app:`Pyramid` 1.0+) added :term:`view |
| | | gain. We agreed in general, and for this reason, :mod:`repoze.bfg` version |
| | | 1.1 (and subsequent versions including :app:`Pyramid` 1.0+) added :term:`view |
| | | predicate` and :term:`route predicate` modifiers to view configuration. |
| | | Predicates are request-specific (or :term:`context` -specific) matching |
| | | narrowers which don't use interfaces. Instead, each predicate uses a |
| | | domain-specific string as a match value. |
| | | |
| | | For example, to write a view configuration which matches only requests |
| | | with the ``POST`` HTTP request method, you might write a ``@view_config`` |
| | | For example, to write a view configuration which matches only requests with |
| | | the ``POST`` HTTP request method, you might write a ``@view_config`` |
| | | decorator which mentioned the ``request_method`` predicate: |
| | | |
| | | .. code-block:: python |
| | |
| | | return 'POSTed' |
| | | |
| | | You might further narrow the matching scenario by adding an ``accept`` |
| | | predicate that narrows matching to something that accepts a JSON |
| | | response: |
| | | predicate that narrows matching to something that accepts a JSON response: |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | |
| | | def post_view(request): |
| | | return 'POSTed' |
| | | |
| | | Such a view would only match when the request indicated that HTTP |
| | | request method was ``POST`` and that the remote user agent passed |
| | | Such a view would only match when the request indicated that HTTP request |
| | | method was ``POST`` and that the remote user agent passed |
| | | ``application/json`` (or, for that matter, ``application/*``) in its |
| | | ``Accept`` request header. |
| | | |
| | |
| | | -------------------------------- |
| | | |
| | | :term:`ZCML` is a configuration language that can be used to configure the |
| | | :term:`Zope Component Architecture` registry that :app:`Pyramid` uses as its |
| | | :term:`Zope Component Architecture` registry that :app:`Pyramid` uses for |
| | | application configuration. Often people claim that Pyramid "needs ZCML". |
| | | |
| | | Quick answer: well, 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 |
| | | 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 |
| | |
| | | app = config.make_wsgi_app() |
| | | serve(app) |
| | | |
| | | In this mode, no ZCML is required at all. Hopefully this mode will allow |
| | | people who are used to doing everything imperatively feel more comfortable. |
| | | 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 |
| | | --------------------------------------------------- |
| | |
| | | --------------------------------- |
| | | |
| | | This is true. At the time of this writing, the total number of Python |
| | | package distributions that :app:`Pyramid` depends upon transitively |
| | | is 18 if you use Python 2.6 or 2.7, or 16 if you use Python 2.4 or |
| | | 2.5. This is a lot more than zero package distribution dependencies: |
| | | a metric which various Python microframeworks and Django boast. |
| | | package distributions that :app:`Pyramid` depends upon transitively is 18 if |
| | | you use Python 2.6 or 2.7, or 16 if you use Python 2.4 or 2.5. This is a lot |
| | | more than zero package distribution dependencies: a metric which various |
| | | Python microframeworks and Django boast. |
| | | |
| | | The :mod:`zope.component` and :mod:`zope.configuration` packages on |
| | | which :app:`Pyramid` depends have transitive dependencies on |
| | | several other packages (:mod:`zope.schema`, :mod:`zope.i18n`, |
| | | :mod:`zope.event`, :mod:`zope.interface`, :mod:`zope.deprecation`, |
| | | :mod:`zope.i18nmessageid`). We've been working with the Zope |
| | | community to try to collapse and untangle some of these dependencies. |
| | | We'd prefer that these packages have fewer packages as transitive |
| | | dependencies, and that much of the functionality of these packages was |
| | | moved into a smaller *number* of packages. |
| | | The :mod:`zope.component` and :mod:`zope.configuration` packages on which |
| | | :app:`Pyramid` depends have transitive dependencies on several other packages |
| | | (:mod:`zope.schema`, :mod:`zope.i18n`, :mod:`zope.event`, |
| | | :mod:`zope.interface`, :mod:`zope.deprecation`, :mod:`zope.i18nmessageid`). |
| | | We've been working with the Zope community to try to collapse and untangle |
| | | some of these dependencies. We'd prefer that these packages have fewer |
| | | packages as transitive dependencies, and that much of the functionality of |
| | | these packages was moved into a smaller *number* of packages. |
| | | |
| | | :app:`Pyramid` also has its own direct dependencies, such as :term:`Paste`, |
| | | :term:`Chameleon`, :term:`Mako` and :term:`WebOb`, and some of these in turn |
| | | have their own transitive dependencies. |
| | | |
| | | It should be noted that :app:`Pyramid` is positively lithe compared |
| | | to :term:`Grok`, a different Zope-based framework. As of this |
| | | writing, in its default configuration, Grok has 126 package |
| | | distribution dependencies. The number of dependencies required by |
| | | :app:`Pyramid` is many times fewer than Grok (or Zope itself, upon |
| | | which Grok is based). :app:`Pyramid` has a number of package |
| | | distribution dependencies comparable to similarly-targeted frameworks |
| | | such as Pylons 1.X. |
| | | It should be noted that :app:`Pyramid` is positively lithe compared to |
| | | :term:`Grok`, a different Zope-based framework. As of this writing, in its |
| | | default configuration, Grok has 126 package distribution dependencies. The |
| | | number of dependencies required by :app:`Pyramid` is many times fewer than |
| | | Grok (or Zope itself, upon which Grok is based). :app:`Pyramid` has a number |
| | | of package distribution dependencies comparable to similarly-targeted |
| | | frameworks such as Pylons 1.X. |
| | | |
| | | We try not to reinvent too many wheels (at least the ones that don't |
| | | need reinventing), and this comes at the cost of some number of |
| | | dependencies. However, "number of package distributions" is just not |
| | | a terribly great metric to measure complexity. For example, the |
| | | :mod:`zope.event` distribution on which :app:`Pyramid` depends has |
| | | a grand total of four lines of runtime code. As noted above, we're |
| | | continually trying to agitate for a collapsing of these sorts of |
| | | packages into fewer distribution files. |
| | | We try not to reinvent too many wheels (at least the ones that don't need |
| | | reinventing), and this comes at the cost of some number of dependencies. |
| | | However, "number of package distributions" is just not a terribly great |
| | | metric to measure complexity. For example, the :mod:`zope.event` |
| | | distribution on which :app:`Pyramid` depends has a grand total of four lines |
| | | of runtime code. As noted above, we're continually trying to agitate for a |
| | | collapsing of these sorts of packages into fewer distribution files. |
| | | |
| | | Pyramid "Cheats" To Obtain Speed |
| | | -------------------------------- |
| | | |
| | | Complaints have been lodged by other web framework authors at various |
| | | times that :app:`Pyramid` "cheats" to gain performance. One |
| | | claimed cheating mechanism is our use (transitively) of the C |
| | | extensions provided by :mod:`zope.interface` to do fast lookups. |
| | | Another claimed cheating mechanism is the religious avoidance of |
| | | extraneous function calls. |
| | | Complaints have been lodged by other web framework authors at various times |
| | | that :app:`Pyramid` "cheats" to gain performance. One claimed cheating |
| | | mechanism is our use (transitively) of the C extensions provided by |
| | | :mod:`zope.interface` to do fast lookups. Another claimed cheating mechanism |
| | | is the religious avoidance of extraneous function calls. |
| | | |
| | | If there's such a thing as cheating to get better performance, we want |
| | | to cheat as much as possible. We optimize :app:`Pyramid` |
| | | aggressively. This comes at a cost: the core code has sections that |
| | | could be expressed more readably. As an amelioration, we've commented |
| | | these sections liberally. |
| | | If there's such a thing as cheating to get better performance, we want to |
| | | cheat as much as possible. We optimize :app:`Pyramid` aggressively. This |
| | | comes at a cost: the core code has sections that could be expressed more |
| | | readably. As an amelioration, we've commented these sections liberally. |
| | | |
| | | Pyramid Gets Its Terminology Wrong ("MVC") |
| | | ------------------------------------------ |
| | | |
| | | "I'm a MVC web framework user, and I'm confused. :app:`Pyramid` |
| | | calls the controller a view! And it doesn't have any controllers." |
| | | "I'm a MVC web framework user, and I'm confused. :app:`Pyramid` calls the |
| | | controller a view! And it doesn't have any controllers." |
| | | |
| | | If you are in this camp, you might have come to expect things about how your |
| | | existing "MVC" framework uses its terminology. For example, you probably |
| | |
| | | "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 desktop GUI platforms by using similar terminology, and to |
| | | provide some frame of reference for how various components in the |
| | | common web framework might hang together. But in the opinion of the |
| | | author, "MVC" doesn't match the web very well in general. Quoting from |
| | | the `Model-View-Controller Wikipedia entry |
| | | People very much want to give web applications the same properties as common |
| | | desktop GUI platforms by using similar terminology, and to provide some frame |
| | | of reference for how various components in the common web framework might |
| | | hang together. But in the opinion of the author, "MVC" doesn't match the web |
| | | very well in general. Quoting from the `Model-View-Controller Wikipedia entry |
| | | <http://en.wikipedia.org/wiki/Model–view–controller>`_: |
| | | |
| | | .. code-block:: text |
| | |
| | | The user interface waits for further user interactions, which |
| | | restarts the cycle. |
| | | |
| | | To the author, it seems as if someone edited this Wikipedia |
| | | definition, 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 meet even this falsely generic |
| | | definition. |
| | | To the author, it seems as if someone edited this Wikipedia definition, |
| | | 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 |
| | | meet even this falsely generic definition. |
| | | |
| | | For example, do your templates (views) always query models directly as |
| | | is claimed in "note that the view gets its own data from the model"? |
| | | Probably not. My "controllers" tend to do this, massaging the data for |
| | | easier use by the "view" (template). What do you do when your |
| | | "controller" returns JSON? Do your controllers use a template to |
| | | generate JSON? If not, what's the "view" then? Most MVC-style GUI web |
| | | frameworks have some sort of event system hooked up that lets the view |
| | | detect when the model changes. The web just has no such facility in |
| | | its current form: it's effectively pull-only. |
| | | For example, do your templates (views) always query models directly as is |
| | | claimed in "note that the view gets its own data from the model"? Probably |
| | | not. My "controllers" tend to do this, massaging the data for easier use by |
| | | the "view" (template). What do you do when your "controller" returns JSON? Do |
| | | your controllers use a template to generate JSON? If not, what's the "view" |
| | | then? Most MVC-style GUI web frameworks have some sort of event system |
| | | hooked up that lets the view detect when the model changes. The web just has |
| | | no such facility in its current form: it's effectively pull-only. |
| | | |
| | | So, in the interest of not mistaking desire with reality, and instead of |
| | | trying to jam the square peg that is the web into the round hole of "MVC", we |
| | |
| | | Pyramid Applications are Extensible; I Don't Believe In Application Extensibility |
| | | --------------------------------------------------------------------------------- |
| | | |
| | | Any :app:`Pyramid` application written obeying certain constraints |
| | | is *extensible*. This feature is discussed in the :app:`Pyramid` |
| | | documentation chapter named :ref:`extending_chapter`. It is made |
| | | possible by the use of the :term:`Zope Component Architecture` and |
| | | :term:`ZCML` within :app:`Pyramid`. |
| | | 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`. |
| | | |
| | | "Extensible", in this context, means: |
| | | |
| | |
| | | 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 application for each deployment to override behavior is more |
| | | reasonable. Much discussion about version control branching and |
| | | merging typically ensues. |
| | | 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. |
| | | |
| | | It's clear that making every application extensible isn't required. |
| | | The 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 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 plugpoints than it |
| | | is to continually keep each software branch of the system in sync with |
| | | some upstream source: the upstream developers may change code in such |
| | | a way that your changes to the same codebase conflict with theirs in |
| | | fiddly, trivial ways. Merging such changes repeatedly over the |
| | | lifetime of a deployment can be difficult and time consuming, and it's |
| | | often useful to be able to modify an application for a particular |
| | | deployment in a less invasive way. |
| | | It's clear that making every application extensible isn't required. The |
| | | 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 |
| | | 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 |
| | | plugpoints than it is to continually keep each software branch of the system |
| | | in sync with some upstream source: the upstream developers may change code in |
| | | such a way that your changes to the same codebase conflict with theirs in |
| | | fiddly, trivial ways. Merging such changes repeatedly over the lifetime of a |
| | | deployment can be difficult and time consuming, and it's often useful to be |
| | | able to modify an application for a particular deployment in a less invasive |
| | | way. |
| | | |
| | | If you don't want to think about :app:`Pyramid` application |
| | | extensibility at all, you needn't. You can ignore extensibility |
| | | entirely. However, if you follow the set of rules defined in |
| | | :ref:`extending_chapter`, you don't need to *make* your application |
| | | extensible: any application you write in the framework just *is* |
| | | automatically extensible at a basic level. The mechanisms that |
| | | deployers use to extend it will be necessarily coarse: typically, |
| | | views, routes, and resources will be capable of being overridden, |
| | | usually via :term:`ZCML`. But for most minor (and even some major) |
| | | customizations, these are often the only override plugpoints |
| | | necessary: if the application doesn't do exactly what the deployment |
| | | requires, it's often possible for a deployer to override a view, |
| | | route, or resource and quickly make it do what he or she wants it to |
| | | do in ways *not necessarily anticipated by the original developer*. |
| | | Here are some example scenarios demonstrating the benefits of such a |
| | | feature. |
| | | If you don't want to think about :app:`Pyramid` application extensibility at |
| | | all, you needn't. You can ignore extensibility entirely. However, if you |
| | | follow the set of rules defined in :ref:`extending_chapter`, you don't need |
| | | to *make* your application extensible: any application you write in the |
| | | framework just *is* automatically extensible at a basic level. The |
| | | mechanisms that deployers use to extend it will be necessarily coarse: |
| | | typically, views, routes, and resources will be capable of being |
| | | overridden. But for most minor (and even some major) customizations, these |
| | | are often the only override plugpoints necessary: if the application doesn't |
| | | do exactly what the deployment requires, it's often possible for a deployer |
| | | to override a view, route, or resource and quickly make it do what he or she |
| | | wants it to do in ways *not necessarily anticipated by the original |
| | | developer*. Here are some example scenarios demonstrating the benefits of |
| | | such a feature. |
| | | |
| | | - If a deployment needs a different styling, the deployer may override |
| | | the main template and the CSS in a separate Python package which |
| | | defines overrides. |
| | | - If a deployment needs a different styling, the deployer may override the |
| | | main template and the CSS in a separate Python package which defines |
| | | overrides. |
| | | |
| | | - If a deployment needs an application page to do something |
| | | differently needs it to expose more or different information, the |
| | | deployer may override the view that renders the page within a |
| | | separate Python package. |
| | | - If a deployment needs an application page to do something differently needs |
| | | it to expose more or different information, the deployer may override the |
| | | view that renders the page within a separate Python package. |
| | | |
| | | - If a deployment needs an additional feature, the deployer may add a |
| | | view to the override package. |
| | | - If a deployment needs an additional feature, the deployer may add a view to |
| | | the override package. |
| | | |
| | | As long as the fundamental design of the upstream package doesn't |
| | | change, these types of modifications often survive across many |
| | | releases of the upstream package without needing to be revisited. |
| | | As long as the fundamental design of the upstream package doesn't change, |
| | | these types of modifications often survive across many releases of the |
| | | upstream package without needing to be revisited. |
| | | |
| | | Extending an application externally is not a panacea, and carries a |
| | | set of risks similar to branching and merging: sometimes major changes |
| | | upstream will cause you to need to revisit and update some of your |
| | | modifications. But you won't regularly need to deal wth meaningless |
| | | textual merge conflicts that trivial changes to upstream packages |
| | | often entail when it comes time to update the upstream package, |
| | | because if you extend an application externally, there just is no |
| | | textual merge done. Your modifications will also, for whatever its |
| | | worth, be contained in one, canonical, well-defined place. |
| | | Extending an application externally is not a panacea, and carries a set of |
| | | risks similar to branching and merging: sometimes major changes upstream will |
| | | cause you to need to revisit and update some of your modifications. But you |
| | | won't regularly need to deal wth meaningless textual merge conflicts that |
| | | trivial changes to upstream packages often entail when it comes time to |
| | | update the upstream package, because if you extend an application externally, |
| | | there just is no textual merge done. Your modifications will also, for |
| | | whatever its worth, be contained in one, canonical, well-defined place. |
| | | |
| | | Branching an application and continually merging in order to get new |
| | | features and bugfixes is clearly useful. You can do that with a |
| | | :app:`Pyramid` application just as usefully as you can do it with |
| | | any application. But deployment of an application written in |
| | | :app:`Pyramid` makes it possible to avoid the need for this even if |
| | | the application doesn't define any plugpoints ahead of time. It's |
| | | possible that promoters of competing web frameworks dismiss this |
| | | feature in favor of branching and merging because applications written |
| | | in their framework of choice aren't extensible out of the box in a |
| | | Branching an application and continually merging in order to get new features |
| | | and bugfixes is clearly useful. You can do that with a :app:`Pyramid` |
| | | application just as usefully as you can do it with any application. But |
| | | deployment of an application written in :app:`Pyramid` makes it possible to |
| | | avoid the need for this even if the application doesn't define any plugpoints |
| | | ahead of time. It's possible that promoters of competing web frameworks |
| | | dismiss this feature in favor of branching and merging because applications |
| | | written in their framework of choice aren't extensible out of the box in a |
| | | comparably fundamental way. |
| | | |
| | | While :app:`Pyramid` application are fundamentally extensible even |
| | | if you don't write them with specific extensibility in mind, if you're |
| | | moderately adventurous, you can also take it a step further. If you |
| | | learn more about 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 understand the ZCA or he |
| | | will need to develop his own similar extensibility system. |
| | | While :app:`Pyramid` application are fundamentally extensible even if you |
| | | don't write them with specific extensibility in mind, if you're moderately |
| | | adventurous, you can also take it a step further. If you learn more about |
| | | 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 |
| | | understand the ZCA or he will need to develop his own similar extensibility |
| | | system. |
| | | |
| | | Ultimately, any argument about whether the extensibility features lent |
| | | to applications by :app:`Pyramid` are "good" or "bad" is somewhat |
| | | 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 plugpoints entirely, and |
| | | instead use version control branching and merging to manage |
| | | application deployment modifications instead, as if you were deploying |
| | | Ultimately, any argument about whether the extensibility features lent to |
| | | applications by :app:`Pyramid` are "good" or "bad" is somewhat 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 |
| | | plugpoints entirely, and instead use version control branching and merging to |
| | | manage application deployment modifications instead, as if you were deploying |
| | | an application written using any other web framework. |
| | | |
| | | Zope 3 Enforces "TTW" Authorization Checks By Default; Pyramid Does Not |
| | |
| | | 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 do also security checks during attribute |
| | | access. I like this, because it means: |
| | | :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 |
| | | do also security checks during attribute access. I like this, because it |
| | | means: |
| | | |
| | | #) When I use the security proxy machinery, I can have a view that |
| | | conditionally displays certain HTML elements (like form fields) or |
| | |
| | | Defense |
| | | +++++++ |
| | | |
| | | :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. |
| | | :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. |
| | | |
| | | 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. |
| | | 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. |
| | | 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. |
| | | |
| | | .. _microframeworks_smaller_hello_world: |
| | | |
| | | Microframeworks Have Smaller Hello World Programs |
| | | ------------------------------------------------- |
| | | |
| | | Self-described "microframeworks" exist: `Bottle |
| | | <http://bottle.paws.de>`_ and `Flask <http://flask.pocoo.org/>`_ are |
| | | two that are becoming popular. `Bobo <http://bobo.digicool.com/>`_ |
| | | doesn't describe itself as a microframework, but its intended userbase |
| | | is much the same. Many others exist. We've actually even (only as a |
| | | teaching tool, not as any sort of official project) `created one using |
| | | BFG <http://bfg.repoze.org/videos#groundhog1>`_ (the precursor to |
| | | Pyramid). Microframeworks are small frameworks with one common |
| | | feature: each allows its users to create a fully functional |
| | | application that lives in a single Python file. |
| | | Self-described "microframeworks" exist: `Bottle <http://bottle.paws.de>`_ and |
| | | `Flask <http://flask.pocoo.org/>`_ are two that are becoming popular. `Bobo |
| | | <http://bobo.digicool.com/>`_ doesn't describe itself as a microframework, |
| | | but its intended userbase is much the same. Many others exist. We've |
| | | actually even (only as a teaching tool, not as any sort of official project) |
| | | `created one using BFG <http://bfg.repoze.org/videos#groundhog1>`_ (the |
| | | precursor to Pyramid). Microframeworks are small frameworks with one common |
| | | feature: each allows its users to create a fully functional application that |
| | | lives in a single Python file. |
| | | |
| | | Some developers and microframework authors point out that Pyramid's |
| | | "hello world" single-file program is longer (by about five lines) than |
| | | the equivalent program in their favorite microframework. Guilty as |
| | | charged; in a contest of "whose is shortest", Pyramid indeed loses. |
| | | Some developers and microframework authors point out that Pyramid's "hello |
| | | world" single-file program is longer (by about five lines) than the |
| | | equivalent program in their favorite microframework. Guilty as charged; in a |
| | | contest of "whose is shortest", Pyramid indeed loses. |
| | | |
| | | This loss isn't for lack of trying. Pyramid aims to be useful in the |
| | | same circumstance in which microframeworks claim dominance: |
| | | single-file applications. But Pyramid doesn't sacrifice its ability |
| | | to credibly support larger applications in order to achieve |
| | | hello-world LoC parity with the current crop of microframeworks. |
| | | Pyramid's design instead tries to avoid some common pitfalls |
| | | associated with naive declarative configuration schemes. The |
| | | subsections which follow explain the rationale. |
| | | This loss isn't for lack of trying. Pyramid aims to be useful in the same |
| | | circumstance in which microframeworks claim dominance: single-file |
| | | applications. But Pyramid doesn't sacrifice its ability to credibly support |
| | | larger applications in order to achieve hello-world LoC parity with the |
| | | current crop of microframeworks. Pyramid's design instead tries to avoid |
| | | some common pitfalls associated with naive declarative configuration schemes. |
| | | The subsections which follow explain the rationale. |
| | | |
| | | .. _you_dont_own_modulescope: |
| | | |
| | | Application Programmers Don't Control The Module-Scope Codepath (Import-Time Side-Effects Are Evil) |
| | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| | | |
| | | Please imagine a directory structure with a set of Python files in |
| | | it: |
| | | Please imagine a directory structure with a set of Python files in it: |
| | | |
| | | .. code-block:: text |
| | | |
| | |
| | | L.append(func) |
| | | return func |
| | | |
| | | If we cd to the directory that holds these files and we run ``python |
| | | app.py`` given the directory structure and code above, what happens? |
| | | Presumably, our ``decorator`` decorator will be used twice, once by the |
| | | decorated function ``foo`` in ``app.py`` and once by the decorated |
| | | function ``bar`` in ``app2.py``. Since each time the decorator is |
| | | used, the list ``L`` in ``config.py`` is appended to, we'd expect a |
| | | list with two elements to be printed, right? Sadly, no: |
| | | If we cd to the directory that holds these files and we run ``python app.py`` |
| | | given the directory structure and code above, what happens? Presumably, our |
| | | ``decorator`` decorator will be used twice, once by the decorated function |
| | | ``foo`` in ``app.py`` and once by the decorated function ``bar`` in |
| | | ``app2.py``. Since each time the decorator is used, the list ``L`` in |
| | | ``config.py`` is appended to, we'd expect a list with two elements to be |
| | | printed, right? Sadly, no: |
| | | |
| | | .. code-block:: text |
| | | |
| | |
| | | <function foo at 0x7f4ea41ab230>, |
| | | <function bar at 0x7f4ea41ab2a8>] |
| | | |
| | | By visual inspection, that outcome (three different functions in the |
| | | list) seems impossible. We only defined two functions and we |
| | | decorated each of those functions only once, so we believe that the |
| | | ``decorator`` decorator will only run twice. However, what we believe |
| | | is wrong because the code at module scope in our ``app.py`` module was |
| | | *executed twice*. The code is executed once when the script is run as |
| | | ``__main__`` (via ``python app.py``), and then it is executed again |
| | | when ``app2.py`` imports the same file as ``app``. |
| | | By visual inspection, that outcome (three different functions in the list) |
| | | seems impossible. We only defined two functions and we decorated each of |
| | | those functions only once, so we believe that the ``decorator`` decorator |
| | | will only run twice. However, what we believe is wrong because the code at |
| | | module scope in our ``app.py`` module was *executed twice*. The code is |
| | | executed once when the script is run as ``__main__`` (via ``python app.py``), |
| | | and then it is executed again when ``app2.py`` imports the same file as |
| | | ``app``. |
| | | |
| | | What does this have to do with our comparison to microframeworks? |
| | | Many microframeworks in the current crop (e.g. Bottle, Flask) |
| | | encourage you to attach configuration decorators to objects defined at |
| | | module scope. These decorators execute arbitrarily complex |
| | | registration code which populates a singleton registry that is a |
| | | global defined in external Python module. This is analogous to the |
| | | above example: the "global registry" in the above example is the list |
| | | ``L``. |
| | | What does this have to do with our comparison to microframeworks? Many |
| | | microframeworks in the current crop (e.g. Bottle, Flask) encourage you to |
| | | attach configuration decorators to objects defined at module scope. These |
| | | decorators execute arbitrarily complex registration code which populates a |
| | | singleton registry that is a global defined in external Python module. This |
| | | is analogous to the above example: the "global registry" in the above example |
| | | is the list ``L``. |
| | | |
| | | Let's see what happens when we use the same pattern with the |
| | | `Groundhog <http://bfg.repoze.org/videos#groundhog1>`_ microframework. |
| | | Replace the contents of ``app.py`` above with this: |
| | | Let's see what happens when we use the same pattern with the `Groundhog |
| | | <http://bfg.repoze.org/videos#groundhog1>`_ microframework. Replace the |
| | | contents of ``app.py`` above with this: |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | |
| | | from groundhog import Groundhog |
| | | gh = Groundhog('myapp', 'seekrit') |
| | | |
| | | How many routes will be registered within the routing table of the |
| | | "gh" Groundhog application? If you answered three, you are correct. |
| | | How many would a casual reader (and any sane developer) expect to be |
| | | registered? If you answered two, you are correct. Will the double |
| | | registration be a problem? With our fictional Groundhog framework's |
| | | ``route`` method backing this application, not really. It will slow |
| | | the application down a little bit, because it will need to miss twice |
| | | for a route when it does not match. Will it be a problem with another |
| | | framework, another application, or another decorator? Who knows. You |
| | | need to understand the application in its totality, the framework in |
| | | its totality, and the chronology of execution to be able to predict |
| | | what the impact of unintentional code double-execution will be. |
| | | How many routes will be registered within the routing table of the "gh" |
| | | Groundhog application? If you answered three, you are correct. How many |
| | | would a casual reader (and any sane developer) expect to be registered? If |
| | | you answered two, you are correct. Will the double registration be a |
| | | problem? With our fictional Groundhog framework's ``route`` method backing |
| | | this application, not really. It will slow the application down a little |
| | | bit, because it will need to miss twice for a route when it does not match. |
| | | Will it be a problem with another framework, another application, or another |
| | | decorator? Who knows. You need to understand the application in its |
| | | totality, the framework in its totality, and the chronology of execution to |
| | | be able to predict what the impact of unintentional code double-execution |
| | | will be. |
| | | |
| | | The encouragement to use decorators which perform population of an |
| | | external registry has an unintended consequence: the application |
| | | developer now must assert ownership of every codepath that executes |
| | | Python module scope code. This code is presumed by the current crop of |
| | | decorator-based microframeworks to execute once and only once; if it |
| | | executes more than once, weird things will start to happen. It is up |
| | | to the application developer to maintain this invariant. |
| | | Unfortunately, however, in reality, this is an impossible task, |
| | | because, Python programmers *do not own the module scope codepath, and |
| | | never will*. Microframework programmers therefore will at some point |
| | | then need to start reading the tea leaves about what *might* happen if |
| | | module scope code gets executed more than once like we do in the |
| | | previous paragraph. This is a really pretty poor situation to find |
| | | yourself in as an application developer: you probably didn't even know |
| | | you signed up for the job, because the documentation offered by |
| | | decorator-based microframeworks don't warn you about it. |
| | | The encouragement to use decorators which perform population of an external |
| | | registry has an unintended consequence: the application developer now must |
| | | assert ownership of every codepath that executes Python module scope |
| | | code. Module-scope code is presumed by the current crop of decorator-based |
| | | microframeworks to execute once and only once; if it executes more than once, |
| | | weird things will start to happen. It is up to the application developer to |
| | | maintain this invariant. Unfortunately, however, in reality, this is an |
| | | impossible task, because, Python programmers *do not own the module scope |
| | | codepath, and never will*. Anyone who tries to sell you on the idea that |
| | | they do is simply mistaken. Test runners that you may want to use to run |
| | | your code's tests often perform imports of arbitrary code in strange orders |
| | | that manifest bugs like the one demonstrated above. API documentation |
| | | generation tools do the same. Some (mutant) people even think it's safe to |
| | | use the Python ``reload`` command or delete objects from ``sys.modules``, |
| | | each of which has hilarious effects when used against code that has |
| | | import-time side effects. |
| | | |
| | | Python application programmers do not control the module scope codepath. |
| | | Anyone who tries to sell you on the idea that they do is simply mistaken. |
| | | Test runners that you may want to use to run your code's tests often perform |
| | | imports of arbitrary code in strange orders that manifest bugs like the one |
| | | demonstrated above. API documentation generation tools do the same. Some |
| | | (mutant) people even think it's safe to use the Python ``reload`` command or |
| | | delete objects from ``sys.modules``, each of which has hilarious effects when |
| | | used against code that has import-time side effects. When Python programmers |
| | | assume they can use the module-scope codepath to run arbitrary code |
| | | (especially code which populates an external registry), and this assumption |
| | | is challenged by reality, the application developer is often required to |
| | | undergo a painful, meticulous debugging process to find the root cause of an |
| | | inevitably obscure symptom. The solution is often to rearrange application |
| | | import ordering or move an import statement from module-scope into a function |
| | | body. The rationale for doing so can never be expressed adequnately in the |
| | | checkin message which accompanies the fix or documented succinctly enough for |
| | | the benefit of the rest of the development team so that the problem never |
| | | happens again. It will happen again next month too, especially if you are |
| | | working on a project with other people who haven't yet internalized the |
| | | lessons you learned while you stepped through module-scope code using |
| | | ``pdb``. |
| | | Global-registry-mutating microframework programmers therefore will at some |
| | | point need to start reading the tea leaves about what *might* happen if |
| | | module scope code gets executed more than once like we do in the previous |
| | | paragraph. When Python programmers assume they can use the module-scope |
| | | codepath to run arbitrary code (especially code which populates an external |
| | | registry), and this assumption is challenged by reality, the application |
| | | developer is often required to undergo a painful, meticulous debugging |
| | | process to find the root cause of an inevitably obscure symptom. The |
| | | solution is often to rearrange application import ordering or move an import |
| | | statement from module-scope into a function body. The rationale for doing so |
| | | can never be expressed adequately in the checkin message which accompanies |
| | | the fix and can't be documented succinctly enough for the benefit of the rest |
| | | of the development team so that the problem never happens again. It will |
| | | happen again, especially if you are working on a project with other people |
| | | who haven't yet internalized the lessons you learned while you stepped |
| | | through module-scope code using ``pdb``. This is a really pretty poor |
| | | situation to find yourself in as an application developer: you probably |
| | | didn't even know your or your team signed up for the job, because the |
| | | documentation offered by decorator-based microframeworks don't warn you about |
| | | it. |
| | | |
| | | Folks who have a large investment in eager decorator-based |
| | | configuration that populates an external data structure (such as |
| | | microframework authors) may argue that the set of circumstances I |
| | | outlined above is anomalous and contrived. They will argue that it |
| | | just will never happen. If you never intend your application to grow |
| | | beyond one or two or three modules, that's probably true. However, as |
| | | your codebase grows, and becomes spread across a greater number of |
| | | modules, the circumstances in which module-scope code will be executed |
| | | multiple times will become more and more likely to occur and less and |
| | | less predictable. It's not responsible to claim that double-execution |
| | | of module-scope code will never happen. It will; it's just a matter |
| | | of luck, time, and application complexity. |
| | | Folks who have a large investment in eager decorator-based configuration that |
| | | populates an external data structure (such as microframework authors) may |
| | | argue that the set of circumstances I outlined above is anomalous and |
| | | contrived. They will argue that it just will never happen. If you never |
| | | intend your application to grow beyond one or two or three modules, that's |
| | | probably true. However, as your codebase grows, and becomes spread across a |
| | | greater number of modules, the circumstances in which module-scope code will |
| | | be executed multiple times will become more and more likely to occur and less |
| | | and less predictable. It's not responsible to claim that double-execution of |
| | | module-scope code will never happen. It will; it's just a matter of luck, |
| | | 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 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 ("X is executed, then Y, then X |
| | | again.. a train leaves Chicago at 50 mph... "). And even if given the |
| | | gift of omniscience for some limited set of circumstances, the |
| | | framework author almost certainly does not have the double-execution |
| | | anomaly in mind when coding new features. He's thinking of adding a |
| | | feature, not protecting against problems that might be caused by the |
| | | 1% multiple execution case. However, any 1% case may cause 50% of |
| | | your pain on a project, so it'd be nice if it never occured. |
| | | 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 |
| | | 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 |
| | | omniscience for some limited set of circumstances, the framework author |
| | | almost certainly does not have the double-execution anomaly in mind when |
| | | coding new features. He's thinking of adding a feature, not protecting |
| | | against problems that might be caused by the 1% multiple execution case. |
| | | However, any 1% case may cause 50% of your pain on a project, so it'd be nice |
| | | if it never occured. |
| | | |
| | | Responsible microframeworks actually offer a back-door way around the |
| | | problem. They allow you to disuse decorator based configuration |
| | | entirely. Instead of requiring you to do the following: |
| | | problem. They allow you to disuse decorator based configuration entirely. |
| | | Instead of requiring you to do the following: |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | |
| | | if __name__ == '__main__': |
| | | gh.run() |
| | | |
| | | They allow you to disuse the decorator syntax and go |
| | | almost-all-imperative: |
| | | They allow you to disuse the decorator syntax and go almost-all-imperative: |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | |
| | | gh.run() |
| | | |
| | | This is a generic mode of operation that is encouraged in the Pyramid |
| | | documentation. Some existing microframeworks (Flask, in particular) |
| | | allow for it as well. None (other than Pyramid) *encourage* it. If |
| | | you never expect your application to grow beyond two or three or four |
| | | or ten modules, it probably doesn't matter very much which mode you |
| | | use. If your application grows large, however, imperative |
| | | configuration can provide better predictability. |
| | | documentation. Some existing microframeworks (Flask, in particular) allow for |
| | | it as well. None (other than Pyramid) *encourage* it. If you never expect |
| | | your application to grow beyond two or three or four or ten modules, it |
| | | probably doesn't matter very much which mode you use. If your application |
| | | grows large, however, imperative configuration can provide better |
| | | predictability. |
| | | |
| | | .. note:: |
| | | |
| | | Astute readers may notice that Pyramid has configuration decorators |
| | | too. 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 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, that's OK, |
| | | because each decorator invocation will always be mutating an |
| | | independent copy of the object its attached to, not a shared |
| | | resource like a registry in another module. This has the effect |
| | | that double-registrations will never be performed. |
| | | Astute readers may notice that Pyramid has configuration decorators too. |
| | | 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 |
| | | 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, |
| | | that's OK, because each decorator invocation will always be mutating an |
| | | independent copy of the object its attached to, not a shared resource like |
| | | a registry in another module. This has the effect that |
| | | double-registrations will never be performed. |
| | | |
| | | Routes (Usually) Need Relative Ordering |
| | | +++++++++++++++++++++++++++++++++++++++ |
| | |
| | | app.run() |
| | | |
| | | If you run this application and visit the URL ``/admin``, you will see |
| | | "admin" page. This is the intended result. However, what if you |
| | | rearrange the order of the function definitions in the file? |
| | | "admin" page. This is the intended result. However, what if you rearrange |
| | | the order of the function definitions in the file? |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | |
| | | if __name__ == '__main__': |
| | | app.run() |
| | | |
| | | If you run this application and visit the URL ``/admin``, you will now |
| | | be returned a 404 error. This is probably not what you intended. The |
| | | reason you see a 404 error when you rearrange function definition |
| | | ordering is that routing declarations expressed via our |
| | | microframework's routing decorators have an *ordering*, and that |
| | | ordering matters. |
| | | If you run this application and visit the URL ``/admin``, you will now be |
| | | returned a 404 error. This is probably not what you intended. The reason |
| | | you see a 404 error when you rearrange function definition ordering is that |
| | | routing declarations expressed via our microframework's routing decorators |
| | | have an *ordering*, and that ordering matters. |
| | | |
| | | In the first case, where we achieved the expected result, we first |
| | | added a route with the pattern ``/admin``, then we added a route with |
| | | the pattern ``/:action`` by virtue of adding routing patterns via |
| | | decorators at module scope. When a request with a ``PATH_INFO`` of |
| | | ``/admin`` enters our application, the web framework loops over each |
| | | of our application's route patterns in the order in which they were |
| | | defined in our module. As a result, the view associated with the |
| | | ``/admin`` routing pattern will be invoked: it matches first. All is |
| | | right with the world. |
| | | In the first case, where we achieved the expected result, we first added a |
| | | route with the pattern ``/admin``, then we added a route with the pattern |
| | | ``/:action`` by virtue of adding routing patterns via decorators at module |
| | | scope. When a request with a ``PATH_INFO`` of ``/admin`` enters our |
| | | application, the web framework loops over each of our application's route |
| | | patterns in the order in which they were defined in our module. As a result, |
| | | the view associated with the ``/admin`` routing pattern will be invoked: it |
| | | matches first. All is right with the world. |
| | | |
| | | In the second case, where we did not achieve the expected result, we |
| | | first added a route with the pattern ``/:action``, then we added a |
| | | route with the pattern ``/admin``. When a request with a |
| | | ``PATH_INFO`` of ``/admin`` enters our application, the web framework |
| | | loops over each of our application's route patterns in the order in |
| | | which they were defined in our module. As a result, the view |
| | | associated with the ``/:action`` routing pattern will be invoked: it |
| | | 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. |
| | | In the second case, where we did not achieve the expected result, we first |
| | | added a route with the pattern ``/:action``, then we added a route with the |
| | | pattern ``/admin``. When a request with a ``PATH_INFO`` of ``/admin`` enters |
| | | our application, the web framework loops over each of our application's route |
| | | patterns in the order in which they were defined in our module. As a result, |
| | | the view associated with the ``/:action`` routing pattern will be invoked: it |
| | | 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. |
| | | |
| | | You may be willing to maintain an ordering of your view functions |
| | | which reifies your routing policy. Your application may be small |
| | | enough where this will never cause an issue. If it becomes large |
| | | enough to matter, however, I don't envy you. Maintaining that |
| | | ordering as your application grows larger will be difficult. At some |
| | | point, you will also need to start controlling *import* ordering as |
| | | well as function definition ordering. When your application grows |
| | | beyond the size of a single file, and when decorators are used to |
| | | register views, the non-``__main__`` modules which contain |
| | | configuration decorators must be imported somehow for their |
| | | configuration to be executed. |
| | | You may be willing to maintain an ordering of your view functions which |
| | | reifies your routing policy. Your application may be small enough where this |
| | | will never cause an issue. If it becomes large enough to matter, however, I |
| | | don't envy you. Maintaining that ordering as your application grows larger |
| | | will be difficult. At some point, you will also need to start controlling |
| | | *import* ordering as well as function definition ordering. When your |
| | | application grows beyond the size of a single file, and when decorators are |
| | | used to register views, the non-``__main__`` modules which contain |
| | | configuration decorators must be imported somehow for their configuration to |
| | | be executed. |
| | | |
| | | Does that make you a little |
| | | uncomfortable? It should, because :ref:`you_dont_own_modulescope`. |
| | | Does that make you a little uncomfortable? It should, because |
| | | :ref:`you_dont_own_modulescope`. |
| | | |
| | | "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*: |
| | | In another manifestation of "import fascination", some microframeworks use |
| | | the ``import`` statement to get a handle to an object which *is not logically |
| | | global*: |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | |
| | | # credentials were invalid |
| | | |
| | | The `Pylons 1.X <http://pylonshq.com>`_ 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. |
| | | strategy. It calls these things "Stacked Object Proxies", so, for purposes |
| | | of this discussion, I'll do so as well. |
| | | |
| | | 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 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 the body |
| | | of a function. For example, we'd never try to import ``i`` from the |
| | | 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 |
| | | 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 |
| | | the body of a function. For example, we'd never try to import ``i`` from the |
| | | code below: |
| | | |
| | | .. code-block:: python |
| | |
| | | for i in range(10): |
| | | print i |
| | | |
| | | By its nature, the *request* object created as the result of a WSGI |
| | | server's call into a long-lived web framework cannot be global, |
| | | because the lifetime of a single request will be much shorter than the |
| | | lifetime of the process 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. |
| | | By its nature, the *request* object created as the result of a WSGI server's |
| | | call into a long-lived web framework cannot be global, because the lifetime |
| | | of a single request will be much shorter than the lifetime of the process |
| | | 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. |
| | | |
| | | 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 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 = get_request()`` |
| | | even though the latter is more explicit. |
| | | 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 |
| | | 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 = |
| | | get_request()`` even though the latter is more explicit. |
| | | |
| | | It would be *most* explicit if the microframeworks did not use thread |
| | | local variables at all. Pyramid view functions are passed a request |
| | | object; many of Pyramid's APIs require that an explicit request object |
| | | be passed to them. It is *possible* to retrieve the current Pyramid |
| | | request as a threadlocal variable but it is a "in case of emergency, |
| | | break glass" type of activity. This explicitness makes Pyramid view |
| | | functions more easily unit testable, as you don't need to rely on the |
| | | framework to manufacture suitable "dummy" request (and other |
| | | similarly-scoped) objects during test setup. It also makes them more |
| | | likely to work on arbitrary systems, such as async servers that do no |
| | | monkeypatching. |
| | | It would be *most* explicit if the microframeworks did not use thread local |
| | | variables at all. Pyramid view functions are passed a request object; many |
| | | of Pyramid's APIs require that an explicit request object be passed to them. |
| | | It is *possible* to retrieve the current Pyramid request as a threadlocal |
| | | variable but it is a "in case of emergency, break glass" type of activity. |
| | | This explicitness makes Pyramid view functions more easily unit testable, as |
| | | you don't need to rely on the framework to manufacture suitable "dummy" |
| | | request (and other similarly-scoped) objects during test setup. It also |
| | | makes them more likely to work on arbitrary systems, such as async servers |
| | | that do no monkeypatching. |
| | | |
| | | Explicitly WSGI |
| | | +++++++++++++++ |
| | | |
| | | Some microframeworks offer a ``run()`` method of an application object |
| | | that executes a default server configuration for easy execution. |
| | | Some microframeworks offer a ``run()`` method of an application object that |
| | | executes a default server configuration for easy execution. |
| | | |
| | | Pyramid doesn't currently try to hide the fact that its router is a |
| | | WSGI application behind a convenience ``run()`` API. It just tells |
| | | people to import a WSGI server and use it to serve up their Pyramid |
| | | application as per the documentation of that WSGI server. |
| | | Pyramid doesn't currently try to hide the fact that its router is a WSGI |
| | | application behind a convenience ``run()`` API. It just tells people to |
| | | import a WSGI server and use it to serve up their Pyramid application as per |
| | | the documentation of that WSGI server. |
| | | |
| | | The extra lines saved by abstracting away the serving step behind |
| | | ``run()`` seem to have driven dubious second-order decisions related |
| | | to API in some microframeworks. For example, Bottle contains a |
| | | ``ServerAdapter`` subclass for each type of WSGI server it supports |
| | | via its ``app.run()`` mechanism. This means that there exists code in |
| | | ``bottle.py`` that depends on the following modules: ``wsgiref``, |
| | | ``flup``, ``paste``, ``cherrypy``, ``fapws``, ``tornado``, |
| | | ``google.appengine``, ``twisted.web``, ``diesel``, ``gevent``, |
| | | ``gunicorn``, ``eventlet``, and ``rocket``. You choose the kind of |
| | | server you want to run by passing its name into the ``run`` method. |
| | | In theory, this sounds great: I can try Bottle out on ``gunicorn`` |
| | | just by passing in a name! However, to fully test Bottle, all of |
| | | these third-party systems must be installed and functional; the Bottle |
| | | developers must monitor changes to each of these packages and make |
| | | sure their code still interfaces properly with them. This expands the |
| | | packages required for testing greatly; this is a *lot* of |
| | | requirements. It is likely difficult to fully automate these tests |
| | | The extra lines saved by abstracting away the serving step behind ``run()`` |
| | | seem to have driven dubious second-order decisions related to API in some |
| | | microframeworks. For example, Bottle contains a ``ServerAdapter`` subclass |
| | | for each type of WSGI server it supports via its ``app.run()`` mechanism. |
| | | This means that there exists code in ``bottle.py`` that depends on the |
| | | following modules: ``wsgiref``, ``flup``, ``paste``, ``cherrypy``, ``fapws``, |
| | | ``tornado``, ``google.appengine``, ``twisted.web``, ``diesel``, ``gevent``, |
| | | ``gunicorn``, ``eventlet``, and ``rocket``. You choose the kind of server |
| | | you want to run by passing its name into the ``run`` method. In theory, this |
| | | sounds great: I can try Bottle out on ``gunicorn`` just by passing in a name! |
| | | However, to fully test Bottle, all of these third-party systems must be |
| | | installed and functional; the Bottle developers must monitor changes to each |
| | | of these packages and make sure their code still interfaces properly with |
| | | them. This expands the packages required for testing greatly; this is a |
| | | *lot* of requirements. It is likely difficult to fully automate these tests |
| | | due to requirements conflicts and build issues. |
| | | |
| | | 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 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 more likely to |
| | | be timely and correct. |
| | | 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 |
| | | 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 |
| | | more likely to be timely and correct. |
| | | |
| | | Wrapping Up |
| | | +++++++++++ |
| | | |
| | | Here's a diagrammed version of the simplest pyramid application, |
| | | where comments take into account what we've discussed in the |
| | | Here's a diagrammed version of the simplest pyramid application, where |
| | | comments take into account what we've discussed in the |
| | | :ref:`microframeworks_smaller_hello_world` section. |
| | | |
| | | .. code-block:: python |
| | |
| | | app = config.make_wsgi_app() # explicitly WSGI |
| | | serve(app, host='0.0.0.0') # explicitly WSGI |
| | | |
| | | Pyramid Has Zope Things In It, So It's Too Complex |
| | | -------------------------------------------------- |
| | | |
| | | On occasion, someone will feel compelled to post a mailing list message that |
| | | reads something like this: |
| | | |
| | | .. code-block:: text |
| | | |
| | | had a quick look at pyramid ... too complex to me and not really |
| | | understand for which benefits.. I feel should consider whether it's time |
| | | for me to step back to django .. I always hated zope (useless ?) |
| | | complexity and I love simple way of thinking |
| | | |
| | | (Paraphrased from a real email, actually.) |
| | | |
| | | Let's take this criticism point-by point. |
| | | |
| | | Too Complex |
| | | +++++++++++ |
| | | |
| | | - If you can understand this hello world program, you can use Pyramid: |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | from paste.httpserver import serve |
| | | from pyramid.config import Configurator |
| | | from pyramid.response import Response |
| | | |
| | | def hello_world(request): |
| | | return Response('Hello world!') |
| | | |
| | | if __name__ == '__main__': |
| | | config = Configurator() |
| | | config.add_view(hello_world) |
| | | app = config.make_wsgi_app() |
| | | serve(app) |
| | | |
| | | 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. |
| | | |
| | | Hate Zope |
| | | +++++++++ |
| | | |
| | | I'm sorry you feel that way. The Zope brand has certainly taken its share of |
| | | lumps over the years, and has a reputation for being insular and mysterious. |
| | | But the word "Zope" is literally quite meaningless without qualification. |
| | | What *part* of Zope do you hate? "Zope" is a brand, not a technology. |
| | | |
| | | If it's Zope2-the-web-framework, Pyramid is not that. The primary designers |
| | | and developers of Pyramid, if anyone, should know. We wrote Pyramid's |
| | | predecessor (:mod:`repoze.bfg`), in part, because *we* knew that Zope 2 had |
| | | usability issues and limitations. :mod:`repoze.bfg` (and now :app:`Pyramid`) |
| | | was written to address these issues. |
| | | |
| | | If it's Zope3-the-web-framework, Pyramid is *definitely* not that. Making |
| | | use of lots of Zope 3 technologies is territory already staked out by the |
| | | :term:`Grok` project. Save for the obvious fact that they're both web |
| | | frameworks, :mod:`Pyramid` is very, very different than Grok. Grok exposes |
| | | lots of Zope technologies to end users. On the other hand, if you need to |
| | | understand a Zope-only concept while using Pyramid, then we've failed on some |
| | | very basic axis. |
| | | |
| | | If it's just the word Zope: this can only be guilt by association. Because a |
| | | piece of software internally uses some package named ``zope.foo``, it doesn't |
| | | turn the piece of software that uses it into "Zope". There is a lot of |
| | | *great* software written that has the word Zope in its name. Zope is not |
| | | some sort of monolithic thing, and a lot of its software is usable |
| | | externally. And while it's not really the job of this document to defend it, |
| | | Zope has been around for over 10 years and has an incredibly large, active |
| | | community. If you don't believe this, |
| | | http://taichino.appspot.com/pypi_ranking/authors is an eye-opening reality |
| | | check. |
| | | |
| | | Love Simplicity |
| | | +++++++++++++++ |
| | | |
| | | Years of effort have gone into honing this package and its documentation to |
| | | make it as simple as humanly possible for developers to use. Everything is a |
| | | tradeoff, of course, and people have their own ideas about what "simple" is. |
| | | You may have a style difference if you believe Pyramid is complex. Its |
| | | developers obviously disagree. |
| | | |
| | | Other Challenges |
| | | ---------------- |
| | | |