commit | author | age
|
672bbe
|
1 |
.. _design_defense: |
CM |
2 |
|
edd915
|
3 |
Defending Pyramid's Design |
CM |
4 |
========================== |
fbfea7
|
5 |
|
8cdb1b
|
6 |
From time to time, challenges to various aspects of :app:`Pyramid` design are |
CM |
7 |
lodged. To give context to discussions that follow, we detail some of the |
|
8 |
design decisions and trade-offs here. In some cases, we acknowledge that the |
|
9 |
framework can be made better and we describe future steps which will be taken |
|
10 |
to improve it; in some cases we just file the challenge as "noted", as |
|
11 |
obviously you can't please everyone all of the time. |
70f7c3
|
12 |
|
6cdbba
|
13 |
Pyramid Provides More Than One Way to Do It |
CM |
14 |
------------------------------------------- |
|
15 |
|
dfc9d6
|
16 |
A canon of Python popular culture is "TIOOWTDI" ("there is only one way to do |
6cdbba
|
17 |
it", a slighting, tongue-in-cheek reference to Perl's "TIMTOWTDI", which is |
CM |
18 |
an acronym for "there is more than one way to do it"). |
|
19 |
|
|
20 |
:app:`Pyramid` is, for better or worse, a "TIMTOWTDI" system. For example, |
|
21 |
it includes more than one way to resolve a URL to a :term:`view callable`: |
|
22 |
via :term:`url dispatch` or :term:`traversal`. Multiple methods of |
|
23 |
configuration exist: :term:`imperative configuration`, :term:`configuration |
bee624
|
24 |
decoration`, and :term:`ZCML` (optionally via :term:`pyramid_zcml`). It works |
CM |
25 |
with multiple different kinds of persistence and templating systems. And so |
|
26 |
on. However, the existence of most of these overlapping ways to do things |
|
27 |
are not without reason and purpose: we have a number of audiences to serve, |
|
28 |
and we believe that TIMTOWTI at the web framework level actually *prevents* a |
|
29 |
much more insidious and harmful set of duplication at higher levels in the |
|
30 |
Python web community. |
6cdbba
|
31 |
|
CM |
32 |
:app:`Pyramid` began its life as :mod:`repoze.bfg`, written by a team of |
a9ff22
|
33 |
people with many years of prior :term:`Zope` experience. The idea of |
bee624
|
34 |
:term:`traversal` and the way :term:`view lookup` works was stolen entirely |
CM |
35 |
from Zope. The authorization subsystem provided by :app:`Pyramid` is a |
|
36 |
derivative of Zope's. The idea that an application can be *extended* without |
|
37 |
forking is also a Zope derivative. |
6cdbba
|
38 |
|
CM |
39 |
Implementations of these features were *required* to allow the :app:`Pyramid` |
|
40 |
authors to build the bread-and-butter CMS-type systems for customers in the |
|
41 |
way they were accustomed to building them. No other system save Zope itself |
b36921
|
42 |
had such features. And Zope itself was beginning to show signs of its age. |
2ce478
|
43 |
We were becoming hampered by consequences of its early design mistakes. |
CM |
44 |
Zope's lack of documentation was also difficult to work around: it was hard |
|
45 |
to hire smart people to work on Zope applications, because there was no |
|
46 |
comprehensive documentation set to point them at which explained "it all" in |
|
47 |
one consumble place, and it was too large and self-inconsistent to document |
|
48 |
properly. Before :mod:`repoze.bfg` went under development, its authors |
b36921
|
49 |
obviously looked around for other frameworks that fit the bill. But no |
CM |
50 |
non-Zope framework did. So we embarked on building :mod:`repoze.bfg`. |
6cdbba
|
51 |
|
CM |
52 |
As the result of our research, however, it became apparent that, despite the |
|
53 |
fact that no *one* framework had all the features we required, lots of |
|
54 |
existing frameworks had good, and sometimes very compelling ideas. In |
|
55 |
particular, :term:`URL dispatch` is a more direct mechanism to map URLs to |
|
56 |
code. |
|
57 |
|
|
58 |
So although we couldn't find a framework save for Zope that fit our needs, |
|
59 |
and while we incorporated a lot of Zope ideas into BFG, we also emulated the |
|
60 |
features we found compelling in other frameworks (such as :term:`url |
|
61 |
dispatch`). After the initial public release of BFG, as time went on, |
|
62 |
features were added to support people allergic to various Zope-isms in the |
|
63 |
system, such as the ability to configure the application using |
55ce9d
|
64 |
:term:`imperative configuration` and :term:`configuration decoration` rather |
CM |
65 |
than solely using :term:`ZCML`, and the elimination of the required use of |
|
66 |
:term:`interface` objects. It soon became clear that we had a system that |
|
67 |
was very generic, and was beginning to appeal to non-Zope users as well as |
|
68 |
ex-Zope users. |
6cdbba
|
69 |
|
CM |
70 |
As the result of this generalization, it became obvious BFG shared 90% of its |
b36921
|
71 |
featureset with the featureset of Pylons 1, and thus had a very similar |
CM |
72 |
target market. Because they were so similar, choosing between the two |
|
73 |
systems was an exercise in frustration for an otherwise non-partisan |
2ce478
|
74 |
developer. It was also strange for the Pylons and BFG development |
CM |
75 |
communities to be in competition for the same set of users, given how similar |
|
76 |
the two frameworks were. So the Pylons and BFG teams began to work together |
|
77 |
to form a plan to "merge". The features missing from BFG (notably |
b36921
|
78 |
:term:`view handler` classes, flash messaging, and other minor missing bits), |
CM |
79 |
were added, to provide familiarity to ex-Pylons users. The result is |
|
80 |
:app:`Pyramid`. |
6cdbba
|
81 |
|
0bdbdf
|
82 |
The Python web framework space is currently notoriously balkanized. We're |
CM |
83 |
truly hoping that the amalgamation of components in :app:`Pyramid` will |
6cdbba
|
84 |
appeal to at least two currently very distinct sets of users: Pylons and BFG |
b36921
|
85 |
users. By unifying the best concepts from Pylons and BFG into a single |
CM |
86 |
codebase and leaving the bad concepts from their ancestors behind, we'll be |
|
87 |
able to consolidate our efforts better, share more code, and promote our |
|
88 |
efforts as a unit rather than competing pointlessly. We hope to be able to |
|
89 |
shortcut the pack mentality which results in a *much larger* duplication of |
|
90 |
effort, represented by competing but incredibly similar applications and |
|
91 |
libraries, each built upon a specific low level stack that is incompatible |
|
92 |
with the other. We'll also shrink the choice of credible Python web |
|
93 |
frameworks down by at least one. We're also hoping to attract users from |
|
94 |
other communities (such as Zope's and TurboGears') by providing the features |
291bcc
|
95 |
they require, while allowing enough flexibility to do things in a familiar |
b36921
|
96 |
fashion. Some overlap of functionality to achieve these goals is expected |
CM |
97 |
and unavoidable, at least if we aim to prevent pointless duplication at |
|
98 |
higher levels. If we've done our job well enough, the various audiences will |
|
99 |
be able to coexist and cooperate rather than firing at each other across some |
|
100 |
imaginary web framework "DMZ". |
6cdbba
|
101 |
|
edd915
|
102 |
Pyramid Uses A Zope Component Architecture ("ZCA") Registry |
CM |
103 |
----------------------------------------------------------- |
fbfea7
|
104 |
|
8cdb1b
|
105 |
:app:`Pyramid` uses a :term:`Zope Component Architecture` (ZCA) "component |
CM |
106 |
registry" as its :term:`application registry` under the hood. This is a |
|
107 |
point of some contention. :app:`Pyramid` is of a :term:`Zope` pedigree, so |
|
108 |
it was natural for its developers to use a ZCA registry at its inception. |
|
109 |
However, we understand that using a ZCA registry has issues and consequences, |
|
110 |
which we've attempted to address as best we can. Here's an introspection |
|
111 |
about :app:`Pyramid` use of a ZCA registry, and the trade-offs its usage |
0435ce
|
112 |
involves. |
fbfea7
|
113 |
|
CM |
114 |
Problems |
|
115 |
++++++++ |
|
116 |
|
fa8994
|
117 |
The "global" API that may be used to access data in a ZCA "component |
8cdb1b
|
118 |
registry" is not particularly pretty or intuitive, and sometimes it's just |
CM |
119 |
plain obtuse. Likewise, the conceptual load on a casual source code reader |
|
120 |
of code that uses the ZCA global API is somewhat high. Consider a ZCA |
|
121 |
neophyte reading the code that performs a typical "unnamed utility" lookup |
|
122 |
using the :func:`zope.component.getUtility` global API: |
fbfea7
|
123 |
|
0ac7b0
|
124 |
.. ignore-next-block |
fbfea7
|
125 |
.. code-block:: python |
CM |
126 |
:linenos: |
|
127 |
|
edd915
|
128 |
from pyramid.interfaces import ISettings |
fbfea7
|
129 |
from zope.component import getUtility |
CM |
130 |
settings = getUtility(ISettings) |
|
131 |
|
8cdb1b
|
132 |
After this code runs, ``settings`` will be a Python dictionary. But it's |
CM |
133 |
unlikely that any "civilian" would know that just by reading the code. There |
|
134 |
are a number of comprehension issues with the bit of code above that are |
|
135 |
obvious. |
fbfea7
|
136 |
|
8cdb1b
|
137 |
First, what's a "utility"? Well, for the purposes of this discussion, and |
CM |
138 |
for the purpose of the code above, it's just not very important. If you |
|
139 |
really want to know, you can read `this |
|
140 |
<http://www.muthukadan.net/docs/zca.html#utility>`_. However, still, readers |
|
141 |
of such code need to understand the concept in order to parse it. This is |
|
142 |
problem number one. |
fbfea7
|
143 |
|
8cdb1b
|
144 |
Second, what's this ``ISettings`` thing? It's an :term:`interface`. Is that |
CM |
145 |
important here? Not really, we're just using it as a "key" for some lookup |
|
146 |
based on its identity as a marker: it represents an object that has the |
|
147 |
dictionary API, but that's not very important in this context. That's |
|
148 |
problem number two. |
fbfea7
|
149 |
|
8cdb1b
|
150 |
Third of all, what does the ``getUtility`` function do? It's performing a |
CM |
151 |
lookup for the ``ISettings`` "utility" that should return.. well, a utility. |
|
152 |
Note how we've already built up a dependency on the understanding of an |
|
153 |
:term:`interface` and the concept of "utility" to answer this question: a bad |
|
154 |
sign so far. Note also that the answer is circular, a *really* bad sign. |
fbfea7
|
155 |
|
8cdb1b
|
156 |
Fourth, where does ``getUtility`` look to get the data? Well, the "component |
CM |
157 |
registry" of course. What's a component registry? Problem number four. |
fbfea7
|
158 |
|
8cdb1b
|
159 |
Fifth, assuming you buy that there's some magical registry hanging around, |
CM |
160 |
where *is* this registry? *Homina homina*... "around"? That's sort of the |
|
161 |
best answer in this context (a more specific answer would require knowledge |
|
162 |
of internals). Can there be more than one registry? Yes. So *which* |
|
163 |
registry does it find the registration in? Well, the "current" registry of |
|
164 |
course. In terms of :app:`Pyramid`, the current registry is a thread local |
|
165 |
variable. Using an API that consults a thread local makes understanding how |
|
166 |
it works non-local. |
fbfea7
|
167 |
|
0435ce
|
168 |
You've now bought in to the fact that there's a registry that is just |
bee624
|
169 |
"hanging around". But how does the registry get populated? Why, via code |
CM |
170 |
that calls directives like ``config.add_view``. In this particular case, |
|
171 |
however, the registration of ``ISettings`` is made by the framework itself |
|
172 |
"under the hood": it's not present in any user configuration. This is |
|
173 |
extremely hard to comprehend. Problem number six. |
fbfea7
|
174 |
|
8cdb1b
|
175 |
Clearly there's some amount of cognitive load here that needs to be borne by |
CM |
176 |
a reader of code that extends the :app:`Pyramid` framework due to its use of |
|
177 |
the ZCA, even if he or she is already an expert Python programmer and whom is |
|
178 |
an expert in the domain of web applications. This is suboptimal. |
fbfea7
|
179 |
|
CM |
180 |
Ameliorations |
|
181 |
+++++++++++++ |
|
182 |
|
8cdb1b
|
183 |
First, the primary amelioration: :app:`Pyramid` *does not expect application |
CM |
184 |
developers to understand ZCA concepts or any of its APIs*. If an |
|
185 |
*application* developer needs to understand a ZCA concept or API during the |
|
186 |
creation of a :app:`Pyramid` application, we've failed on some axis. |
fbfea7
|
187 |
|
fa8994
|
188 |
Instead, the framework hides the presence of the ZCA registry behind |
8cdb1b
|
189 |
special-purpose API functions that *do* use ZCA APIs. Take for example the |
CM |
190 |
``pyramid.security.authenticated_userid`` function, which returns the userid |
|
191 |
present in the current request or ``None`` if no userid is present in the |
|
192 |
current request. The application developer calls it like so: |
fbfea7
|
193 |
|
0ac7b0
|
194 |
.. ignore-next-block |
fbfea7
|
195 |
.. code-block:: python |
CM |
196 |
:linenos: |
|
197 |
|
edd915
|
198 |
from pyramid.security import authenticated_userid |
1b7aaf
|
199 |
userid = authenticated_userid(request) |
fbfea7
|
200 |
|
CM |
201 |
He now has the current user id. |
|
202 |
|
|
203 |
Under its hood however, the implementation of ``authenticated_userid`` |
|
204 |
is this: |
|
205 |
|
b7fdea
|
206 |
.. code-block:: python |
fbfea7
|
207 |
:linenos: |
CM |
208 |
|
|
209 |
def authenticated_userid(request): |
|
210 |
""" Return the userid of the currently authenticated user or |
|
211 |
``None`` if there is no authentication policy in effect or there |
|
212 |
is no currently authenticated user. """ |
|
213 |
|
c91abc
|
214 |
registry = request.registry # the ZCA component registry |
CM |
215 |
policy = registry.queryUtility(IAuthenticationPolicy) |
fbfea7
|
216 |
if policy is None: |
CM |
217 |
return None |
|
218 |
return policy.authenticated_userid(request) |
|
219 |
|
8cdb1b
|
220 |
Using such wrappers, we strive to always hide the ZCA API from application |
CM |
221 |
developers. Application developers should just never know about the ZCA API: |
|
222 |
they should call a Python function with some object germane to the domain as |
|
223 |
an argument, and it should returns a result. A corollary that follows is |
|
224 |
that any reader of an application that has been written using :app:`Pyramid` |
|
225 |
needn't understand the ZCA API either. |
fbfea7
|
226 |
|
8cdb1b
|
227 |
Hiding the ZCA API from application developers and code readers is a form of |
CM |
228 |
enhancing "domain specificity". No application developer wants to need to |
d141dd
|
229 |
understand the small, detailed mechanics of how a web framework does its |
8cdb1b
|
230 |
thing. People want to deal in concepts that are closer to the domain they're |
CM |
231 |
working in: for example, web developers want to know about *users*, not |
|
232 |
*utilities*. :app:`Pyramid` uses the ZCA as an implementation detail, not as |
|
233 |
a feature which is exposed to end users. |
fbfea7
|
234 |
|
8cdb1b
|
235 |
However, unlike application developers, *framework developers*, including |
CM |
236 |
people who want to override :app:`Pyramid` functionality via preordained |
|
237 |
framework plugpoints like traversal or view lookup *must* understand the ZCA |
|
238 |
registry API. |
fbfea7
|
239 |
|
8cdb1b
|
240 |
:app:`Pyramid` framework developers were so concerned about conceptual load |
CM |
241 |
issues of the ZCA registry API for framework developers that a `replacement |
|
242 |
registry implementation <http://svn.repoze.org/repoze.component/trunk>`_ |
|
243 |
named :mod:`repoze.component` was actually developed. Though this package |
|
244 |
has a registry implementation which is fully functional and well-tested, and |
|
245 |
its API is much nicer than the ZCA registry API, work on it was largely |
|
246 |
abandoned and it is not used in :app:`Pyramid`. We continued to use a ZCA |
|
247 |
registry within :app:`Pyramid` because it ultimately proved a better fit. |
aade46
|
248 |
|
fa8994
|
249 |
.. note:: We continued using ZCA registry rather than disusing it in |
CM |
250 |
favor of using the registry implementation in |
aade46
|
251 |
:mod:`repoze.component` largely because the ZCA concept of |
CM |
252 |
interfaces provides for use of an interface hierarchy, which is |
|
253 |
useful in a lot of scenarios (such as context type inheritance). |
|
254 |
Coming up with a marker type that was something like an interface |
|
255 |
that allowed for this functionality seemed like it was just |
|
256 |
reinventing the wheel. |
fbfea7
|
257 |
|
8cdb1b
|
258 |
Making framework developers and extenders understand the ZCA registry API is |
CM |
259 |
a trade-off. We (the :app:`Pyramid` developers) like the features that the |
|
260 |
ZCA registry gives us, and we have long-ago borne the weight of understanding |
|
261 |
what it does and how it works. The authors of :app:`Pyramid` understand the |
|
262 |
ZCA deeply and can read code that uses it as easily as any other code. |
fbfea7
|
263 |
|
ea32d6
|
264 |
But we recognize that developers who might want to extend the framework are not |
8cdb1b
|
265 |
as comfortable with the ZCA registry API as the original developers are with |
CM |
266 |
it. So, for the purposes of being kind to third-party :app:`Pyramid` |
|
267 |
framework developers in, we've drawn some lines in the sand. |
fbfea7
|
268 |
|
8cdb1b
|
269 |
In all "core" code, We've made use of ZCA global API functions such as |
CM |
270 |
``zope.component.getUtility`` and ``zope.component.getAdapter`` the exception |
|
271 |
instead of the rule. So instead of: |
fbfea7
|
272 |
|
8cdb1b
|
273 |
.. code-block:: python |
CM |
274 |
:linenos: |
fbfea7
|
275 |
|
8cdb1b
|
276 |
from pyramid.interfaces import IAuthenticationPolicy |
CM |
277 |
from zope.component import getUtility |
|
278 |
policy = getUtility(IAuthenticationPolicy) |
fbfea7
|
279 |
|
8cdb1b
|
280 |
:app:`Pyramid` code will usually do: |
fbfea7
|
281 |
|
8cdb1b
|
282 |
.. code-block:: python |
CM |
283 |
:linenos: |
fbfea7
|
284 |
|
8cdb1b
|
285 |
from pyramid.interfaces import IAuthenticationPolicy |
CM |
286 |
from pyramid.threadlocal import get_current_registry |
|
287 |
registry = get_current_registry() |
|
288 |
policy = registry.getUtility(IAuthenticationPolicy) |
fbfea7
|
289 |
|
8cdb1b
|
290 |
While the latter is more verbose, it also arguably makes it more obvious |
CM |
291 |
what's going on. All of the :app:`Pyramid` core code uses this pattern |
|
292 |
rather than the ZCA global API. |
fbfea7
|
293 |
|
CM |
294 |
Rationale |
|
295 |
+++++++++ |
|
296 |
|
8cdb1b
|
297 |
Here are the main rationales involved in the :app:`Pyramid` decision to use |
CM |
298 |
the ZCA registry: |
fbfea7
|
299 |
|
d141dd
|
300 |
- History. A nontrivial part of the answer to this question is "history". |
8cdb1b
|
301 |
Much of the design of :app:`Pyramid` is stolen directly from :term:`Zope`. |
CM |
302 |
Zope uses the ZCA registry to do a number of tricks. :app:`Pyramid` mimics |
|
303 |
these tricks, and, because the ZCA registry works well for that set of |
|
304 |
tricks, :app:`Pyramid` uses it for the same purposes. For example, the way |
|
305 |
that :app:`Pyramid` maps a :term:`request` to a :term:`view callable` using |
|
306 |
:term:`traversal` is lifted almost entirely from Zope. The ZCA registry |
|
307 |
plays an important role in the particulars of how this request to view |
|
308 |
mapping is done. |
fbfea7
|
309 |
|
8cdb1b
|
310 |
- Features. The ZCA component registry essentially provides what can be |
CM |
311 |
considered something like a "superdictionary", which allows for more |
|
312 |
complex lookups than retrieving a value based on a single key. Some of |
|
313 |
this lookup capability is very useful for end users, such as being able to |
|
314 |
register a view that is only found when the context is some class of |
|
315 |
object, or when the context implements some :term:`interface`. |
fbfea7
|
316 |
|
8cdb1b
|
317 |
- Singularity. There's only one "place" where "application configuration" |
CM |
318 |
lives in a :app:`Pyramid` application: in a component registry. The |
|
319 |
component registry answers questions made to it by the framework at runtime |
|
320 |
based on the configuration of *an application*. Note: "an application" is |
|
321 |
not the same as "a process", multiple independently configured copies of |
|
322 |
the same :app:`Pyramid` application are capable of running in the same |
0435ce
|
323 |
process space. |
fbfea7
|
324 |
|
8cdb1b
|
325 |
- Composability. A ZCA component registry can be populated imperatively, or |
CM |
326 |
there's an existing mechanism to populate a registry via the use of a |
55ce9d
|
327 |
configuration file (ZCML, via the optional :term:`pyramid_zcml` package). |
CM |
328 |
We didn't need to write a frontend from scratch to make use of |
|
329 |
configuration-file-driven registry population. |
fbfea7
|
330 |
|
8cdb1b
|
331 |
- Pluggability. Use of the ZCA registry allows for framework extensibility |
CM |
332 |
via a well-defined and widely understood plugin architecture. As long as |
|
333 |
framework developers and extenders understand the ZCA registry, it's |
|
334 |
possible to extend :app:`Pyramid` almost arbitrarily. For example, it's |
bee624
|
335 |
relatively easy to build a directive that registers several views "all at |
CM |
336 |
once", allowing app developers to use that directive as a "macro" in code |
|
337 |
that they write. This is somewhat of a differentiating feature from other |
|
338 |
(non-Zope) frameworks. |
fbfea7
|
339 |
|
8cdb1b
|
340 |
- Testability. Judicious use of the ZCA registry in framework code makes |
CM |
341 |
testing that code slightly easier. Instead of using monkeypatching or |
|
342 |
other facilities to register mock objects for testing, we inject |
|
343 |
dependencies via ZCA registrations and then use lookups in the code find |
|
344 |
our mock objects. |
fbfea7
|
345 |
|
8cdb1b
|
346 |
- Speed. The ZCA registry is very fast for a specific set of complex lookup |
CM |
347 |
scenarios that :app:`Pyramid` uses, having been optimized through the years |
|
348 |
for just these purposes. The ZCA registry contains optional C code for |
|
349 |
this purpose which demonstrably has no (or very few) bugs. |
fbfea7
|
350 |
|
bee624
|
351 |
- Ecosystem. Many existing Zope packages can be used in :app:`Pyramid` with |
CM |
352 |
few (or no) changes due to our use of the ZCA registry. |
fbfea7
|
353 |
|
CM |
354 |
Conclusion |
|
355 |
++++++++++ |
|
356 |
|
fd5ae9
|
357 |
If you only *develop applications* using :app:`Pyramid`, there's not much to |
0d43ed
|
358 |
complain about here. You just should never need to understand the ZCA |
8cdb1b
|
359 |
registry API: use documented :app:`Pyramid` APIs instead. However, you may |
CM |
360 |
be an application developer who doesn't read API documentation because it's |
|
361 |
unmanly. Instead you read the raw source code, and because you haven't read |
|
362 |
the documentation, you don't know what functions, classes, and methods even |
|
363 |
*form* the :app:`Pyramid` API. As a result, you've now written code that |
|
364 |
uses internals and you've painted yourself into a conceptual corner as a |
|
365 |
result of needing to wrestle with some ZCA-using implementation detail. If |
|
366 |
this is you, it's extremely hard to have a lot of sympathy for you. You'll |
|
367 |
either need to get familiar with how we're using the ZCA registry or you'll |
|
368 |
need to use only the documented APIs; that's why we document them as APIs. |
fbfea7
|
369 |
|
bee624
|
370 |
If you *extend* or *develop* :app:`Pyramid` (create new directives, use some |
CM |
371 |
of the more obscure "hooks" as described in :ref:`hooks_chapter`, or work on |
|
372 |
the :app:`Pyramid` core code), you will be faced with needing to understand |
|
373 |
at least some ZCA concepts. In some places it's used unabashedly, and will |
|
374 |
be forever. We know it's quirky, but it's also useful and fundamentally |
|
375 |
understandable if you take the time to do some reading about it. |
fbfea7
|
376 |
|
edd915
|
377 |
Pyramid Uses Interfaces Too Liberally |
CM |
378 |
------------------------------------- |
621dd7
|
379 |
|
CM |
380 |
In this `TOPP Engineering blog entry |
|
381 |
<http://www.coactivate.org/projects/topp-engineering/blog/2008/10/20/what-bothers-me-about-the-component-architecture/>`_, |
809744
|
382 |
Ian Bicking asserts that the way :mod:`repoze.bfg` used a Zope interface to |
CM |
383 |
represent an HTTP request method added too much indirection for not enough |
8cdb1b
|
384 |
gain. We agreed in general, and for this reason, :mod:`repoze.bfg` version |
CM |
385 |
1.1 (and subsequent versions including :app:`Pyramid` 1.0+) added :term:`view |
809744
|
386 |
predicate` and :term:`route predicate` modifiers to view configuration. |
CM |
387 |
Predicates are request-specific (or :term:`context` -specific) matching |
|
388 |
narrowers which don't use interfaces. Instead, each predicate uses a |
621dd7
|
389 |
domain-specific string as a match value. |
CM |
390 |
|
8cdb1b
|
391 |
For example, to write a view configuration which matches only requests with |
CM |
392 |
the ``POST`` HTTP request method, you might write a ``@view_config`` |
621dd7
|
393 |
decorator which mentioned the ``request_method`` predicate: |
CM |
394 |
|
|
395 |
.. code-block:: python |
|
396 |
:linenos: |
|
397 |
|
197f0c
|
398 |
from pyramid.view import view_config |
CM |
399 |
@view_config(name='post_view', request_method='POST', renderer='json') |
621dd7
|
400 |
def post_view(request): |
CM |
401 |
return 'POSTed' |
|
402 |
|
|
403 |
You might further narrow the matching scenario by adding an ``accept`` |
8cdb1b
|
404 |
predicate that narrows matching to something that accepts a JSON response: |
621dd7
|
405 |
|
CM |
406 |
.. code-block:: python |
|
407 |
:linenos: |
|
408 |
|
197f0c
|
409 |
from pyramid.view import view_config |
809744
|
410 |
@view_config(name='post_view', request_method='POST', |
CM |
411 |
accept='application/json', renderer='json') |
621dd7
|
412 |
def post_view(request): |
CM |
413 |
return 'POSTed' |
|
414 |
|
8cdb1b
|
415 |
Such a view would only match when the request indicated that HTTP request |
CM |
416 |
method was ``POST`` and that the remote user agent passed |
621dd7
|
417 |
``application/json`` (or, for that matter, ``application/*``) in its |
CM |
418 |
``Accept`` request header. |
|
419 |
|
|
420 |
"Under the hood", these features make no use of interfaces. |
|
421 |
|
809744
|
422 |
Many "prebaked" predicates exist. However, use of only "prebaked" predicates, |
CM |
423 |
however, doesn't entirely meet Ian's criterion. He would like to be able to |
|
424 |
match a request using a lambda or another function which interrogates the |
|
425 |
request imperatively. In :mod:`repoze.bfg` version 1.2, we acommodate this by |
|
426 |
allowing people to define "custom" view predicates: |
6225a2
|
427 |
|
CM |
428 |
.. code-block:: python |
|
429 |
:linenos: |
|
430 |
|
197f0c
|
431 |
from pyramid.view import view_config |
d868ff
|
432 |
from pyramid.response import Response |
6225a2
|
433 |
|
CM |
434 |
def subpath(context, request): |
|
435 |
return request.subpath and request.subpath[0] == 'abc' |
|
436 |
|
197f0c
|
437 |
@view_config(custom_predicates=(subpath,)) |
6225a2
|
438 |
def aview(request): |
CM |
439 |
return Response('OK') |
|
440 |
|
|
441 |
The above view will only match when the first element of the request's |
|
442 |
:term:`subpath` is ``abc``. |
621dd7
|
443 |
|
b1d4c0
|
444 |
.. _zcml_encouragement: |
CM |
445 |
|
edd915
|
446 |
Pyramid "Encourages Use of ZCML" |
CM |
447 |
-------------------------------- |
fbfea7
|
448 |
|
809744
|
449 |
:term:`ZCML` is a configuration language that can be used to configure the |
8cdb1b
|
450 |
:term:`Zope Component Architecture` registry that :app:`Pyramid` uses for |
809744
|
451 |
application configuration. Often people claim that Pyramid "needs ZCML". |
fbfea7
|
452 |
|
c9c3c4
|
453 |
It doesn't. In :app:`Pyramid` 1.0, ZCML doesn't ship as part of the core; |
CM |
454 |
instead it ships in the :term:`pyramid_zcml` add-on package, which is |
|
455 |
completely optional. No ZCML is required at all to use :app:`Pyramid`, nor |
|
456 |
any other sort of frameworky declarative frontend to application |
|
457 |
configuration. |
fbfea7
|
458 |
|
7bb032
|
459 |
.. _model_traversal_confusion: |
CM |
460 |
|
edd915
|
461 |
Pyramid Uses "Model" To Represent A Node In The Graph of Objects Traversed |
CM |
462 |
-------------------------------------------------------------------------- |
7bb032
|
463 |
|
a5ffd6
|
464 |
The ``repoze.bfg`` documentation used to refer to the graph being traversed |
CM |
465 |
when :term:`traversal` is used as a "model graph". A terminology overlap |
|
466 |
confused people who wrote applications that always use ORM packages such as |
|
467 |
SQLAlchemy, which has a different notion of the definition of a "model". As |
a44b4f
|
468 |
a result, in Pyramid 1.0a7, the tree of objects traversed is now renamed to |
a5ffd6
|
469 |
:term:`resource tree` and its components are now named :term:`resource` |
CM |
470 |
objects. Associated APIs have been changed. This hopefully alleviates the |
|
471 |
terminology confusion caused by overriding the term "model". |
7bb032
|
472 |
|
edd915
|
473 |
Pyramid Does Traversal, And I Don't Like Traversal |
CM |
474 |
-------------------------------------------------- |
7bb032
|
475 |
|
a5ffd6
|
476 |
In :app:`Pyramid`, :term:`traversal` is the act of resolving a URL path to a |
CM |
477 |
:term:`resource` object in a resource tree. Some people are uncomfortable |
ae1d22
|
478 |
with this notion, and believe it is wrong. Thankfully, if you use |
CM |
479 |
:app:`Pyramid`, and you don't want to model your application in terms of a |
|
480 |
resource tree, you needn't use it at all. Instead, use :term:`URL dispatch` |
|
481 |
to map URL paths to views. |
c58cf2
|
482 |
|
ae1d22
|
483 |
The idea that some folks believe traversal is unilaterally "wrong" is |
CM |
484 |
understandable. The people who believe it is wrong almost invariably have |
|
485 |
all of their data in a relational database. Relational databases aren't |
a5ffd6
|
486 |
naturally hierarchical, so "traversing" one like a tree is not possible. |
c58cf2
|
487 |
|
ae1d22
|
488 |
However, folks who deem traversal unilaterally wrong are neglecting to take |
CM |
489 |
into account that many persistence mechanisms *are* hierarchical. Examples |
a5ffd6
|
490 |
include a filesystem, an LDAP database, a :term:`ZODB` (or another type of |
CM |
491 |
graph) database, an XML document, and the Python module namespace. It is |
|
492 |
often convenient to model the frontend to a hierarchical data store as a |
|
493 |
graph, using traversal to apply views to objects that either *are* the |
|
494 |
resources in the tree being traversed (such as in the case of ZODB) or at |
|
495 |
least ones which stand in for them (such as in the case of wrappers for files |
|
496 |
from the filesystem). |
c58cf2
|
497 |
|
a5ffd6
|
498 |
Also, many website structures are naturally hierarchical, even if the data |
CM |
499 |
which drives them isn't. For example, newspaper websites are often extremely |
|
500 |
hierarchical: sections within sections within sections, ad infinitum. If you |
|
501 |
want your URLs to indicate this structure, and the structure is indefinite |
|
502 |
(the number of nested sections can be "N" instead of some fixed number), a |
|
503 |
resource tree is an excellent way to model this, even if the backend is a |
|
504 |
relational database. In this situation, the resource tree a just a site |
|
505 |
structure. |
c58cf2
|
506 |
|
ae1d22
|
507 |
Traversal also offers better composability of applications than URL dispatch, |
CM |
508 |
because it doesn't rely on a fixed ordering of URL matching. You can compose |
|
509 |
a set of disparate functionality (and add to it later) around a mapping of |
|
510 |
view to resource more predictably than trying to get "the right" ordering of |
|
511 |
URL pattern matching. |
|
512 |
|
|
513 |
But the point is ultimately moot. If you don't want to use traversal, you |
|
514 |
needn't. Use URL dispatch instead. |
c58cf2
|
515 |
|
edd915
|
516 |
Pyramid Does URL Dispatch, And I Don't Like URL Dispatch |
CM |
517 |
-------------------------------------------------------- |
c58cf2
|
518 |
|
ae1d22
|
519 |
In :app:`Pyramid`, :term:`url dispatch` is the act of resolving a URL path to |
CM |
520 |
a :term:`view` callable by performing pattern matching against some set of |
|
521 |
ordered route definitions. The route definitions are examined in order: the |
|
522 |
first pattern which matches is used to associate the URL with a view |
|
523 |
callable. |
c58cf2
|
524 |
|
ae1d22
|
525 |
Some people are uncomfortable with this notion, and believe it is wrong. |
CM |
526 |
These are usually people who are steeped deeply in :term:`Zope`. Zope does |
|
527 |
not provide any mechanism except :term:`traversal` to map code to URLs. This |
|
528 |
is mainly because Zope effectively requires use of :term:`ZODB`, which is a |
|
529 |
hierarchical object store. Zope also supports relational databases, but |
|
530 |
typically the code that calls into the database lives somewhere in the ZODB |
|
531 |
object graph (or at least is a :term:`view` related to a node in the object |
|
532 |
graph), and traversal is required to reach this code. |
c58cf2
|
533 |
|
589ee7
|
534 |
I'll argue that URL dispatch is ultimately useful, even if you want to use |
CM |
535 |
traversal as well. You can actually *combine* URL dispatch and traversal in |
fd5ae9
|
536 |
:app:`Pyramid` (see :ref:`hybrid_chapter`). One example of such a usage: if |
589ee7
|
537 |
you want to emulate something like Zope 2's "Zope Management Interface" UI on |
CM |
538 |
top of your object graph (or any administrative interface), you can register |
62a48b
|
539 |
a route like ``config.add_route('manage', '/manage/*traverse')`` and then |
589ee7
|
540 |
associate "management" views in your code by using the ``route_name`` |
62a48b
|
541 |
argument to a ``view`` configuration, |
CM |
542 |
e.g. ``config.add_view('.some.callable', context=".some.Resource", |
|
543 |
route_name='manage')``. If you wire things up this way someone then walks up |
|
544 |
to for example, ``/manage/ob1/ob2``, they might be presented with a |
|
545 |
management interface, but walking up to ``/ob1/ob2`` would present them with |
|
546 |
the default object view. There are other tricks you can pull in these hybrid |
|
547 |
configurations if you're clever (and maybe masochistic) too. |
c58cf2
|
548 |
|
ae1d22
|
549 |
Also, if you are a URL dispatch hater, if you should ever be asked to write |
CM |
550 |
an application that must use some legacy relational database structure, you |
|
551 |
might find that using URL dispatch comes in handy for one-off associations |
|
552 |
between views and URL paths. Sometimes it's just pointless to add a node to |
|
553 |
the object graph that effectively represents the entry point for some bit of |
|
554 |
code. You can just use a route and be done with it. If a route matches, a |
|
555 |
view associated with the route will be called; if no route matches, |
|
556 |
:app:`Pyramid` falls back to using traversal. |
c58cf2
|
557 |
|
ae1d22
|
558 |
But the point is ultimately moot. If you use :app:`Pyramid`, and you really |
CM |
559 |
don't want to use URL dispatch, you needn't use it at all. Instead, use |
|
560 |
:term:`traversal` exclusively to map URL paths to views, just like you do in |
|
561 |
:term:`Zope`. |
c58cf2
|
562 |
|
edd915
|
563 |
Pyramid Views Do Not Accept Arbitrary Keyword Arguments |
CM |
564 |
------------------------------------------------------- |
f34859
|
565 |
|
589ee7
|
566 |
Many web frameworks (Zope, TurboGears, Pylons 1.X, Django) allow for their |
CM |
567 |
variant of a :term:`view callable` to accept arbitrary keyword or positional |
|
568 |
arguments, which are "filled in" using values present in the ``request.POST`` |
|
569 |
or ``request.GET`` dictionaries or by values present in the "route match |
|
570 |
dictionary". For example, a Django view will accept positional arguments |
|
571 |
which match information in an associated "urlconf" such as |
|
572 |
``r'^polls/(?P<poll_id>\d+)/$``: |
f34859
|
573 |
|
CM |
574 |
.. code-block:: python |
|
575 |
:linenos: |
|
576 |
|
|
577 |
def aview(request, poll_id): |
|
578 |
return HttpResponse(poll_id) |
|
579 |
|
|
580 |
Zope, likewise allows you to add arbitrary keyword and positional |
a5ffd6
|
581 |
arguments to any method of a resource object found via traversal: |
f34859
|
582 |
|
0ac7b0
|
583 |
.. ignore-next-block |
f34859
|
584 |
.. code-block:: python |
CM |
585 |
:linenos: |
0ac7b0
|
586 |
|
CM |
587 |
from persistent import Persistent |
f34859
|
588 |
|
CM |
589 |
class MyZopeObject(Persistent): |
|
590 |
def aview(self, a, b, c=None): |
|
591 |
return '%s %s %c' % (a, b, c) |
|
592 |
|
ae1d22
|
593 |
When this method is called as the result of being the published callable, the |
CM |
594 |
Zope request object's GET and POST namespaces are searched for keys which |
|
595 |
match the names of the positional and keyword arguments in the request, and |
|
596 |
the method is called (if possible) with its argument list filled with values |
|
597 |
mentioned therein. TurboGears and Pylons 1.X operate similarly. |
f34859
|
598 |
|
ae1d22
|
599 |
Out of the box, :app:`Pyramid` is configured to have none of these features. |
522c23
|
600 |
By default, :mod:`pyramid` view callables always accept only ``request`` and |
ae1d22
|
601 |
no other arguments. The rationale: this argument specification matching done |
CM |
602 |
aggressively can be costly, and :app:`Pyramid` has performance as one of its |
|
603 |
main goals, so we've decided to make people, by default, obtain information |
|
604 |
by interrogating the request object within the view callable body instead of |
|
605 |
providing magic to do unpacking into the view argument list. |
f34859
|
606 |
|
ae1d22
|
607 |
However, as of :app:`Pyramid` 1.0a9, user code can influence the way view |
CM |
608 |
callables are expected to be called, making it possible to compose a system |
|
609 |
out of view callables which are called with arbitrary arguments. See |
|
610 |
:ref:`using_a_view_mapper`. |
f34859
|
611 |
|
edd915
|
612 |
Pyramid Provides Too Few "Rails" |
CM |
613 |
-------------------------------- |
6956a8
|
614 |
|
fd5ae9
|
615 |
By design, :app:`Pyramid` is not a particularly "opinionated" web framework. |
f52ff2
|
616 |
It has a relatively parsimonious feature set. It contains no built in ORM |
CM |
617 |
nor any particular database bindings. It contains no form generation |
|
618 |
framework. It has no administrative web user interface. It has no built in |
|
619 |
text indexing. It does not dictate how you arrange your code. |
6956a8
|
620 |
|
f52ff2
|
621 |
Such opinionated functionality exists in applications and frameworks built |
fd5ae9
|
622 |
*on top* of :app:`Pyramid`. It's intended that higher-level systems emerge |
CM |
623 |
built using :app:`Pyramid` as a base. See also :ref:`apps_are_extensible`. |
6956a8
|
624 |
|
edd915
|
625 |
Pyramid Provides Too Many "Rails" |
CM |
626 |
--------------------------------- |
6956a8
|
627 |
|
a09149
|
628 |
:app:`Pyramid` provides some features that other web frameworks do not. |
CM |
629 |
These are features meant for use cases that might not make sense to you if |
|
630 |
you're building a simple "bespoke" web application: |
6956a8
|
631 |
|
a09149
|
632 |
- An optional way to map URLs to code using :term:`traversal` which implies a |
CM |
633 |
walk of a :term:`resource tree`. |
6956a8
|
634 |
|
a09149
|
635 |
- The ability to aggregate Pyramid application configuration from multiple |
CM |
636 |
sources using :meth:`pyramid.config.Configurator.include`. |
6956a8
|
637 |
|
a09149
|
638 |
- View and subscriber registrations made using :term:`interface` objects |
CM |
639 |
instead of class objects (e.g. :ref:`using_resource_interfaces`). |
|
640 |
|
|
641 |
- A declarative :term:`authorization` system. |
|
642 |
|
|
643 |
- Multiple separate I18N :term:`translation string` factories, each of which |
|
644 |
can name its own "domain". |
|
645 |
|
|
646 |
These features are important to the authors of :app:`Pyramid`. The |
|
647 |
:app:`Pyramid` authors are often commissioned to build CMS-style |
|
648 |
applications. Such applications are often "frameworky" because they have |
|
649 |
more than one deployment. Each deployment requires a slightly different |
ae1d22
|
650 |
composition of sub-applications, and the framework and sub-applications often |
a09149
|
651 |
need to be *extensible*. Because the application has more than one |
CM |
652 |
deployment, pluggability and extensibility is important, as maintaining |
|
653 |
multiple forks of the application, one per deployment, is extremely |
|
654 |
undesirable. Because it's easier to extend a system that uses |
|
655 |
:term:`traversal` "from the outside" than it is to do the same in a system |
|
656 |
that uses :term:`URL dispatch`, each deployment uses a :term:`resource tree` |
|
657 |
composed of a persistent tree of domain model objects, and uses |
|
658 |
:term:`traversal` to map :term:`view callable` code to resources in the tree. |
|
659 |
The resource tree contains very granular security declarations, as resources |
ae1d22
|
660 |
are owned and accessible by different sets of users. Interfaces are used to |
CM |
661 |
make unit testing and implementation substitutability easier. |
a09149
|
662 |
|
CM |
663 |
In a bespoke web application, usually there's a single canonical deployment, |
|
664 |
and therefore no possibility of multiple code forks. Extensibility is not |
ae1d22
|
665 |
required; the code is just changed in-place. Security requirements are often |
CM |
666 |
less granular. Using the features listed above will often be overkill for |
|
667 |
such an application. |
a09149
|
668 |
|
CM |
669 |
If you don't like these features, it doesn't mean you can't or shouldn't use |
ae1d22
|
670 |
:app:`Pyramid`. They are all optional, and a lot of time has been spent |
a09149
|
671 |
making sure you don't need to know about them up-front. You can build |
CM |
672 |
"Pylons-1.X-style" applications using :app:`Pyramid` that are purely bespoke |
|
673 |
by ignoring the features above. You may find these features handy later |
|
674 |
after building a "bespoke" web application that suddenly becomes popular and |
|
675 |
requires extensibility because it must be deployed in multiple locations. |
6956a8
|
676 |
|
edd915
|
677 |
Pyramid Is Too Big |
CM |
678 |
------------------ |
4b32c8
|
679 |
|
fd5ae9
|
680 |
"The :app:`Pyramid` compressed tarball is almost 2MB. It must be |
0435ce
|
681 |
enormous!" |
4b32c8
|
682 |
|
CM |
683 |
No. We just ship it with test code and helper templates. Here's a |
|
684 |
breakdown of what's included in subdirectories of the package tree: |
|
685 |
|
|
686 |
docs/ |
|
687 |
|
5c1510
|
688 |
3.0MB |
4b32c8
|
689 |
|
5c1510
|
690 |
pyramid/tests/ |
4b32c8
|
691 |
|
5c1510
|
692 |
1.1MB |
4b32c8
|
693 |
|
5c1510
|
694 |
pyramid/paster_templates/ |
4b32c8
|
695 |
|
5c1510
|
696 |
804KB |
4b32c8
|
697 |
|
5c1510
|
698 |
pyramid/ (except for ``pyramd/tests and pyramid/paster_templates``) |
4b32c8
|
699 |
|
5c1510
|
700 |
539K |
4b32c8
|
701 |
|
fd5ae9
|
702 |
The actual :app:`Pyramid` runtime code is about 10% of the total size of the |
589ee7
|
703 |
tarball omitting docs, helper templates used for package generation, and test |
CM |
704 |
code. Of the approximately 19K lines of Python code in the package, the code |
|
705 |
that actually has a chance of executing during normal operation, excluding |
|
706 |
tests and paster template Python files, accounts for approximately 5K lines |
|
707 |
of Python code. This is comparable to Pylons 1.X, which ships with a little |
|
708 |
over 2K lines of Python code, excluding tests. |
4b32c8
|
709 |
|
edd915
|
710 |
Pyramid Has Too Many Dependencies |
CM |
711 |
--------------------------------- |
4b32c8
|
712 |
|
ddb6a8
|
713 |
This is true. At the time of this writing, the total number of Python |
8cdb1b
|
714 |
package distributions that :app:`Pyramid` depends upon transitively is 18 if |
48d4c8
|
715 |
you use Python 2.6 or 2.7, or 16 if you use Python 2.5. This is a lot more |
CM |
716 |
than zero package distribution dependencies: a metric which various Python |
|
717 |
microframeworks and Django boast. |
4b32c8
|
718 |
|
8cdb1b
|
719 |
The :mod:`zope.component` and :mod:`zope.configuration` packages on which |
CM |
720 |
:app:`Pyramid` depends have transitive dependencies on several other packages |
|
721 |
(:mod:`zope.schema`, :mod:`zope.i18n`, :mod:`zope.event`, |
|
722 |
:mod:`zope.interface`, :mod:`zope.deprecation`, :mod:`zope.i18nmessageid`). |
|
723 |
We've been working with the Zope community to try to collapse and untangle |
|
724 |
some of these dependencies. We'd prefer that these packages have fewer |
|
725 |
packages as transitive dependencies, and that much of the functionality of |
|
726 |
these packages was moved into a smaller *number* of packages. |
ddb6a8
|
727 |
|
fd5ae9
|
728 |
:app:`Pyramid` also has its own direct dependencies, such as :term:`Paste`, |
809744
|
729 |
:term:`Chameleon`, :term:`Mako` and :term:`WebOb`, and some of these in turn |
CM |
730 |
have their own transitive dependencies. |
4b32c8
|
731 |
|
8cdb1b
|
732 |
It should be noted that :app:`Pyramid` is positively lithe compared to |
CM |
733 |
:term:`Grok`, a different Zope-based framework. As of this writing, in its |
ef9f29
|
734 |
default configuration, Grok has 109 package distribution dependencies. The |
8cdb1b
|
735 |
number of dependencies required by :app:`Pyramid` is many times fewer than |
CM |
736 |
Grok (or Zope itself, upon which Grok is based). :app:`Pyramid` has a number |
|
737 |
of package distribution dependencies comparable to similarly-targeted |
|
738 |
frameworks such as Pylons 1.X. |
4b32c8
|
739 |
|
8cdb1b
|
740 |
We try not to reinvent too many wheels (at least the ones that don't need |
CM |
741 |
reinventing), and this comes at the cost of some number of dependencies. |
|
742 |
However, "number of package distributions" is just not a terribly great |
|
743 |
metric to measure complexity. For example, the :mod:`zope.event` |
|
744 |
distribution on which :app:`Pyramid` depends has a grand total of four lines |
|
745 |
of runtime code. As noted above, we're continually trying to agitate for a |
|
746 |
collapsing of these sorts of packages into fewer distribution files. |
93f694
|
747 |
|
edd915
|
748 |
Pyramid "Cheats" To Obtain Speed |
CM |
749 |
-------------------------------- |
93f694
|
750 |
|
8cdb1b
|
751 |
Complaints have been lodged by other web framework authors at various times |
CM |
752 |
that :app:`Pyramid` "cheats" to gain performance. One claimed cheating |
|
753 |
mechanism is our use (transitively) of the C extensions provided by |
|
754 |
:mod:`zope.interface` to do fast lookups. Another claimed cheating mechanism |
|
755 |
is the religious avoidance of extraneous function calls. |
93f694
|
756 |
|
8cdb1b
|
757 |
If there's such a thing as cheating to get better performance, we want to |
CM |
758 |
cheat as much as possible. We optimize :app:`Pyramid` aggressively. This |
|
759 |
comes at a cost: the core code has sections that could be expressed more |
|
760 |
readably. As an amelioration, we've commented these sections liberally. |
4b32c8
|
761 |
|
edd915
|
762 |
Pyramid Gets Its Terminology Wrong ("MVC") |
CM |
763 |
------------------------------------------ |
384ead
|
764 |
|
8cdb1b
|
765 |
"I'm a MVC web framework user, and I'm confused. :app:`Pyramid` calls the |
CM |
766 |
controller a view! And it doesn't have any controllers." |
384ead
|
767 |
|
5df6be
|
768 |
If you are in this camp, you might have come to expect things about how your |
CM |
769 |
existing "MVC" framework uses its terminology. For example, you probably |
|
770 |
expect that models are ORM models, controllers are classes that have methods |
fd5ae9
|
771 |
that map to URLs, and views are templates. :app:`Pyramid` indeed has each of |
5df6be
|
772 |
these concepts, and each probably *works* almost exactly like your existing |
CM |
773 |
"MVC" web framework. We just don't use the "MVC" terminology, as we can't |
|
774 |
square its usage in the web framework space with historical reality. |
|
775 |
|
8cdb1b
|
776 |
People very much want to give web applications the same properties as common |
CM |
777 |
desktop GUI platforms by using similar terminology, and to provide some frame |
|
778 |
of reference for how various components in the common web framework might |
|
779 |
hang together. But in the opinion of the author, "MVC" doesn't match the web |
|
780 |
very well in general. Quoting from the `Model-View-Controller Wikipedia entry |
16cd50
|
781 |
<http://en.wikipedia.org/wiki/Model–view–controller>`_: |
BL |
782 |
|
|
783 |
.. code-block:: text |
384ead
|
784 |
|
CM |
785 |
Though MVC comes in different flavors, control flow is generally as |
|
786 |
follows: |
|
787 |
|
|
788 |
The user interacts with the user interface in some way (for |
|
789 |
example, presses a mouse button). |
|
790 |
|
|
791 |
The controller handles the input event from the user interface, |
|
792 |
often via a registered handler or callback and converts the event |
|
793 |
into appropriate user action, understandable for the model. |
|
794 |
|
|
795 |
The controller notifies the model of the user action, possibly |
|
796 |
resulting in a change in the model's state. (For example, the |
|
797 |
controller updates the user's shopping cart.)[5] |
|
798 |
|
|
799 |
A view queries the model in order to generate an appropriate |
|
800 |
user interface (for example, the view lists the shopping cart's |
|
801 |
contents). Note that the view gets its own data from the model. |
|
802 |
|
|
803 |
The controller may (in some implementations) issue a general |
|
804 |
instruction to the view to render itself. In others, the view is |
|
805 |
automatically notified by the model of changes in state |
|
806 |
(Observer) which require a screen update. |
|
807 |
|
|
808 |
The user interface waits for further user interactions, which |
|
809 |
restarts the cycle. |
|
810 |
|
8cdb1b
|
811 |
To the author, it seems as if someone edited this Wikipedia definition, |
CM |
812 |
tortuously couching concepts in the most generic terms possible in order to |
|
813 |
account for the use of the term "MVC" by current web frameworks. I doubt |
|
814 |
such a broad definition would ever be agreed to by the original authors of |
|
815 |
the MVC pattern. But *even so*, it seems most "MVC" web frameworks fail to |
|
816 |
meet even this falsely generic definition. |
384ead
|
817 |
|
8cdb1b
|
818 |
For example, do your templates (views) always query models directly as is |
CM |
819 |
claimed in "note that the view gets its own data from the model"? Probably |
|
820 |
not. My "controllers" tend to do this, massaging the data for easier use by |
|
821 |
the "view" (template). What do you do when your "controller" returns JSON? Do |
|
822 |
your controllers use a template to generate JSON? If not, what's the "view" |
|
823 |
then? Most MVC-style GUI web frameworks have some sort of event system |
|
824 |
hooked up that lets the view detect when the model changes. The web just has |
|
825 |
no such facility in its current form: it's effectively pull-only. |
384ead
|
826 |
|
a5ffd6
|
827 |
So, in the interest of not mistaking desire with reality, and instead of |
CM |
828 |
trying to jam the square peg that is the web into the round hole of "MVC", we |
|
829 |
just punt and say there are two things: resources and views. The resource |
|
830 |
tree represents a site structure, the view presents a resource. The |
|
831 |
templates are really just an implementation detail of any given view: a view |
|
832 |
doesn't need a template to return a response. There's no "controller": it |
|
833 |
just doesn't exist. The "model" is either represented by the resource tree |
|
834 |
or by a "domain model" (like a SQLAlchemy model) that is separate from the |
|
835 |
framework entirely. This seems to us like more reasonable terminology, given |
|
836 |
the current constraints of the web. |
384ead
|
837 |
|
0435ce
|
838 |
.. _apps_are_extensible: |
CM |
839 |
|
edd915
|
840 |
Pyramid Applications are Extensible; I Don't Believe In Application Extensibility |
CM |
841 |
--------------------------------------------------------------------------------- |
1df991
|
842 |
|
8cdb1b
|
843 |
Any :app:`Pyramid` application written obeying certain constraints is |
CM |
844 |
*extensible*. This feature is discussed in the :app:`Pyramid` documentation |
55f967
|
845 |
chapters named :ref:`extending_chapter` and :ref:`advconfig_narr`. It is |
CM |
846 |
made possible by the use of the :term:`Zope Component Architecture` and |
|
847 |
within :app:`Pyramid`. |
ddb6a8
|
848 |
|
CM |
849 |
"Extensible", in this context, means: |
1df991
|
850 |
|
CM |
851 |
- The behavior of an application can be overridden or extended in a |
|
852 |
particular *deployment* of the application without requiring that |
|
853 |
the deployer modify the source of the original application. |
|
854 |
|
ddb6a8
|
855 |
- The original developer is not required to anticipate any |
CM |
856 |
extensibility plugpoints at application creation time to allow |
|
857 |
fundamental application behavior to be overriden or extended. |
1df991
|
858 |
|
ddb6a8
|
859 |
- The original developer may optionally choose to anticipate an |
0d43ed
|
860 |
application-specific set of plugpoints, which may be hooked by |
ddb6a8
|
861 |
a deployer. If he chooses to use the facilities provided by the |
CM |
862 |
ZCA, the original developer does not need to think terribly hard |
|
863 |
about the mechanics of introducing such a plugpoint. |
1df991
|
864 |
|
8cdb1b
|
865 |
Many developers seem to believe that creating extensible applications is "not |
CM |
866 |
worth it". They instead suggest that modifying the source of a given |
|
867 |
application for each deployment to override behavior is more reasonable. |
|
868 |
Much discussion about version control branching and merging typically ensues. |
1df991
|
869 |
|
8cdb1b
|
870 |
It's clear that making every application extensible isn't required. The |
CM |
871 |
majority of web applications only have a single deployment, and thus needn't |
|
872 |
be extensible at all. However, some web applications have multiple |
|
873 |
deployments, and some have *many* deployments. For example, a generic |
|
874 |
"content management" system (CMS) may have basic functionality that needs to |
|
875 |
be extended for a particular deployment. That CMS system may be deployed for |
|
876 |
many organizations at many places. Some number of deployments of this CMS |
|
877 |
may be deployed centrally by a third party and managed as a group. It's |
|
878 |
useful to be able to extend such a system for each deployment via preordained |
|
879 |
plugpoints than it is to continually keep each software branch of the system |
|
880 |
in sync with some upstream source: the upstream developers may change code in |
|
881 |
such a way that your changes to the same codebase conflict with theirs in |
|
882 |
fiddly, trivial ways. Merging such changes repeatedly over the lifetime of a |
|
883 |
deployment can be difficult and time consuming, and it's often useful to be |
|
884 |
able to modify an application for a particular deployment in a less invasive |
|
885 |
way. |
1df991
|
886 |
|
8cdb1b
|
887 |
If you don't want to think about :app:`Pyramid` application extensibility at |
CM |
888 |
all, you needn't. You can ignore extensibility entirely. However, if you |
|
889 |
follow the set of rules defined in :ref:`extending_chapter`, you don't need |
|
890 |
to *make* your application extensible: any application you write in the |
|
891 |
framework just *is* automatically extensible at a basic level. The |
|
892 |
mechanisms that deployers use to extend it will be necessarily coarse: |
|
893 |
typically, views, routes, and resources will be capable of being |
|
894 |
overridden. But for most minor (and even some major) customizations, these |
|
895 |
are often the only override plugpoints necessary: if the application doesn't |
|
896 |
do exactly what the deployment requires, it's often possible for a deployer |
|
897 |
to override a view, route, or resource and quickly make it do what he or she |
|
898 |
wants it to do in ways *not necessarily anticipated by the original |
|
899 |
developer*. Here are some example scenarios demonstrating the benefits of |
|
900 |
such a feature. |
1df991
|
901 |
|
8cdb1b
|
902 |
- If a deployment needs a different styling, the deployer may override the |
CM |
903 |
main template and the CSS in a separate Python package which defines |
|
904 |
overrides. |
f5d708
|
905 |
|
8cdb1b
|
906 |
- If a deployment needs an application page to do something differently needs |
CM |
907 |
it to expose more or different information, the deployer may override the |
|
908 |
view that renders the page within a separate Python package. |
f5d708
|
909 |
|
8cdb1b
|
910 |
- If a deployment needs an additional feature, the deployer may add a view to |
CM |
911 |
the override package. |
f5d708
|
912 |
|
8cdb1b
|
913 |
As long as the fundamental design of the upstream package doesn't change, |
CM |
914 |
these types of modifications often survive across many releases of the |
|
915 |
upstream package without needing to be revisited. |
ddb6a8
|
916 |
|
8cdb1b
|
917 |
Extending an application externally is not a panacea, and carries a set of |
CM |
918 |
risks similar to branching and merging: sometimes major changes upstream will |
|
919 |
cause you to need to revisit and update some of your modifications. But you |
|
920 |
won't regularly need to deal wth meaningless textual merge conflicts that |
|
921 |
trivial changes to upstream packages often entail when it comes time to |
|
922 |
update the upstream package, because if you extend an application externally, |
|
923 |
there just is no textual merge done. Your modifications will also, for |
|
924 |
whatever its worth, be contained in one, canonical, well-defined place. |
ddb6a8
|
925 |
|
8cdb1b
|
926 |
Branching an application and continually merging in order to get new features |
CM |
927 |
and bugfixes is clearly useful. You can do that with a :app:`Pyramid` |
|
928 |
application just as usefully as you can do it with any application. But |
|
929 |
deployment of an application written in :app:`Pyramid` makes it possible to |
|
930 |
avoid the need for this even if the application doesn't define any plugpoints |
|
931 |
ahead of time. It's possible that promoters of competing web frameworks |
|
932 |
dismiss this feature in favor of branching and merging because applications |
|
933 |
written in their framework of choice aren't extensible out of the box in a |
f5d708
|
934 |
comparably fundamental way. |
CM |
935 |
|
8cdb1b
|
936 |
While :app:`Pyramid` application are fundamentally extensible even if you |
CM |
937 |
don't write them with specific extensibility in mind, if you're moderately |
|
938 |
adventurous, you can also take it a step further. If you learn more about |
|
939 |
the :term:`Zope Component Architecture`, you can optionally use it to expose |
|
940 |
other more domain-specific configuration plugpoints while developing an |
|
941 |
application. The plugpoints you expose needn't be as coarse as the ones |
|
942 |
provided automatically by :app:`Pyramid` itself. For example, you might |
bee624
|
943 |
compose your own directive that configures a set of views for a prebaked |
CM |
944 |
purpose (e.g. ``restview`` or somesuch) , allowing other people to refer to |
|
945 |
that directive when they make declarations in the ``includeme`` of their |
|
946 |
customization package. There is a cost for this: the developer of an |
|
947 |
application that defines custom plugpoints for its deployers will need to |
8cdb1b
|
948 |
understand the ZCA or he will need to develop his own similar extensibility |
CM |
949 |
system. |
1df991
|
950 |
|
8cdb1b
|
951 |
Ultimately, any argument about whether the extensibility features lent to |
e8db03
|
952 |
applications by :app:`Pyramid` are "good" or "bad" is mostly pointless. You |
8cdb1b
|
953 |
needn't take advantage of the extensibility features provided by a particular |
CM |
954 |
:app:`Pyramid` application in order to affect a modification for a particular |
|
955 |
set of its deployments. You can ignore the application's extensibility |
|
956 |
plugpoints entirely, and instead use version control branching and merging to |
|
957 |
manage application deployment modifications instead, as if you were deploying |
15be26
|
958 |
an application written using any other web framework. |
CM |
959 |
|
edd915
|
960 |
Zope 3 Enforces "TTW" Authorization Checks By Default; Pyramid Does Not |
CM |
961 |
----------------------------------------------------------------------- |
a39aca
|
962 |
|
CM |
963 |
Challenge |
672bbe
|
964 |
+++++++++ |
a39aca
|
965 |
|
8cdb1b
|
966 |
:app:`Pyramid` performs automatic authorization checks only at :term:`view` |
CM |
967 |
execution time. Zope 3 wraps context objects with a `security proxy |
83fa67
|
968 |
<http://wiki.zope.org/zope3/WhatAreSecurityProxies>`_, which causes Zope 3 to |
8cdb1b
|
969 |
do also security checks during attribute access. I like this, because it |
CM |
970 |
means: |
a39aca
|
971 |
|
CM |
972 |
#) When I use the security proxy machinery, I can have a view that |
|
973 |
conditionally displays certain HTML elements (like form fields) or |
0d43ed
|
974 |
prevents certain attributes from being modified depending on the the |
CM |
975 |
permissions that the accessing user possesses with respect to a context |
|
976 |
object. |
a39aca
|
977 |
|
a5ffd6
|
978 |
#) I want to also expose my resources via a REST API using Twisted Web. If |
0d43ed
|
979 |
Pyramid performed authorization based on attribute access via Zope3's |
a5ffd6
|
980 |
security proxies, I could enforce my authorization policy in both |
fd5ae9
|
981 |
:app:`Pyramid` and in the Twisted-based system the same way. |
a39aca
|
982 |
|
CM |
983 |
Defense |
672bbe
|
984 |
+++++++ |
a39aca
|
985 |
|
8cdb1b
|
986 |
:app:`Pyramid` was developed by folks familiar with Zope 2, which has a |
CM |
987 |
"through the web" security model. This "TTW" security model was the |
|
988 |
precursor to Zope 3's security proxies. Over time, as the :app:`Pyramid` |
|
989 |
developers (working in Zope 2) created such sites, we found authorization |
|
990 |
checks during code interpretation extremely useful in a minority of projects. |
|
991 |
But much of the time, TTW authorization checks usually slowed down the |
|
992 |
development velocity of projects that had no delegation requirements. In |
|
993 |
particular, if we weren't allowing "untrusted" users to write arbitrary |
|
994 |
Python code to be executed by our application, the burden of "through the |
|
995 |
web" security checks proved too costly to justify. We (collectively) haven't |
|
996 |
written an application on top of which untrusted developers are allowed to |
|
997 |
write code in many years, so it seemed to make sense to drop this model by |
|
998 |
default in a new web framework. |
a39aca
|
999 |
|
8cdb1b
|
1000 |
And since we tend to use the same toolkit for all web applications, it's just |
CM |
1001 |
never been a concern to be able to use the same set of restricted-execution |
|
1002 |
code under two web different frameworks. |
a39aca
|
1003 |
|
8cdb1b
|
1004 |
Justifications for disabling security proxies by default notwithstanding, |
CM |
1005 |
given that Zope 3 security proxies are "viral" by nature, the only |
|
1006 |
requirement to use one is to make sure you wrap a single object in a security |
|
1007 |
proxy and make sure to access that object normally when you want proxy |
|
1008 |
security checks to happen. It is possible to override the :app:`Pyramid` |
|
1009 |
"traverser" for a given application (see :ref:`changing_the_traverser`). To |
|
1010 |
get Zope3-like behavior, it is possible to plug in a different traverser |
|
1011 |
which returns Zope3-security-proxy-wrapped objects for each traversed object |
|
1012 |
(including the :term:`context` and the :term:`root`). This would have the |
|
1013 |
effect of creating a more Zope3-like environment without much effort. |
a39aca
|
1014 |
|
8cbbd9
|
1015 |
.. _http_exception_hierarchy: |
CM |
1016 |
|
|
1017 |
Pyramid Uses its Own HTTP Exception Class Hierarchy Rather Than ``webob.exc`` |
|
1018 |
----------------------------------------------------------------------------- |
1e5e31
|
1019 |
|
CM |
1020 |
.. note:: This defense is new as of Pyramid 1.1. |
|
1021 |
|
|
1022 |
The HTTP exception classes defined in :mod:`pyramid.httpexceptions` are very |
|
1023 |
much like the ones defined in ``webob.exc`` |
|
1024 |
(e.g. :class:`~pyramid.httpexceptions.HTTPNotFound`, |
|
1025 |
:class:`~pyramid.httpexceptions.HTTPForbidden`, etc). They have the same |
|
1026 |
names and largely the same behavior and all have a very similar |
|
1027 |
implementation, but not the same identity. Here's why they have a separate |
|
1028 |
identity: |
|
1029 |
|
|
1030 |
- Making them separate allows the HTTP exception classes to subclass |
|
1031 |
:class:`pyramid.response.Response`. This speeds up response generation |
|
1032 |
slightly due to the way the Pyramid router works. The same speedup could |
|
1033 |
be gained by monkeypatching ``webob.response.Response`` but it's usually |
|
1034 |
the case that monkeypatching turns out to be evil and wrong. |
|
1035 |
|
|
1036 |
- Making them separate allows them to provide alternate ``__call__`` logic |
|
1037 |
which also speeds up response generation. |
|
1038 |
|
|
1039 |
- Making them separate allows the exception classes to provide for the proper |
|
1040 |
value of ``RequestClass`` (:class:`pyramid.request.Request`). |
|
1041 |
|
|
1042 |
- Making them separate allows us freedom from having to think about backwards |
|
1043 |
compatibility code present in ``webob.exc`` having to do with Python 2.4, |
|
1044 |
which we no longer support in Pyramid 1.1+. |
|
1045 |
|
|
1046 |
- We change the behavior of two classes |
|
1047 |
(:class:`~pyramid.httpexceptions.HTTPNotFound` and |
|
1048 |
:class:`~pyramid.httpexceptions.HTTPForbidden`) in the module so that they |
|
1049 |
can be used by Pyramid internally for notfound and forbidden exceptions. |
|
1050 |
|
|
1051 |
- Making them separate allows us to influence the docstrings of the exception |
|
1052 |
classes to provide Pyramid-specific documentation. |
|
1053 |
|
|
1054 |
- Making them separate allows us to silence a stupid deprecation warning |
|
1055 |
under Python 2.6 when the response objects are used as exceptions (related |
|
1056 |
to ``self.message``). |
|
1057 |
|
8f521b
|
1058 |
.. _simpler_traversal_model: |
CM |
1059 |
|
|
1060 |
Pyramid has Simpler Traversal Machinery than Does Zope |
|
1061 |
------------------------------------------------------ |
|
1062 |
|
|
1063 |
Zope's default traverser: |
|
1064 |
|
|
1065 |
- Allows developers to mutate the traversal name stack while traversing (they |
|
1066 |
can add and remove path elements). |
|
1067 |
|
|
1068 |
- Attempts to use an adaptation to obtain the "next" element in the path from |
|
1069 |
the currently traversed object, falling back to ``__bobo_traverse__``, |
|
1070 |
``__getitem__`` and eventually ``__getattr__``. |
|
1071 |
|
|
1072 |
Zope's default traverser allows developers to mutate the traversal name stack |
|
1073 |
during traversal by mutating ``REQUEST['TraversalNameStack']``. Pyramid's |
|
1074 |
default traverser (``pyramid.traversal.ResourceTreeTraverser``) does not |
|
1075 |
offer a way to do this; it does not maintain a stack as a request attribute |
|
1076 |
and, even if it did, it does not pass the request to resource objects while |
|
1077 |
it's traversing. While it was handy at times, this feature was abused in |
|
1078 |
frameworks built atop Zope (like CMF and Plone), often making it difficult to |
|
1079 |
tell exactly what was happening when a traversal didn't match a view. I felt |
|
1080 |
it was better to make folks that wanted the feature replace the traverser |
|
1081 |
rather than build that particular honey pot in to the default traverser. |
|
1082 |
|
|
1083 |
Zope uses multiple mechanisms to attempt to obtain the next element in the |
|
1084 |
resource tree based on a name. It first tries an adaptation of the current |
|
1085 |
resource to ``ITraversable``, and if that fails, it falls back to attempting |
|
1086 |
number of magic methods on the resource (``__bobo_traverse__``, |
|
1087 |
``__getitem__``, and ``__getattr__``). My experience while both using Zope |
|
1088 |
and attempting to reimplement its publisher in ``repoze.zope2`` led me to |
|
1089 |
believe the following: |
|
1090 |
|
|
1091 |
- The *default* traverser should be as simple as possible. Zope's publisher |
|
1092 |
is somewhat difficult to follow and replicate due to the fallbacks it tried |
|
1093 |
when one traversal method failed. It is also slow. |
|
1094 |
|
|
1095 |
- The *entire traverser* should be replaceable, not just elements of the |
|
1096 |
traversal machinery. Pyramid has a few "big" components rather than a |
|
1097 |
plethora of small ones. If the entire traverser is replaceable, it's an |
|
1098 |
antipattern to make portions of the default traverser replaceable. Doing |
|
1099 |
so is a "knobs on knobs" pattern, which is unfortunately somewhat endemic |
|
1100 |
in Zope. In a "knobs on knobs" pattern, a replaceable subcomponent of a |
|
1101 |
larger component is made configurable using the same configuration |
|
1102 |
mechanism that can be used to replace the larger component. For example, |
|
1103 |
in Zope, you can replace the default traverser by registering an adapter. |
|
1104 |
But you can also (or alternately) control how the default traverser |
|
1105 |
traverses by registering one or more adapters. As a result of being able |
|
1106 |
to either replace the larger component entirely or turn knobs on the |
|
1107 |
default implementation of the larger component, no one understands when (or |
|
1108 |
whether) they should ever override the larger component entrirely. This |
|
1109 |
results, over time, in a "rusting together" of the larger "replaceable" |
|
1110 |
component and the framework itself, because people come to depend on the |
|
1111 |
availability of the default component in order just to turn its knobs. The |
|
1112 |
default component effectively becomes part of the framework, which entirely |
|
1113 |
subverts the goal of making it replaceable. In Pyramid, typically if a |
|
1114 |
component is replaceable, it will itself have no knobs (it will be "solid |
|
1115 |
state"). If you want to influence behavior controlled by that component, |
|
1116 |
you will replace the component instead of turning knobs attached to the |
|
1117 |
component. |
|
1118 |
|
7b511a
|
1119 |
.. _microframeworks_smaller_hello_world: |
CM |
1120 |
|
|
1121 |
Microframeworks Have Smaller Hello World Programs |
|
1122 |
------------------------------------------------- |
|
1123 |
|
8cdb1b
|
1124 |
Self-described "microframeworks" exist: `Bottle <http://bottle.paws.de>`_ and |
CM |
1125 |
`Flask <http://flask.pocoo.org/>`_ are two that are becoming popular. `Bobo |
|
1126 |
<http://bobo.digicool.com/>`_ doesn't describe itself as a microframework, |
|
1127 |
but its intended userbase is much the same. Many others exist. We've |
|
1128 |
actually even (only as a teaching tool, not as any sort of official project) |
d3255c
|
1129 |
`created one using Pyramid <http://bfg.repoze.org/videos#groundhog1>`_ (the |
CM |
1130 |
videos use BFG, a precursor to Pyramid, but the resulting code is `available |
|
1131 |
for Pyramid too <http://github.com/Pylons/groundhog>`_). Microframeworks are |
|
1132 |
small frameworks with one common feature: each allows its users to create a |
|
1133 |
fully functional application that lives in a single Python file. |
7b511a
|
1134 |
|
8cdb1b
|
1135 |
Some developers and microframework authors point out that Pyramid's "hello |
CM |
1136 |
world" single-file program is longer (by about five lines) than the |
965fe1
|
1137 |
equivalent program in their favorite microframework. Guilty as charged. |
26a0fd
|
1138 |
|
965fe1
|
1139 |
This loss isn't for lack of trying. Pyramid is useful in the same |
8cdb1b
|
1140 |
circumstance in which microframeworks claim dominance: single-file |
CM |
1141 |
applications. But Pyramid doesn't sacrifice its ability to credibly support |
|
1142 |
larger applications in order to achieve hello-world LoC parity with the |
|
1143 |
current crop of microframeworks. Pyramid's design instead tries to avoid |
|
1144 |
some common pitfalls associated with naive declarative configuration schemes. |
|
1145 |
The subsections which follow explain the rationale. |
7b511a
|
1146 |
|
CM |
1147 |
.. _you_dont_own_modulescope: |
|
1148 |
|
|
1149 |
Application Programmers Don't Control The Module-Scope Codepath (Import-Time Side-Effects Are Evil) |
|
1150 |
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
|
1151 |
|
8cdb1b
|
1152 |
Please imagine a directory structure with a set of Python files in it: |
7b511a
|
1153 |
|
CM |
1154 |
.. code-block:: text |
|
1155 |
|
|
1156 |
. |
|
1157 |
|-- app.py |
|
1158 |
|-- app2.py |
|
1159 |
`-- config.py |
|
1160 |
|
|
1161 |
The contents of ``app.py``: |
|
1162 |
|
|
1163 |
.. code-block:: python |
16cd50
|
1164 |
:linenos: |
7b511a
|
1165 |
|
CM |
1166 |
from config import decorator |
|
1167 |
from config import L |
|
1168 |
import pprint |
|
1169 |
|
|
1170 |
@decorator |
|
1171 |
def foo(): |
|
1172 |
pass |
|
1173 |
|
|
1174 |
if __name__ == '__main__': |
|
1175 |
import app2 |
|
1176 |
pprint.pprint(L) |
|
1177 |
|
|
1178 |
The contents of ``app2.py``: |
|
1179 |
|
|
1180 |
.. code-block:: python |
16cd50
|
1181 |
:linenos: |
7b511a
|
1182 |
|
CM |
1183 |
import app |
|
1184 |
|
|
1185 |
@app.decorator |
|
1186 |
def bar(): |
|
1187 |
pass |
|
1188 |
|
|
1189 |
The contents of ``config.py``: |
|
1190 |
|
|
1191 |
.. code-block:: python |
16cd50
|
1192 |
:linenos: |
7b511a
|
1193 |
|
CM |
1194 |
L = [] |
|
1195 |
|
|
1196 |
def decorator(func): |
|
1197 |
L.append(func) |
|
1198 |
return func |
|
1199 |
|
8cdb1b
|
1200 |
If we cd to the directory that holds these files and we run ``python app.py`` |
CM |
1201 |
given the directory structure and code above, what happens? Presumably, our |
|
1202 |
``decorator`` decorator will be used twice, once by the decorated function |
|
1203 |
``foo`` in ``app.py`` and once by the decorated function ``bar`` in |
|
1204 |
``app2.py``. Since each time the decorator is used, the list ``L`` in |
|
1205 |
``config.py`` is appended to, we'd expect a list with two elements to be |
|
1206 |
printed, right? Sadly, no: |
7b511a
|
1207 |
|
16cd50
|
1208 |
.. code-block:: text |
7b511a
|
1209 |
|
CM |
1210 |
[chrism@thinko]$ python app.py |
|
1211 |
[<function foo at 0x7f4ea41ab1b8>, |
|
1212 |
<function foo at 0x7f4ea41ab230>, |
|
1213 |
<function bar at 0x7f4ea41ab2a8>] |
|
1214 |
|
8cdb1b
|
1215 |
By visual inspection, that outcome (three different functions in the list) |
CM |
1216 |
seems impossible. We only defined two functions and we decorated each of |
|
1217 |
those functions only once, so we believe that the ``decorator`` decorator |
|
1218 |
will only run twice. However, what we believe is wrong because the code at |
|
1219 |
module scope in our ``app.py`` module was *executed twice*. The code is |
|
1220 |
executed once when the script is run as ``__main__`` (via ``python app.py``), |
|
1221 |
and then it is executed again when ``app2.py`` imports the same file as |
|
1222 |
``app``. |
7b511a
|
1223 |
|
8cdb1b
|
1224 |
What does this have to do with our comparison to microframeworks? Many |
CM |
1225 |
microframeworks in the current crop (e.g. Bottle, Flask) encourage you to |
|
1226 |
attach configuration decorators to objects defined at module scope. These |
|
1227 |
decorators execute arbitrarily complex registration code which populates a |
|
1228 |
singleton registry that is a global defined in external Python module. This |
|
1229 |
is analogous to the above example: the "global registry" in the above example |
|
1230 |
is the list ``L``. |
7b511a
|
1231 |
|
8cdb1b
|
1232 |
Let's see what happens when we use the same pattern with the `Groundhog |
965fe1
|
1233 |
<https://github.com/Pylons/groundhog>`_ microframework. Replace the contents |
CM |
1234 |
of ``app.py`` above with this: |
7b511a
|
1235 |
|
CM |
1236 |
.. code-block:: python |
16cd50
|
1237 |
:linenos: |
7b511a
|
1238 |
|
CM |
1239 |
from config import gh |
|
1240 |
|
|
1241 |
@gh.route('/foo/') |
|
1242 |
def foo(): |
|
1243 |
return 'foo' |
|
1244 |
|
|
1245 |
if __name__ == '__main__': |
|
1246 |
import app2 |
|
1247 |
pprint.pprint(L) |
|
1248 |
|
|
1249 |
Replace the contents of ``app2.py`` above with this: |
|
1250 |
|
|
1251 |
.. code-block:: python |
16cd50
|
1252 |
:linenos: |
7b511a
|
1253 |
|
CM |
1254 |
import app |
|
1255 |
|
|
1256 |
@app.gh.route('/bar/') |
|
1257 |
def bar(): |
|
1258 |
'return bar' |
|
1259 |
|
|
1260 |
Replace the contents of ``config.py`` above with this: |
|
1261 |
|
|
1262 |
.. code-block:: python |
16cd50
|
1263 |
:linenos: |
7b511a
|
1264 |
|
CM |
1265 |
from groundhog import Groundhog |
|
1266 |
gh = Groundhog('myapp', 'seekrit') |
|
1267 |
|
8cdb1b
|
1268 |
How many routes will be registered within the routing table of the "gh" |
CM |
1269 |
Groundhog application? If you answered three, you are correct. How many |
|
1270 |
would a casual reader (and any sane developer) expect to be registered? If |
|
1271 |
you answered two, you are correct. Will the double registration be a |
965fe1
|
1272 |
problem? With our Groundhog framework's ``route`` method backing this |
CM |
1273 |
application, not really. It will slow the application down a little bit, |
|
1274 |
because it will need to miss twice for a route when it does not match. Will |
|
1275 |
it be a problem with another framework, another application, or another |
8cdb1b
|
1276 |
decorator? Who knows. You need to understand the application in its |
CM |
1277 |
totality, the framework in its totality, and the chronology of execution to |
|
1278 |
be able to predict what the impact of unintentional code double-execution |
|
1279 |
will be. |
7b511a
|
1280 |
|
8cdb1b
|
1281 |
The encouragement to use decorators which perform population of an external |
CM |
1282 |
registry has an unintended consequence: the application developer now must |
|
1283 |
assert ownership of every codepath that executes Python module scope |
|
1284 |
code. Module-scope code is presumed by the current crop of decorator-based |
|
1285 |
microframeworks to execute once and only once; if it executes more than once, |
|
1286 |
weird things will start to happen. It is up to the application developer to |
|
1287 |
maintain this invariant. Unfortunately, however, in reality, this is an |
|
1288 |
impossible task, because, Python programmers *do not own the module scope |
|
1289 |
codepath, and never will*. Anyone who tries to sell you on the idea that |
|
1290 |
they do is simply mistaken. Test runners that you may want to use to run |
|
1291 |
your code's tests often perform imports of arbitrary code in strange orders |
|
1292 |
that manifest bugs like the one demonstrated above. API documentation |
965fe1
|
1293 |
generation tools do the same. Some people even think it's safe to use the |
CM |
1294 |
Python ``reload`` command or delete objects from ``sys.modules``, each of |
|
1295 |
which has hilarious effects when used against code that has import-time side |
|
1296 |
effects. |
7b511a
|
1297 |
|
8cdb1b
|
1298 |
Global-registry-mutating microframework programmers therefore will at some |
CM |
1299 |
point need to start reading the tea leaves about what *might* happen if |
|
1300 |
module scope code gets executed more than once like we do in the previous |
|
1301 |
paragraph. When Python programmers assume they can use the module-scope |
|
1302 |
codepath to run arbitrary code (especially code which populates an external |
|
1303 |
registry), and this assumption is challenged by reality, the application |
|
1304 |
developer is often required to undergo a painful, meticulous debugging |
|
1305 |
process to find the root cause of an inevitably obscure symptom. The |
|
1306 |
solution is often to rearrange application import ordering or move an import |
|
1307 |
statement from module-scope into a function body. The rationale for doing so |
|
1308 |
can never be expressed adequately in the checkin message which accompanies |
|
1309 |
the fix and can't be documented succinctly enough for the benefit of the rest |
|
1310 |
of the development team so that the problem never happens again. It will |
|
1311 |
happen again, especially if you are working on a project with other people |
|
1312 |
who haven't yet internalized the lessons you learned while you stepped |
|
1313 |
through module-scope code using ``pdb``. This is a really pretty poor |
|
1314 |
situation to find yourself in as an application developer: you probably |
|
1315 |
didn't even know your or your team signed up for the job, because the |
|
1316 |
documentation offered by decorator-based microframeworks don't warn you about |
|
1317 |
it. |
7b511a
|
1318 |
|
8cdb1b
|
1319 |
Folks who have a large investment in eager decorator-based configuration that |
CM |
1320 |
populates an external data structure (such as microframework authors) may |
|
1321 |
argue that the set of circumstances I outlined above is anomalous and |
|
1322 |
contrived. They will argue that it just will never happen. If you never |
|
1323 |
intend your application to grow beyond one or two or three modules, that's |
|
1324 |
probably true. However, as your codebase grows, and becomes spread across a |
|
1325 |
greater number of modules, the circumstances in which module-scope code will |
|
1326 |
be executed multiple times will become more and more likely to occur and less |
|
1327 |
and less predictable. It's not responsible to claim that double-execution of |
|
1328 |
module-scope code will never happen. It will; it's just a matter of luck, |
|
1329 |
time, and application complexity. |
7b511a
|
1330 |
|
8cdb1b
|
1331 |
If microframework authors do admit that the circumstance isn't contrived, |
CM |
1332 |
they might then argue that "real" damage will never happen as the result of |
|
1333 |
the double-execution (or triple-execution, etc) of module scope code. You |
|
1334 |
would be wise to disbelieve this assertion. The potential outcomes of |
|
1335 |
multiple execution are too numerous to predict because they involve delicate |
|
1336 |
relationships between application and framework code as well as chronology of |
|
1337 |
code execution. It's literally impossible for a framework author to know |
|
1338 |
what will happen in all circumstances. But even if given the gift of |
|
1339 |
omniscience for some limited set of circumstances, the framework author |
|
1340 |
almost certainly does not have the double-execution anomaly in mind when |
|
1341 |
coding new features. He's thinking of adding a feature, not protecting |
|
1342 |
against problems that might be caused by the 1% multiple execution case. |
|
1343 |
However, any 1% case may cause 50% of your pain on a project, so it'd be nice |
|
1344 |
if it never occured. |
7b511a
|
1345 |
|
CM |
1346 |
Responsible microframeworks actually offer a back-door way around the |
8cdb1b
|
1347 |
problem. They allow you to disuse decorator based configuration entirely. |
CM |
1348 |
Instead of requiring you to do the following: |
7b511a
|
1349 |
|
CM |
1350 |
.. code-block:: python |
16cd50
|
1351 |
:linenos: |
7b511a
|
1352 |
|
CM |
1353 |
gh = Groundhog('myapp', 'seekrit') |
|
1354 |
|
|
1355 |
@gh.route('/foo/') |
|
1356 |
def foo(): |
|
1357 |
return 'foo' |
|
1358 |
|
|
1359 |
if __name__ == '__main__': |
|
1360 |
gh.run() |
|
1361 |
|
8cdb1b
|
1362 |
They allow you to disuse the decorator syntax and go almost-all-imperative: |
7b511a
|
1363 |
|
CM |
1364 |
.. code-block:: python |
16cd50
|
1365 |
:linenos: |
7b511a
|
1366 |
|
CM |
1367 |
def foo(): |
|
1368 |
return 'foo' |
|
1369 |
|
|
1370 |
gh = Groundhog('myapp', 'seekrit') |
|
1371 |
|
|
1372 |
if __name__ == '__main__': |
|
1373 |
gh.add_route(foo, '/foo/') |
|
1374 |
gh.run() |
|
1375 |
|
edd915
|
1376 |
This is a generic mode of operation that is encouraged in the Pyramid |
8cdb1b
|
1377 |
documentation. Some existing microframeworks (Flask, in particular) allow for |
CM |
1378 |
it as well. None (other than Pyramid) *encourage* it. If you never expect |
|
1379 |
your application to grow beyond two or three or four or ten modules, it |
|
1380 |
probably doesn't matter very much which mode you use. If your application |
|
1381 |
grows large, however, imperative configuration can provide better |
|
1382 |
predictability. |
7b511a
|
1383 |
|
CM |
1384 |
.. note:: |
|
1385 |
|
8cdb1b
|
1386 |
Astute readers may notice that Pyramid has configuration decorators too. |
CM |
1387 |
Aha! Don't these decorators have the same problems? No. These decorators |
|
1388 |
do not populate an external Python module when they are executed. They |
|
1389 |
only mutate the functions (and classes and methods) they're attached to. |
|
1390 |
These mutations must later be found during a "scan" process that has a |
|
1391 |
predictable and structured import phase. Module-localized mutation is |
|
1392 |
actually the best-case circumstance for double-imports; if a module only |
|
1393 |
mutates itself and its contents at import time, if it is imported twice, |
|
1394 |
that's OK, because each decorator invocation will always be mutating an |
|
1395 |
independent copy of the object its attached to, not a shared resource like |
|
1396 |
a registry in another module. This has the effect that |
|
1397 |
double-registrations will never be performed. |
7b511a
|
1398 |
|
0a585a
|
1399 |
Routes Need Relative Ordering |
CM |
1400 |
+++++++++++++++++++++++++++++ |
7b511a
|
1401 |
|
CM |
1402 |
Consider the following simple `Groundhog |
e9bf10
|
1403 |
<https://github.com/Pylons/groundhog>`_ application: |
7b511a
|
1404 |
|
CM |
1405 |
.. code-block:: python |
16cd50
|
1406 |
:linenos: |
7b511a
|
1407 |
|
CM |
1408 |
from groundhog import Groundhog |
|
1409 |
app = Groundhog('myapp', 'seekrit') |
|
1410 |
|
|
1411 |
app.route('/admin') |
|
1412 |
def admin(): |
|
1413 |
return '<html>admin page</html>' |
|
1414 |
|
|
1415 |
app.route('/:action') |
|
1416 |
def action(): |
|
1417 |
if action == 'add': |
7be3b1
|
1418 |
return '<html>add</html>' |
7b511a
|
1419 |
if action == 'delete': |
7be3b1
|
1420 |
return '<html>delete</html>' |
7b511a
|
1421 |
return app.abort(404) |
CM |
1422 |
|
|
1423 |
if __name__ == '__main__': |
|
1424 |
app.run() |
|
1425 |
|
|
1426 |
If you run this application and visit the URL ``/admin``, you will see |
8cdb1b
|
1427 |
"admin" page. This is the intended result. However, what if you rearrange |
CM |
1428 |
the order of the function definitions in the file? |
7b511a
|
1429 |
|
CM |
1430 |
.. code-block:: python |
16cd50
|
1431 |
:linenos: |
7b511a
|
1432 |
|
CM |
1433 |
from groundhog import Groundhog |
|
1434 |
app = Groundhog('myapp', 'seekrit') |
|
1435 |
|
|
1436 |
app.route('/:action') |
|
1437 |
def action(): |
|
1438 |
if action == 'add': |
7be3b1
|
1439 |
return '<html>add</html>' |
7b511a
|
1440 |
if action == 'delete': |
7be3b1
|
1441 |
return '<html>delete</html>' |
7b511a
|
1442 |
return app.abort(404) |
CM |
1443 |
|
|
1444 |
app.route('/admin') |
|
1445 |
def admin(): |
|
1446 |
return '<html>admin page</html>' |
|
1447 |
|
|
1448 |
if __name__ == '__main__': |
|
1449 |
app.run() |
|
1450 |
|
8cdb1b
|
1451 |
If you run this application and visit the URL ``/admin``, you will now be |
CM |
1452 |
returned a 404 error. This is probably not what you intended. The reason |
|
1453 |
you see a 404 error when you rearrange function definition ordering is that |
|
1454 |
routing declarations expressed via our microframework's routing decorators |
|
1455 |
have an *ordering*, and that ordering matters. |
7b511a
|
1456 |
|
8cdb1b
|
1457 |
In the first case, where we achieved the expected result, we first added a |
CM |
1458 |
route with the pattern ``/admin``, then we added a route with the pattern |
|
1459 |
``/:action`` by virtue of adding routing patterns via decorators at module |
|
1460 |
scope. When a request with a ``PATH_INFO`` of ``/admin`` enters our |
|
1461 |
application, the web framework loops over each of our application's route |
|
1462 |
patterns in the order in which they were defined in our module. As a result, |
|
1463 |
the view associated with the ``/admin`` routing pattern will be invoked: it |
|
1464 |
matches first. All is right with the world. |
7b511a
|
1465 |
|
8cdb1b
|
1466 |
In the second case, where we did not achieve the expected result, we first |
CM |
1467 |
added a route with the pattern ``/:action``, then we added a route with the |
|
1468 |
pattern ``/admin``. When a request with a ``PATH_INFO`` of ``/admin`` enters |
|
1469 |
our application, the web framework loops over each of our application's route |
|
1470 |
patterns in the order in which they were defined in our module. As a result, |
|
1471 |
the view associated with the ``/:action`` routing pattern will be invoked: it |
|
1472 |
matches first. A 404 error is raised. This is not what we wanted; it just |
|
1473 |
happened due to the order in which we defined our view functions. |
7b511a
|
1474 |
|
0a585a
|
1475 |
This is because "Groundhog" routes are added to the routing map in import |
CM |
1476 |
order, and matched in the same order when a request comes in. Bottle, like |
|
1477 |
Groundhog, as of this writing, matches routes in the order in which they're |
|
1478 |
defined at Python execution time. Flask, on the other hand, does not order |
|
1479 |
route matching based on import order; it reorders the routes you add to your |
|
1480 |
application based on their "complexity". Other microframeworks have varying |
|
1481 |
strategies to do route ordering. |
7b511a
|
1482 |
|
0a585a
|
1483 |
Your application may be small enough where route ordering will never cause an |
CM |
1484 |
issue. If your application becomes large enough, however, being able to |
|
1485 |
specify or predict that ordering as your application grows larger will be |
|
1486 |
difficult. At some point, you will likely need to more explicitly start |
|
1487 |
controlling route ordering, especially in applications that require |
|
1488 |
extensibility. |
|
1489 |
|
|
1490 |
If your microframework orders route matching based on "complexity", you'll |
|
1491 |
need to understand what that "complexity" ordering is and attempt to inject a |
|
1492 |
"less complex" route to have it get matched before any "more complex" one to |
|
1493 |
ensure that it's tried first. |
|
1494 |
|
|
1495 |
If your microframework orders its route matching based on relative |
|
1496 |
import/execution of function decorator definitions, you will need to ensure |
|
1497 |
you execute all of these statements in the "right" order, and you'll need to |
|
1498 |
be cognizant of this import/execution ordering as you grow your application |
|
1499 |
or try to extend it. This is a difficult invariant to maintain for all but |
|
1500 |
the smallest applications. |
|
1501 |
|
|
1502 |
In either case, your application must import the non-``__main__`` modules |
|
1503 |
which contain configuration decorations somehow for their configuration to be |
|
1504 |
executed. Does that make you a little uncomfortable? It should, because |
8cdb1b
|
1505 |
:ref:`you_dont_own_modulescope`. |
7b511a
|
1506 |
|
0428ed
|
1507 |
Pyramid uses neither decorator import time ordering nor does it attempt to |
CM |
1508 |
divine the relative "complexity" of one route to another in order to define a |
|
1509 |
route match ordering. In Pyramid, you have to maintain relative route |
|
1510 |
ordering imperatively via the chronology of multiple executions of the |
|
1511 |
:meth:`pyramid.config.Configurator.add_route` method. The order in which you |
|
1512 |
repeatedly call ``add_route`` becomes the order of route matching. |
|
1513 |
|
|
1514 |
If needing to maintain this imperative ordering truly bugs you, you can use |
|
1515 |
:term:`traversal` instead of route matching, which is a completely |
|
1516 |
declarative (and completely predictable) mechanism to map code to URLs. |
|
1517 |
While URL dispatch is easier to understand for small non-extensible |
|
1518 |
applications, traversal is a great fit for very large applications and |
|
1519 |
applications that need to be arbitrarily extensible. |
1d1975
|
1520 |
|
7b511a
|
1521 |
"Stacked Object Proxies" Are Too Clever / Thread Locals Are A Nuisance |
CM |
1522 |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
|
1523 |
|
8cdb1b
|
1524 |
In another manifestation of "import fascination", some microframeworks use |
CM |
1525 |
the ``import`` statement to get a handle to an object which *is not logically |
|
1526 |
global*: |
7b511a
|
1527 |
|
CM |
1528 |
.. code-block:: python |
16cd50
|
1529 |
:linenos: |
7b511a
|
1530 |
|
CM |
1531 |
from flask import request |
|
1532 |
|
|
1533 |
@app.route('/login', methods=['POST', 'GET']) |
|
1534 |
def login(): |
|
1535 |
error = None |
|
1536 |
if request.method == 'POST': |
|
1537 |
if valid_login(request.form['username'], |
|
1538 |
request.form['password']): |
|
1539 |
return log_the_user_in(request.form['username']) |
|
1540 |
else: |
|
1541 |
error = 'Invalid username/password' |
|
1542 |
# this is executed if the request method was GET or the |
|
1543 |
# credentials were invalid |
|
1544 |
|
e7b5fe
|
1545 |
The `Pylons 1.X <http://pylonsproject.org>`_ web framework uses a similar |
8cdb1b
|
1546 |
strategy. It calls these things "Stacked Object Proxies", so, for purposes |
CM |
1547 |
of this discussion, I'll do so as well. |
7b511a
|
1548 |
|
8cdb1b
|
1549 |
Import statements in Python (``import foo``, ``from bar import baz``) are |
CM |
1550 |
most frequently performed to obtain a reference to an object defined globally |
|
1551 |
within an external Python module. However, in "normal" programs, they are |
|
1552 |
never used to obtain a reference to an object that has a lifetime measured by |
|
1553 |
the scope of the body of a function. It would be absurd to try to import, |
|
1554 |
for example, a variable named ``i`` representing a loop counter defined in |
|
1555 |
the body of a function. For example, we'd never try to import ``i`` from the |
7b511a
|
1556 |
code below: |
CM |
1557 |
|
|
1558 |
.. code-block:: python |
16cd50
|
1559 |
:linenos: |
7b511a
|
1560 |
|
CM |
1561 |
def afunc(): |
|
1562 |
for i in range(10): |
|
1563 |
print i |
|
1564 |
|
8cdb1b
|
1565 |
By its nature, the *request* object created as the result of a WSGI server's |
CM |
1566 |
call into a long-lived web framework cannot be global, because the lifetime |
|
1567 |
of a single request will be much shorter than the lifetime of the process |
|
1568 |
running the framework. A request object created by a web framework actually |
|
1569 |
has more similarity to the ``i`` loop counter in our example above than it |
|
1570 |
has to any comparable importable object defined in the Python standard |
|
1571 |
library or in "normal" library code. |
7b511a
|
1572 |
|
8cdb1b
|
1573 |
However, systems which use stacked object proxies promote locally scoped |
CM |
1574 |
objects such as ``request`` out to module scope, for the purpose of being |
|
1575 |
able to offer users a "nice" spelling involving ``import``. They, for what I |
|
1576 |
consider dubious reasons, would rather present to their users the canonical |
|
1577 |
way of getting at a ``request`` as ``from framework import request`` instead |
|
1578 |
of a saner ``from myframework.threadlocals import get_request; request = |
|
1579 |
get_request()`` even though the latter is more explicit. |
7b511a
|
1580 |
|
8cdb1b
|
1581 |
It would be *most* explicit if the microframeworks did not use thread local |
CM |
1582 |
variables at all. Pyramid view functions are passed a request object; many |
|
1583 |
of Pyramid's APIs require that an explicit request object be passed to them. |
|
1584 |
It is *possible* to retrieve the current Pyramid request as a threadlocal |
|
1585 |
variable but it is a "in case of emergency, break glass" type of activity. |
|
1586 |
This explicitness makes Pyramid view functions more easily unit testable, as |
|
1587 |
you don't need to rely on the framework to manufacture suitable "dummy" |
|
1588 |
request (and other similarly-scoped) objects during test setup. It also |
|
1589 |
makes them more likely to work on arbitrary systems, such as async servers |
|
1590 |
that do no monkeypatching. |
7b511a
|
1591 |
|
CM |
1592 |
Explicitly WSGI |
|
1593 |
+++++++++++++++ |
|
1594 |
|
8cdb1b
|
1595 |
Some microframeworks offer a ``run()`` method of an application object that |
CM |
1596 |
executes a default server configuration for easy execution. |
7b511a
|
1597 |
|
8cdb1b
|
1598 |
Pyramid doesn't currently try to hide the fact that its router is a WSGI |
CM |
1599 |
application behind a convenience ``run()`` API. It just tells people to |
|
1600 |
import a WSGI server and use it to serve up their Pyramid application as per |
|
1601 |
the documentation of that WSGI server. |
7b511a
|
1602 |
|
8cdb1b
|
1603 |
The extra lines saved by abstracting away the serving step behind ``run()`` |
CM |
1604 |
seem to have driven dubious second-order decisions related to API in some |
|
1605 |
microframeworks. For example, Bottle contains a ``ServerAdapter`` subclass |
|
1606 |
for each type of WSGI server it supports via its ``app.run()`` mechanism. |
|
1607 |
This means that there exists code in ``bottle.py`` that depends on the |
|
1608 |
following modules: ``wsgiref``, ``flup``, ``paste``, ``cherrypy``, ``fapws``, |
|
1609 |
``tornado``, ``google.appengine``, ``twisted.web``, ``diesel``, ``gevent``, |
|
1610 |
``gunicorn``, ``eventlet``, and ``rocket``. You choose the kind of server |
|
1611 |
you want to run by passing its name into the ``run`` method. In theory, this |
|
1612 |
sounds great: I can try Bottle out on ``gunicorn`` just by passing in a name! |
|
1613 |
However, to fully test Bottle, all of these third-party systems must be |
|
1614 |
installed and functional; the Bottle developers must monitor changes to each |
|
1615 |
of these packages and make sure their code still interfaces properly with |
|
1616 |
them. This expands the packages required for testing greatly; this is a |
|
1617 |
*lot* of requirements. It is likely difficult to fully automate these tests |
7b511a
|
1618 |
due to requirements conflicts and build issues. |
CM |
1619 |
|
8cdb1b
|
1620 |
As a result, for single-file apps, we currently don't bother to offer a |
CM |
1621 |
``run()`` shortcut; we tell folks to import their WSGI server of choice and |
|
1622 |
run it "by hand". For the people who want a server abstraction layer, we |
|
1623 |
suggest that they use PasteDeploy. In PasteDeploy-based systems, the onus |
|
1624 |
for making sure that the server can interface with a WSGI application is |
|
1625 |
placed on the server developer, not the web framework developer, making it |
|
1626 |
more likely to be timely and correct. |
7b511a
|
1627 |
|
CM |
1628 |
Wrapping Up |
|
1629 |
+++++++++++ |
|
1630 |
|
8cdb1b
|
1631 |
Here's a diagrammed version of the simplest pyramid application, where |
CM |
1632 |
comments take into account what we've discussed in the |
7b511a
|
1633 |
:ref:`microframeworks_smaller_hello_world` section. |
CM |
1634 |
|
|
1635 |
.. code-block:: python |
16cd50
|
1636 |
:linenos: |
7b511a
|
1637 |
|
d868ff
|
1638 |
from pyramid.response import Response # explicit response objects, no TL |
7b511a
|
1639 |
from paste.httpserver import serve # explicitly WSGI |
CM |
1640 |
|
|
1641 |
def hello_world(request): # accepts a request; no request thread local reqd |
|
1642 |
# explicit response object means no response threadlocal |
|
1643 |
return Response('Hello world!') |
|
1644 |
|
|
1645 |
if __name__ == '__main__': |
d7f259
|
1646 |
from pyramid.config import Configurator |
7b511a
|
1647 |
config = Configurator() # no global application object. |
CM |
1648 |
config.add_view(hello_world) # explicit non-decorator registration |
|
1649 |
app = config.make_wsgi_app() # explicitly WSGI |
|
1650 |
serve(app, host='0.0.0.0') # explicitly WSGI |
|
1651 |
|
82e3e3
|
1652 |
Pyramid Doesn't Offer Pluggable Apps |
CM |
1653 |
------------------------------------ |
|
1654 |
|
|
1655 |
It is "Pyramidic" to compose multiple external sources into the same |
98b28d
|
1656 |
configuration using :meth:`~pyramid.config.Configuration.include`. Any |
CM |
1657 |
number of includes can be done to compose an application; includes can even |
|
1658 |
be done from within other includes. Any directive can be used within an |
|
1659 |
include that can be used outside of one (such as |
|
1660 |
:meth:`~pyramid.config.Configurator.add_view`, etc). |
82e3e3
|
1661 |
|
CM |
1662 |
Pyramid has a conflict detection system that will throw an error if two |
|
1663 |
included externals try to add "the same" configuration in a conflicting |
|
1664 |
way (such as both externals trying to add a route using the same name, |
|
1665 |
or both externals trying to add a view with the same set of predicates). |
|
1666 |
It's awful tempting to call this set of features something that can be |
|
1667 |
used to compose a system out of "pluggable applications". But in |
|
1668 |
reality, there are a number of problems with claiming this: |
|
1669 |
|
|
1670 |
- The terminology is strained. Pyramid really has no notion of a |
|
1671 |
plurality of "applications", just a way to compose configuration |
|
1672 |
from multiple sources to create a single WSGI application. That |
|
1673 |
WSGI application may gain behavior by including or disincluding |
|
1674 |
configuration, but once it's all composed together, Pyramid |
|
1675 |
doesn't really provide any machinery which can be used to demarcate |
|
1676 |
the boundaries of one "application" (in the sense of configuration |
|
1677 |
from an external that adds routes, views, etc) from another. |
|
1678 |
|
|
1679 |
- Pyramid doesn't provide enough "rails" to make it possible to |
|
1680 |
integrate truly honest-to-god, download-an-app-from-a-random-place |
|
1681 |
and-plug-it-in-to-create-a-system "pluggable" applications. |
|
1682 |
Because Pyramid itself isn't opinionated (it doesn't mandate a |
|
1683 |
particular kind of database, it offers multiple ways to map URLs |
|
1684 |
to code, etc), it's unlikely that someone who creates something |
|
1685 |
"application-like" will be able to casually redistribute it |
|
1686 |
to J. Random Pyramid User and have it "just work" by asking him |
|
1687 |
to config.include a function from the package. |
|
1688 |
This is particularly true of very high level components such |
|
1689 |
as blogs, wikis, twitter clones, commenting systems, etc. |
|
1690 |
The "integrator" (the Pyramid developer who has downloaded a |
|
1691 |
package advertised as a "pluggable app") will almost certainly |
|
1692 |
have made different choices about e.g. what type of persistence |
|
1693 |
system he's using, and for the integrator to appease the |
|
1694 |
requirements of the "pluggable application", he may be required |
|
1695 |
to set up a different database, make changes to his own code |
|
1696 |
to prevent his application from "shadowing" the pluggable |
|
1697 |
app (or vice versa), and any other number of arbitrary |
|
1698 |
changes. |
|
1699 |
|
|
1700 |
For this reason, we claim that Pyramid has "extensible" applications, |
|
1701 |
not pluggable applications. Any Pyramid application can be extended |
|
1702 |
without forking it as long as its configuration statements have been |
|
1703 |
composed into things that can be pulled in via "config.include". |
|
1704 |
|
|
1705 |
It's also perfectly reasonable for a single developer or team to create a set |
|
1706 |
of interoperating components which can be enabled or disabled by using |
|
1707 |
config.include. That developer or team will be able to provide the "rails" |
|
1708 |
(by way of making high-level choices about the technology used to create the |
|
1709 |
project, so there won't be any issues with plugging all of the components |
|
1710 |
together. The problem only rears its head when the components need to be |
|
1711 |
distributed to *arbitrary* users. Note that Django has a similar problem |
|
1712 |
with "pluggable applications" that need to work for arbitrary third parties, |
|
1713 |
even though they provide many, many more rails than does Pyramid. Even the |
|
1714 |
rails they provide are not enough to make the "pluggable application" story |
|
1715 |
really work without local modification. |
|
1716 |
|
|
1717 |
Truly pluggable applications need to be created at a much higher level than a |
|
1718 |
web framework, as no web framework can offer enough constraints to really |
|
1719 |
make them "work out of the box". They really need to plug into an |
|
1720 |
application, instead. It would be a noble goal to build an application with |
|
1721 |
Pyramid that provides these constraints and which truly does offer a way to |
|
1722 |
plug in applications (Joomla, Plone, Drupal come to mind). |
|
1723 |
|
8cdb1b
|
1724 |
Pyramid Has Zope Things In It, So It's Too Complex |
CM |
1725 |
-------------------------------------------------- |
|
1726 |
|
|
1727 |
On occasion, someone will feel compelled to post a mailing list message that |
|
1728 |
reads something like this: |
|
1729 |
|
|
1730 |
.. code-block:: text |
|
1731 |
|
|
1732 |
had a quick look at pyramid ... too complex to me and not really |
|
1733 |
understand for which benefits.. I feel should consider whether it's time |
|
1734 |
for me to step back to django .. I always hated zope (useless ?) |
|
1735 |
complexity and I love simple way of thinking |
|
1736 |
|
|
1737 |
(Paraphrased from a real email, actually.) |
|
1738 |
|
|
1739 |
Let's take this criticism point-by point. |
|
1740 |
|
|
1741 |
Too Complex |
|
1742 |
+++++++++++ |
|
1743 |
|
03fc30
|
1744 |
If you can understand this hello world program, you can use Pyramid: |
8cdb1b
|
1745 |
|
CM |
1746 |
.. code-block:: python |
|
1747 |
:linenos: |
|
1748 |
|
|
1749 |
from paste.httpserver import serve |
|
1750 |
from pyramid.config import Configurator |
|
1751 |
from pyramid.response import Response |
|
1752 |
|
|
1753 |
def hello_world(request): |
|
1754 |
return Response('Hello world!') |
|
1755 |
|
|
1756 |
if __name__ == '__main__': |
|
1757 |
config = Configurator() |
|
1758 |
config.add_view(hello_world) |
|
1759 |
app = config.make_wsgi_app() |
|
1760 |
serve(app) |
|
1761 |
|
adfcf6
|
1762 |
Pyramid has ~ 650 pages of documentation (printed), covering topics from the |
TS |
1763 |
very basic to the most advanced. *Nothing* is left undocumented, quite |
|
1764 |
literally. It also has an *awesome*, very helpful community. Visit the |
16b93e
|
1765 |
#pyramid and/or #pylons IRC channels on freenode.net and see. |
8cdb1b
|
1766 |
|
CM |
1767 |
Hate Zope |
|
1768 |
+++++++++ |
|
1769 |
|
|
1770 |
I'm sorry you feel that way. The Zope brand has certainly taken its share of |
|
1771 |
lumps over the years, and has a reputation for being insular and mysterious. |
|
1772 |
But the word "Zope" is literally quite meaningless without qualification. |
|
1773 |
What *part* of Zope do you hate? "Zope" is a brand, not a technology. |
|
1774 |
|
|
1775 |
If it's Zope2-the-web-framework, Pyramid is not that. The primary designers |
|
1776 |
and developers of Pyramid, if anyone, should know. We wrote Pyramid's |
|
1777 |
predecessor (:mod:`repoze.bfg`), in part, because *we* knew that Zope 2 had |
|
1778 |
usability issues and limitations. :mod:`repoze.bfg` (and now :app:`Pyramid`) |
|
1779 |
was written to address these issues. |
|
1780 |
|
|
1781 |
If it's Zope3-the-web-framework, Pyramid is *definitely* not that. Making |
|
1782 |
use of lots of Zope 3 technologies is territory already staked out by the |
|
1783 |
:term:`Grok` project. Save for the obvious fact that they're both web |
|
1784 |
frameworks, :mod:`Pyramid` is very, very different than Grok. Grok exposes |
|
1785 |
lots of Zope technologies to end users. On the other hand, if you need to |
|
1786 |
understand a Zope-only concept while using Pyramid, then we've failed on some |
|
1787 |
very basic axis. |
|
1788 |
|
|
1789 |
If it's just the word Zope: this can only be guilt by association. Because a |
|
1790 |
piece of software internally uses some package named ``zope.foo``, it doesn't |
|
1791 |
turn the piece of software that uses it into "Zope". There is a lot of |
|
1792 |
*great* software written that has the word Zope in its name. Zope is not |
|
1793 |
some sort of monolithic thing, and a lot of its software is usable |
|
1794 |
externally. And while it's not really the job of this document to defend it, |
|
1795 |
Zope has been around for over 10 years and has an incredibly large, active |
|
1796 |
community. If you don't believe this, |
|
1797 |
http://taichino.appspot.com/pypi_ranking/authors is an eye-opening reality |
|
1798 |
check. |
|
1799 |
|
|
1800 |
Love Simplicity |
|
1801 |
+++++++++++++++ |
|
1802 |
|
|
1803 |
Years of effort have gone into honing this package and its documentation to |
|
1804 |
make it as simple as humanly possible for developers to use. Everything is a |
|
1805 |
tradeoff, of course, and people have their own ideas about what "simple" is. |
|
1806 |
You may have a style difference if you believe Pyramid is complex. Its |
|
1807 |
developers obviously disagree. |
|
1808 |
|
fa2e24
|
1809 |
Other Challenges |
CM |
1810 |
---------------- |
fbfea7
|
1811 |
|
809744
|
1812 |
Other challenges are encouraged to be sent to the `Pylons-devel |
CM |
1813 |
<http://groups.google.com/group/pylons-devel>`_ maillist. We'll try to address |
|
1814 |
them by considering a design change, or at very least via exposition here. |