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