summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2016-04-11 21:34:44 -0500
committerMichael Merickel <michael@merickel.org>2016-04-11 21:34:44 -0500
commit167b7728a84202daa17004b88c57cb29970ce7d1 (patch)
tree859e02f5eab85c6ae2b6478e4c38ecb108a1df0a
parentdb3aa086a25504f8faf0149875eb7ff73ef08352 (diff)
parentfa43952e617ad68c52447da28fc7f5be23ff4b10 (diff)
downloadpyramid-167b7728a84202daa17004b88c57cb29970ce7d1.tar.gz
pyramid-167b7728a84202daa17004b88c57cb29970ce7d1.tar.bz2
pyramid-167b7728a84202daa17004b88c57cb29970ce7d1.zip
Merge branch 'master' into docs/easy-install-to-pip.2104
-rw-r--r--CHANGES.txt13
-rw-r--r--docs/_static/pyramid_request_processing.graffle60
-rw-r--r--docs/_static/pyramid_request_processing.pngbin122854 -> 123953 bytes
-rw-r--r--docs/_static/pyramid_request_processing.svg2
-rw-r--r--docs/api/config.rst1
-rw-r--r--docs/api/interfaces.rst6
-rw-r--r--docs/api/paster.rst2
-rw-r--r--docs/api/viewderivers.rst17
-rw-r--r--docs/glossary.rst7
-rw-r--r--docs/narr/extconfig.rst1
-rw-r--r--docs/narr/hooks.rst183
-rw-r--r--docs/narr/i18n.rst15
-rw-r--r--pyramid/config/__init__.py4
-rw-r--r--pyramid/config/tweens.py7
-rw-r--r--pyramid/config/util.py11
-rw-r--r--pyramid/config/views.py829
-rw-r--r--pyramid/interfaces.py33
-rw-r--r--pyramid/paster.py18
-rw-r--r--pyramid/scripts/pserve.py2
-rw-r--r--pyramid/testing.py1
-rw-r--r--pyramid/tests/test_config/test_views.py1146
-rw-r--r--pyramid/tests/test_paster.py26
-rw-r--r--pyramid/tests/test_viewderivers.py1442
-rw-r--r--pyramid/util.py3
-rw-r--r--pyramid/viewderivers.py459
25 files changed, 2551 insertions, 1737 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index a17f4aab5..2b3db8644 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -27,6 +27,19 @@ unreleased
where you may need more control over the request.
See https://github.com/Pylons/pyramid/pull/2393
+- Allow using variable substitutions like ``%(LOGGING_LOGGER_ROOT_LEVEL)s``
+ for logging sections of the .ini file and populate these variables from
+ the ``pserve`` command line -- e.g.:
+ ``pserve development.ini LOGGING_LOGGER_ROOT_LEVEL=DEBUG``
+ See https://github.com/Pylons/pyramid/pull/2399
+
+- Add a new "view deriver" concept to Pyramid to allow framework authors to
+ inject elements into the standard Pyramid view pipeline and affect all
+ views in an application. This is similar to a decorator except that it
+ has access to options passed to ``config.add_view`` and can affect other
+ stages of the pipeline such as the raw response from a view or prior to
+ security checks. See https://github.com/Pylons/pyramid/pull/2021
+
1.6 (2015-04-14)
================
diff --git a/docs/_static/pyramid_request_processing.graffle b/docs/_static/pyramid_request_processing.graffle
index 71319610b..16b360543 100644
--- a/docs/_static/pyramid_request_processing.graffle
+++ b/docs/_static/pyramid_request_processing.graffle
@@ -53,7 +53,7 @@
<key>Creator</key>
<string>Steve Piercy</string>
<key>DisplayScale</key>
- <string>1 0/72 in = 1 0/72 in</string>
+ <string>1 0/72 in = 1.0000 in</string>
<key>GraphDocumentVersion</key>
<integer>8</integer>
<key>GraphicsList</key>
@@ -84,8 +84,8 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{344.41667175292969, 402.88506673894034}</string>
- <string>{375.5, 402.27232108797347}</string>
+ <string>{344.41668319702148, 402.88506673894034}</string>
+ <string>{375.5, 402.77232108797347}</string>
</array>
<key>Style</key>
<dict>
@@ -113,7 +113,7 @@
<key>Tail</key>
<dict>
<key>ID</key>
- <integer>169428</integer>
+ <integer>169509</integer>
</dict>
</dict>
<dict>
@@ -243,11 +243,11 @@
<array>
<dict>
<key>Bounds</key>
- <string>{{238.8333613077798, 284.99999999999994}, {105.66668701171875, 18.656048080136394}}</string>
+ <string>{{238.74999618530273, 284.99999999999994}, {105.75002924601222, 18.656048080136394}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
- <integer>169425</integer>
+ <integer>169506</integer>
<key>Magnets</key>
<array>
<string>{0, 1}</string>
@@ -296,11 +296,11 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{238.75000762939453, 412.15071036499205}, {105.66666412353516, 18.656048080136394}}</string>
+ <string>{{238.74999618530273, 412.15071036499205}, {105.66668701171875, 18.656048080136394}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
- <integer>169426</integer>
+ <integer>169507</integer>
<key>Magnets</key>
<array>
<string>{0, 1}</string>
@@ -349,11 +349,11 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{238.75000762939453, 303.65604172230951}, {105.66666412353516, 18.656048080136394}}</string>
+ <string>{{238.74999618530273, 303.65604172230951}, {105.66668701171875, 18.656048080136394}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
- <integer>169427</integer>
+ <integer>169508</integer>
<key>Magnets</key>
<array>
<string>{0, 1}</string>
@@ -402,11 +402,11 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{238.75000762939453, 393.55704269887212}, {105.66666412353516, 18.656048080136394}}</string>
+ <string>{{238.74999618530273, 393.55704269887212}, {105.66668701171875, 18.656048080136394}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
- <integer>169428</integer>
+ <integer>169509</integer>
<key>Magnets</key>
<array>
<string>{0, 1}</string>
@@ -453,11 +453,11 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{238.75000762939453, 374.90099016834085}, {105.66666412353516, 18.656048080136394}}</string>
+ <string>{{238.74999618530273, 374.90099016834085}, {105.66668701171875, 18.656048080136394}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
- <integer>169429</integer>
+ <integer>169510</integer>
<key>Magnets</key>
<array>
<string>{0, 1}</string>
@@ -504,11 +504,11 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{238.75000762939453, 341.36561209044055}, {105.66666412353516, 33.089282989501953}}</string>
+ <string>{{238.74999618530273, 341.36561209044055}, {105.66668701171875, 33.089282989501953}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
- <integer>169430</integer>
+ <integer>169511</integer>
<key>Magnets</key>
<array>
<string>{0, 1}</string>
@@ -555,11 +555,11 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{238.75000762939453, 322.26348241170439}, {105.66666412353516, 18.656048080136394}}</string>
+ <string>{{238.74999618530273, 322.26348241170439}, {105.66668701171875, 18.656048080136394}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
- <integer>169431</integer>
+ <integer>169512</integer>
<key>Magnets</key>
<array>
<string>{0, 1}</string>
@@ -606,7 +606,7 @@
</dict>
</array>
<key>ID</key>
- <integer>169424</integer>
+ <integer>169505</integer>
<key>Layer</key>
<integer>0</integer>
</dict>
@@ -1094,7 +1094,7 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{238.75000762939462, 430.80675844512831}</string>
+ <string>{238.74999618530282, 430.80675844512831}</string>
<string>{207.66666666666765, 385.656005859375}</string>
</array>
<key>Style</key>
@@ -1123,7 +1123,7 @@
<key>Tail</key>
<dict>
<key>ID</key>
- <integer>169426</integer>
+ <integer>169507</integer>
<key>Info</key>
<integer>6</integer>
</dict>
@@ -1144,7 +1144,7 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{239.33336141608385, 285.57837549845181}</string>
+ <string>{239.25039065750093, 285.57837549845181}</string>
<string>{207.66666666666777, 353.07514659563753}</string>
</array>
<key>Style</key>
@@ -1173,7 +1173,7 @@
<key>Tail</key>
<dict>
<key>ID</key>
- <integer>169425</integer>
+ <integer>169506</integer>
<key>Info</key>
<integer>6</integer>
</dict>
@@ -1515,7 +1515,7 @@
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
-\f0\fs20 \cf0 view}</string>
+\f0\fs20 \cf0 view deriver}</string>
<key>VerticalPad</key>
<integer>0</integer>
</dict>
@@ -1742,7 +1742,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{375.5, 391}, {105.66666412353516, 22.544642175946908}}</string>
+ <string>{{375.5, 391.5}, {105.66666412353516, 22.544642175946908}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -9637,7 +9637,7 @@
<key>MasterSheets</key>
<array/>
<key>ModificationDate</key>
- <string>2014-11-23 07:19:11 +0000</string>
+ <string>2016-03-13 08:04:48 +0000</string>
<key>Modifier</key>
<string>Steve Piercy</string>
<key>NotesVisible</key>
@@ -9732,15 +9732,15 @@
<key>SidebarWidth</key>
<integer>163</integer>
<key>VisibleRegion</key>
- <string>{{-231, -226}, {1037, 1186}}</string>
+ <string>{{152.25, 226.5}, {255.75, 292.75}}</string>
<key>Zoom</key>
- <real>1</real>
+ <real>4</real>
<key>ZoomValues</key>
<array>
<array>
<string>Request Processing</string>
- <real>1</real>
- <real>2</real>
+ <real>4</real>
+ <real>8</real>
</array>
</array>
</dict>
diff --git a/docs/_static/pyramid_request_processing.png b/docs/_static/pyramid_request_processing.png
index 2fbb1e164..c684255fa 100644
--- a/docs/_static/pyramid_request_processing.png
+++ b/docs/_static/pyramid_request_processing.png
Binary files differ
diff --git a/docs/_static/pyramid_request_processing.svg b/docs/_static/pyramid_request_processing.svg
index 21bbcb532..d32d5c5bc 100644
--- a/docs/_static/pyramid_request_processing.svg
+++ b/docs/_static/pyramid_request_processing.svg
@@ -1,3 +1,3 @@
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="91 11 424 533" width="424pt" height="533pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2014-11-23 07:19Z</dc:date><!-- Produced by OmniGraffle Professional 5.4.4 --></metadata><defs><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="SharpArrow_Marker" viewBox="-4 -4 10 8" markerWidth="10" markerHeight="8" color="#191919"><g><path d="M 5 0 L -3 -3 L 0 0 L 0 0 L -3 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="522.94922" cap-height="717.28516" ascent="770.01953" descent="-229.98047" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="12" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Request Processing</title><rect fill="white" width="576" height="733"/><g><title>no exceptions</title><path d="M 155 444.75674 C 155 450.64061 155 486.2592 155 502.71617" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 322.33334 C 154.99999 327.72413 155 337.74646 155 346.1775" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 245.22768 C 154.99999 250.5417 154.99999 257.93189 154.99999 265.10145" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99995 198.62203 C 154.99995 203.74682 154.99998 209.1909 154.99999 215.28222" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="102.16667" y="45.183037" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="45.183037" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 50.455358)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="4.7596016" y="10" textLength="88.92578">middleware ingress </tspan></text><rect x="102.16667" y="96.183037" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="96.183037" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 101.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.983723" y="10" textLength="61.69922">tween ingress</tspan></text><rect x="102.16667" y="222.18304" width="105.666664" height="22.544641" fill="#d2ffd0"/><rect x="102.16667" y="222.18304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 227.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="28.660969" y="10" textLength="38.344727">traversal</tspan></text><rect x="238.83336" y="247.18304" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="247.18304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 252.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.424641" y="10" textLength="62.817383">ContextFound</tspan></text><rect x="102.16667" y="422.2121" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="422.2121" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 427.48442)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="18.094563" y="10" textLength="59.47754">tween egress</tspan></text><rect x="239" y="445.2359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="445.2359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 450.50821)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.3113594" y="10" textLength="85.043945">response callbacks</tspan></text><rect x="239" y="497.2359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="497.2359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 502.5082)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="8.6463203" y="10" textLength="5">fi</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="13.64632" y="10" textLength="73.374023">nished callbacks</tspan></text><rect x="102.16667" y="509.61795" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="509.61795" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 514.89027)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.8704414" y="10" textLength="83.92578">middleware egress</tspan></text><path d="M 155 67.72768 C 155 73.048893 155 81.55558 155 89.2853" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 155 119.22768 C 155 124.62026 154.99997 133.48763 154.99996 141.38632" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="375.5" y="391" width="105.666664" height="22.544642" fill="#dfbeff"/><rect x="375.5" y="391" width="105.666664" height="22.544642" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(380.5 396.27232)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.702961" y="10" textLength="62.260742">BeforeRender</tspan></text><text transform="translate(233.5 20)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="bold" x=".31445312" y="11" textLength="115.371094">Request Processing</tspan></text><path d="M 375.99995 42.910746 L 498.66662 42.910746 C 501.42805 42.910746 503.66662 45.149323 503.66662 47.910746 L 503.66662 222 C 503.66662 224.76142 501.42805 227 498.66662 227 L 375.99995 227 C 373.23853 227 370.99995 224.76142 370.99995 222 L 370.99995 47.910746 C 370.99995 45.149323 373.23853 42.910746 375.99995 42.910746 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(375.99995 42.910746)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="0" y="10" textLength="35.55664">Legend</tspan></text><rect x="383.66662" y="63.908513" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="383.66662" y="63.908513" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 69.180834)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="35.601887" y="10" textLength="24.46289">event</tspan></text><rect x="383.66662" y="186.58226" width="105.666664" height="22.544641" fill="#fed153"/><rect x="383.66662" y="186.58226" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 191.85458)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="29.769367" y="10" textLength="36.12793">callback</tspan></text><rect x="383.66662" y="158.54998" width="105.666664" height="22.544641" fill="#ffff6c"/><rect x="383.66662" y="158.54998" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 163.8223)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.83089" y="10" textLength="20.004883">view</tspan></text><rect x="383.66662" y="91.94079" width="105.666664" height="33.089283" fill="#a4cfff"/><rect x="383.66662" y="91.94079" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 96.48543)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.148762" y="10" textLength="76.14746">external process </tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="2.8162422" y="22" textLength="90.03418">(middleware, tween)</tspan></text><rect x="383.66662" y="130.51771" width="105.666664" height="22.544641" fill="#d2ffd0"/><rect x="383.66662" y="130.51771" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 135.79003)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="12.537922" y="10" textLength="70.59082">internal process</tspan></text><line x1="154.99999" y1="258.44082" x2="238.83336" y2="258.45536" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="102.16667" y="353.07515" width="105.666664" height="33.089283" fill="#ffff6c"/><rect x="102.16667" y="353.07515" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 363.61979)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.205402" y="10" textLength="57.25586">view pipeline</tspan></text><path d="M 155 386.66443 C 155 392.17252 155 405.5052 155 415.30935" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="239.33336" y1="285.57838" x2="207.66667" y2="353.07515" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><line x1="238.75001" y1="430.80676" x2="207.66667" y2="385.656" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><rect x="102.16666" y="305.0893" width="105.666664" height="17.244049" fill="#d2ffd0"/><rect x="102.16666" y="305.0893" width="105.666664" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 307.71132)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="24.764484" y="10" textLength="46.137695">predicates</tspan></text><rect x="102.16666" y="272" width="105.666664" height="33.089294" fill="#d2ffd0"/><rect x="102.16666" y="272" width="105.666664" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 282.54465)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="21.707844" y="10" textLength="52.250977">view lookup</tspan></text><rect x="102.166606" y="181.37798" width="105.666695" height="17.244049" fill="#d2ffd0"/><rect x="102.166606" y="181.37798" width="105.666695" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 184)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.978855" y="10" textLength="71.708984">route predicates</tspan></text><rect x="102.166606" y="148.28869" width="105.666695" height="33.089294" fill="#d2ffd0"/><rect x="102.166606" y="148.28869" width="105.666695" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 158.83333)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="18.001804" y="10" textLength="20.004883">URL</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.640476" y="10" textLength="40.024414"> dispatch</tspan></text><rect x="239.8334" y="117.3192" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="239.8334" y="117.3192" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244.8334 122.59152)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.207844" y="10" textLength="57.250977">NewRequest</tspan></text><line x1="154.99999" y1="128.68025" x2="239.8334" y2="128.59152" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.83336" y="471.2262" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="471.2262" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 476.49852)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="15.316242" y="10" textLength="65.03418">NewResponse</tspan></text><line x1="155" y1="470.25295" x2="238.33861" y2="482.42625" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.75001" y="322.26348" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="322.26348" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 325.5915)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="1.9812813" y="10" textLength="91.7041">view mapper ingress</tspan></text><rect x="238.75001" y="341.36561" width="105.666664" height="33.089283" fill="#ffff6c"/><rect x="238.75001" y="341.36561" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 351.91025)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.83089" y="10" textLength="20.004883">view</tspan></text><rect x="238.75001" y="374.901" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="374.901" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 378.22901)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="3.0921211" y="10" textLength="89.48242">view mapper egress</tspan></text><rect x="238.75001" y="393.55704" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="393.55704" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 396.88507)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="8.9173164" y="10" textLength="77.83203">response adapter</tspan></text><rect x="238.75001" y="303.65604" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="303.65604" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 306.98407)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="6.702961" y="10" textLength="82.26074">decorators ingress</tspan></text><rect x="238.75001" y="412.1507" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="412.1507" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 415.47873)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="7.813801" y="10" textLength="80.039062">decorators egress</tspan></text><rect x="238.83336" y="285" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.83336" y="285" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 288.32802)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.202972" y="10" textLength="57.260742">authorization</tspan></text><line x1="155" y1="482.12575" x2="238.52297" y2="508.3584" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="155" y1="459.27668" x2="238.50027" y2="456.52468" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="344.41667" y1="402.88507" x2="375.5" y2="402.27232" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/></g></g></svg>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="91 11 424 533" width="424pt" height="533pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2016-03-13 08:04Z</dc:date><!-- Produced by OmniGraffle Professional 5.4.4 --></metadata><defs><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="SharpArrow_Marker" viewBox="-4 -4 10 8" markerWidth="10" markerHeight="8" color="#191919"><g><path d="M 5 0 L -3 -3 L 0 0 L 0 0 L -3 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="522.94922" cap-height="717.28516" ascent="770.01953" descent="-229.98047" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="12" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Request Processing</title><rect fill="white" width="576" height="733"/><g><title>no exceptions</title><path d="M 155 444.75674 C 155 450.64061 155 486.2592 155 502.71617" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 322.33334 C 154.99999 327.72413 155 337.74646 155 346.1775" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 245.22768 C 154.99999 250.5417 154.99999 257.93189 154.99999 265.10145" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99995 198.62203 C 154.99995 203.74682 154.99998 209.1909 154.99999 215.28222" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="102.16667" y="45.183037" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="45.183037" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 50.455358)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="4.7596016" y="10" textLength="88.92578">middleware ingress </tspan></text><rect x="102.16667" y="96.183037" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="96.183037" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 101.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.983723" y="10" textLength="61.69922">tween ingress</tspan></text><rect x="102.16667" y="222.18304" width="105.666664" height="22.544641" fill="#d2ffd0"/><rect x="102.16667" y="222.18304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 227.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="28.660969" y="10" textLength="38.344727">traversal</tspan></text><rect x="238.83336" y="247.18304" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="247.18304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 252.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.424641" y="10" textLength="62.817383">ContextFound</tspan></text><rect x="102.16667" y="422.2121" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="422.2121" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 427.48442)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="18.094563" y="10" textLength="59.47754">tween egress</tspan></text><rect x="239" y="445.2359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="445.2359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 450.50821)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.3113594" y="10" textLength="85.043945">response callbacks</tspan></text><rect x="239" y="497.2359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="497.2359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 502.5082)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="8.6463203" y="10" textLength="5">fi</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="13.64632" y="10" textLength="73.374023">nished callbacks</tspan></text><rect x="102.16667" y="509.61795" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="509.61795" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 514.89027)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.8704414" y="10" textLength="83.92578">middleware egress</tspan></text><path d="M 155 67.72768 C 155 73.048893 155 81.55558 155 89.2853" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 155 119.22768 C 155 124.62026 154.99997 133.48763 154.99996 141.38632" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="375.5" y="391.5" width="105.666664" height="22.544642" fill="#dfbeff"/><rect x="375.5" y="391.5" width="105.666664" height="22.544642" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(380.5 396.77232)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.702961" y="10" textLength="62.260742">BeforeRender</tspan></text><text transform="translate(233.5 20)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="bold" x=".31445312" y="11" textLength="115.371094">Request Processing</tspan></text><path d="M 375.99995 42.910746 L 498.66662 42.910746 C 501.42805 42.910746 503.66662 45.149323 503.66662 47.910746 L 503.66662 222 C 503.66662 224.76142 501.42805 227 498.66662 227 L 375.99995 227 C 373.23853 227 370.99995 224.76142 370.99995 222 L 370.99995 47.910746 C 370.99995 45.149323 373.23853 42.910746 375.99995 42.910746 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(375.99995 42.910746)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="0" y="10" textLength="35.55664">Legend</tspan></text><rect x="383.66662" y="63.908513" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="383.66662" y="63.908513" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 69.180834)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="35.601887" y="10" textLength="24.46289">event</tspan></text><rect x="383.66662" y="186.58226" width="105.666664" height="22.544641" fill="#fed153"/><rect x="383.66662" y="186.58226" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 191.85458)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="29.769367" y="10" textLength="36.12793">callback</tspan></text><rect x="383.66662" y="158.54998" width="105.666664" height="22.544641" fill="#ffff6c"/><rect x="383.66662" y="158.54998" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 163.8223)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="21.158527" y="10" textLength="53.34961">view deriver</tspan></text><rect x="383.66662" y="91.94079" width="105.666664" height="33.089283" fill="#a4cfff"/><rect x="383.66662" y="91.94079" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 96.48543)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.148762" y="10" textLength="76.14746">external process </tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="2.8162422" y="22" textLength="90.03418">(middleware, tween)</tspan></text><rect x="383.66662" y="130.51771" width="105.666664" height="22.544641" fill="#d2ffd0"/><rect x="383.66662" y="130.51771" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 135.79003)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="12.537922" y="10" textLength="70.59082">internal process</tspan></text><line x1="154.99999" y1="258.44082" x2="238.83336" y2="258.45536" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="102.16667" y="353.07515" width="105.666664" height="33.089283" fill="#ffff6c"/><rect x="102.16667" y="353.07515" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 363.61979)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.205402" y="10" textLength="57.25586">view pipeline</tspan></text><path d="M 155 386.66443 C 155 392.17252 155 405.5052 155 415.30935" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="239.25039" y1="285.57838" x2="207.66667" y2="353.07515" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><line x1="238.75" y1="430.80676" x2="207.66667" y2="385.656" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><rect x="102.16666" y="305.0893" width="105.666664" height="17.244049" fill="#d2ffd0"/><rect x="102.16666" y="305.0893" width="105.666664" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 307.71132)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="24.764484" y="10" textLength="46.137695">predicates</tspan></text><rect x="102.16666" y="272" width="105.666664" height="33.089294" fill="#d2ffd0"/><rect x="102.16666" y="272" width="105.666664" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 282.54465)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="21.707844" y="10" textLength="52.250977">view lookup</tspan></text><rect x="102.166606" y="181.37798" width="105.666695" height="17.244049" fill="#d2ffd0"/><rect x="102.166606" y="181.37798" width="105.666695" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 184)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.978855" y="10" textLength="71.708984">route predicates</tspan></text><rect x="102.166606" y="148.28869" width="105.666695" height="33.089294" fill="#d2ffd0"/><rect x="102.166606" y="148.28869" width="105.666695" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 158.83333)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="18.001804" y="10" textLength="20.004883">URL</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.640476" y="10" textLength="40.024414"> dispatch</tspan></text><rect x="239.8334" y="117.3192" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="239.8334" y="117.3192" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244.8334 122.59152)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.207844" y="10" textLength="57.250977">NewRequest</tspan></text><line x1="154.99999" y1="128.68025" x2="239.8334" y2="128.59152" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.83336" y="471.2262" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="471.2262" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 476.49852)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="15.316242" y="10" textLength="65.03418">NewResponse</tspan></text><line x1="155" y1="470.25295" x2="238.33861" y2="482.42625" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.75" y="322.26348" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="322.26348" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 325.5915)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="1.9812927" y="10" textLength="91.7041">view mapper ingress</tspan></text><rect x="238.75" y="341.36561" width="105.66669" height="33.089283" fill="#ffff6c"/><rect x="238.75" y="341.36561" width="105.66669" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 351.91025)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.830902" y="10" textLength="20.004883">view</tspan></text><rect x="238.75" y="374.901" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="374.901" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 378.22901)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="3.0921326" y="10" textLength="89.48242">view mapper egress</tspan></text><rect x="238.75" y="393.55704" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="393.55704" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 396.88507)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="8.917328" y="10" textLength="77.83203">response adapter</tspan></text><rect x="238.75" y="303.65604" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="303.65604" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 306.98407)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="6.7029724" y="10" textLength="82.26074">decorators ingress</tspan></text><rect x="238.75" y="412.1507" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="412.1507" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 415.47873)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="7.8138123" y="10" textLength="80.039062">decorators egress</tspan></text><rect x="238.75" y="285" width="105.75003" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="285" width="105.75003" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 288.32802)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.244644" y="10" textLength="57.260742">authorization</tspan></text><line x1="155" y1="482.12575" x2="238.52297" y2="508.3584" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="155" y1="459.27668" x2="238.50027" y2="456.52468" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="344.41668" y1="402.88507" x2="375.5" y2="402.77232" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/></g></g></svg>
diff --git a/docs/api/config.rst b/docs/api/config.rst
index ae913d32c..e083dbc68 100644
--- a/docs/api/config.rst
+++ b/docs/api/config.rst
@@ -66,6 +66,7 @@
.. automethod:: add_tween
.. automethod:: add_route_predicate
.. automethod:: add_view_predicate
+ .. automethod:: add_view_deriver
.. automethod:: set_request_factory
.. automethod:: set_root_factory
.. automethod:: set_session_factory
diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst
index de2a664a4..635d3c5b6 100644
--- a/docs/api/interfaces.rst
+++ b/docs/api/interfaces.rst
@@ -91,3 +91,9 @@ Other Interfaces
.. autointerface:: ICacheBuster
:members:
+
+ .. autointerface:: IViewDeriver
+ :members:
+
+ .. autointerface:: IViewDeriverInfo
+ :members:
diff --git a/docs/api/paster.rst b/docs/api/paster.rst
index edc3738fc..27bc81a1f 100644
--- a/docs/api/paster.rst
+++ b/docs/api/paster.rst
@@ -11,4 +11,4 @@
.. autofunction:: get_appsettings(config_uri, name=None, options=None)
- .. autofunction:: setup_logging(config_uri)
+ .. autofunction:: setup_logging(config_uri, global_conf=None)
diff --git a/docs/api/viewderivers.rst b/docs/api/viewderivers.rst
new file mode 100644
index 000000000..2a141501e
--- /dev/null
+++ b/docs/api/viewderivers.rst
@@ -0,0 +1,17 @@
+.. _viewderivers_module:
+
+:mod:`pyramid.viewderivers`
+---------------------------
+
+.. automodule:: pyramid.viewderivers
+
+ .. attribute:: INGRESS
+
+ Constant representing the request ingress, for use in ``under``
+ arguments to :meth:`pyramid.config.Configurator.add_view_deriver`.
+
+ .. attribute:: VIEW
+
+ Constant representing the :term:`view callable` at the end of the view
+ pipeline, for use in ``over`` arguments to
+ :meth:`pyramid.config.Configurator.add_view_deriver`.
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 858f3de51..1d8e0de1b 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -1092,6 +1092,13 @@ Glossary
a client to query the new version of the asset. See :ref:`cache_busting`
for more information.
+ view deriver
+ A view deriver is a composable component of the view pipeline which is
+ used to create a :term:`view callable`. A view deriver is a callable
+ implementing the :class:`pyramid.interfaces.IViewDeriver` interface.
+ Examples of built-in derivers including view mapper, the permission
+ checker, and applying a renderer to a dictionary returned from the view.
+
pip
The `Python Packaging Authority <https://www.pypa.io/>`_ recommended tool
for installing Python packages.
diff --git a/docs/narr/extconfig.rst b/docs/narr/extconfig.rst
index fee8d0d3a..af7d0a349 100644
--- a/docs/narr/extconfig.rst
+++ b/docs/narr/extconfig.rst
@@ -259,6 +259,7 @@ Pre-defined Phases
- :meth:`pyramid.config.Configurator.add_route_predicate`
- :meth:`pyramid.config.Configurator.add_subscriber_predicate`
- :meth:`pyramid.config.Configurator.add_view_predicate`
+- :meth:`pyramid.config.Configurator.add_view_deriver`
- :meth:`pyramid.config.Configurator.set_authorization_policy`
- :meth:`pyramid.config.Configurator.set_default_permission`
- :meth:`pyramid.config.Configurator.set_view_mapper`
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 7ff119b53..2c3782387 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -1547,3 +1547,186 @@ in every subscriber registration. It is not the responsibility of the
predicate author to make every predicate make sense for every event type; it is
the responsibility of the predicate consumer to use predicates that make sense
for a particular event type registration.
+
+
+.. index::
+ single: view derivers
+
+.. _view_derivers:
+
+View Derivers
+-------------
+
+.. versionadded:: 1.7
+
+Every URL processed by :app:`Pyramid` is matched against a custom view
+pipeline. See :ref:`router_chapter` for how this works. The view pipeline
+itself is built from the user-supplied :term:`view callable`, which is then
+composed with :term:`view derivers <view deriver>`. A view deriver is a
+composable element of the view pipeline which is used to wrap a view with
+added functionality. View derivers are very similar to the ``decorator``
+argument to :meth:`pyramid.config.Configurator.add_view`, except that they have
+the option to execute for every view in the application.
+
+It is helpful to think of a :term:`view deriver` as middleware for views.
+Unlike tweens or WSGI middleware which are scoped to the application itself,
+a view deriver is invoked once per view in the application, and can use
+configuration options from the view to customize its behavior.
+
+Built-in View Derivers
+~~~~~~~~~~~~~~~~~~~~~~
+
+There are several built-in view derivers that :app:`Pyramid` will automatically
+apply to any view. Below they are defined in order from furthest to closest to
+the user-defined :term:`view callable`:
+
+``secured_view``
+
+ Enforce the ``permission`` defined on the view. This element is a no-op if no
+ permission is defined. Note there will always be a permission defined if a
+ default permission was assigned via
+ :meth:`pyramid.config.Configurator.set_default_permission`.
+
+ This element will also output useful debugging information when
+ ``pyramid.debug_authorization`` is enabled.
+
+``owrapped_view``
+
+ Invokes the wrapped view defined by the ``wrapper`` option.
+
+``http_cached_view``
+
+ Applies cache control headers to the response defined by the ``http_cache``
+ option. This element is a no-op if the ``pyramid.prevent_http_cache`` setting
+ is enabled or the ``http_cache`` option is ``None``.
+
+``decorated_view``
+
+ Wraps the view with the decorators from the ``decorator`` option.
+
+``rendered_view``
+
+ Adapts the result of the :term:`view callable` into a :term:`response`
+ object. Below this point the result may be any Python object.
+
+``mapped_view``
+
+ Applies the :term:`view mapper` defined by the ``mapper`` option or the
+ application's default view mapper to the :term:`view callable`. This
+ is always the closest deriver to the user-defined view and standardizes the
+ view pipeline interface to accept ``(context, request)`` from all previous
+ view derivers.
+
+.. warning::
+
+ Any view derivers defined ``under`` the ``rendered_view`` are not
+ guaranteed to receive a valid response object. Rather they will receive the
+ result from the :term:`view mapper` which is likely the original response
+ returned from the view. This is possibly a dictionary for a renderer but it
+ may be any Python object that may be adapted into a response.
+
+Custom View Derivers
+~~~~~~~~~~~~~~~~~~~~
+
+It is possible to define custom view derivers which will affect all views in an
+application. There are many uses for this, but most will likely be centered
+around monitoring and security. In order to register a custom :term:`view
+deriver`, you should create a callable that conforms to the
+:class:`pyramid.interfaces.IViewDeriver` interface, and then register it with
+your application using :meth:`pyramid.config.Configurator.add_view_deriver`.
+For example, below is a callable that can provide timing information for the
+view pipeline:
+
+.. code-block:: python
+ :linenos:
+
+ import time
+
+ def timing_view(view, info):
+ def wrapper_view(context, request):
+ start = time.time()
+ response = view(context, request)
+ end = time.time()
+ response.headers['X-View-Performance'] = '%.3f' % (end - start,)
+ return wrapper_view
+
+ config.add_view_deriver(timing_view)
+
+View derivers are unique in that they have access to most of the options
+passed to :meth:`pyramid.config.Configurator.add_view` in order to decide what
+to do, and they have a chance to affect every view in the application.
+
+Let's look at one more example which will protect views by requiring a CSRF
+token unless ``disable_csrf=True`` is passed to the view:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.response import Response
+ from pyramid.session import check_csrf_token
+
+ def require_csrf_view(view, info):
+ wrapper_view = view
+ if not info.options.get('disable_csrf', False):
+ def wrapper_view(context, request):
+ if request.method == 'POST':
+ check_csrf_token(request)
+ return view(context, request)
+ return wrapper_view
+
+ require_csrf_view.options = ('disable_csrf',)
+
+ config.add_view_deriver(require_csrf_view)
+
+ def protected_view(request):
+ return Response('protected')
+
+ def unprotected_view(request):
+ return Response('unprotected')
+
+ config.add_view(protected_view, name='safe')
+ config.add_view(unprotected_view, name='unsafe', disable_csrf=True)
+
+Navigating to ``/safe`` with a POST request will then fail when the call to
+:func:`pyramid.session.check_csrf_token` raises a
+:class:`pyramid.exceptions.BadCSRFToken` exception. However, ``/unsafe`` will
+not error.
+
+Ordering View Derivers
+~~~~~~~~~~~~~~~~~~~~~~
+
+By default, every new view deriver is added between the ``decorated_view`` and
+``rendered_view`` built-in derivers. It is possible to customize this ordering
+using the ``over`` and ``under`` options. Each option can use the names of
+other view derivers in order to specify an ordering. There should rarely be a
+reason to worry about the ordering of the derivers except when the deriver
+depends on other operations in the view pipeline.
+
+Both ``over`` and ``under`` may also be iterables of constraints. For either
+option, if one or more constraints was defined, at least one must be satisfied,
+else a :class:`pyramid.exceptions.ConfigurationError` will be raised. This may
+be used to define fallback constraints if another deriver is missing.
+
+Two sentinel values exist, :attr:`pyramid.viewderivers.INGRESS` and
+:attr:`pyramid.viewderivers.VIEW`, which may be used when specifying
+constraints at the edges of the view pipeline. For example, to add a deriver
+at the start of the pipeline you may use ``under=INGRESS``.
+
+It is not possible to add a view deriver under the ``mapped_view`` as the
+:term:`view mapper` is intimately tied to the signature of the user-defined
+:term:`view callable`. If you simply need to know what the original view
+callable was, it can be found as ``info.original_view`` on the provided
+:class:`pyramid.interfaces.IViewDeriverInfo` object passed to every view
+deriver.
+
+.. warning::
+
+ The default constraints for any view deriver are ``over='rendered_view'``
+ and ``under='decorated_view'``. When escaping these constraints you must
+ take care to avoid cyclic dependencies between derivers. For example, if
+ you want to add a new view deriver before ``secured_view`` then
+ simply specifying ``over='secured_view'`` is not enough, because the
+ default is also under ``decorated view`` there will be an unsatisfiable
+ cycle. You must specify a valid ``under`` constraint as well, such as
+ ``under=INGRESS`` to fall between INGRESS and ``secured_view`` at the
+ beginning of the view pipeline.
diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst
index 839a48df4..b385eaf96 100644
--- a/docs/narr/i18n.rst
+++ b/docs/narr/i18n.rst
@@ -670,6 +670,21 @@ There exists a recipe within the :term:`Pyramid Community Cookbook` named
:ref:`Mako Internationalization <cookbook:mako_i18n>` which explains how to add
idiomatic i18n support to :term:`Mako` templates.
+
+.. index::
+ single: Jinja2 i18n
+
+Jinja2 Pyramid i18n Support
+---------------------------
+
+The add-on `pyramid_jinja2 <https://github.com/Pylons/pyramid_jinja2>`_
+provides a scaffold with an example of how to use internationalization with
+Jinja2 in Pyramid. See the documentation sections `Internalization (i18n)
+<http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/#internalization-i18n>`_
+and `Paster Template I18N
+<http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/#paster-template-i18n>`_.
+
+
.. index::
single: localization deployment settings
single: default_locale_name
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py
index 5a1b7b122..553f32c9b 100644
--- a/pyramid/config/__init__.py
+++ b/pyramid/config/__init__.py
@@ -378,6 +378,7 @@ class Configurator(
self.add_default_response_adapters()
self.add_default_renderers()
self.add_default_view_predicates()
+ self.add_default_view_derivers()
self.add_default_route_predicates()
if exceptionresponse_view is not None:
@@ -521,10 +522,11 @@ class Configurator(
self.registry.registerUtility(predlist, IPredicateList, name=name)
return predlist
+
def _add_predicate(self, type, name, factory, weighs_more_than=None,
weighs_less_than=None):
factory = self.maybe_dotted(factory)
- discriminator = ('%s predicate' % type, name)
+ discriminator = ('%s option' % type, name)
intr = self.introspectable(
'%s predicates' % type,
discriminator,
diff --git a/pyramid/config/tweens.py b/pyramid/config/tweens.py
index cd14c9ff6..8e1800f33 100644
--- a/pyramid/config/tweens.py
+++ b/pyramid/config/tweens.py
@@ -18,6 +18,7 @@ from pyramid.tweens import (
from pyramid.config.util import (
action_method,
+ is_string_or_iterable,
TopologicalSorter,
)
@@ -122,12 +123,6 @@ class TweensConfiguratorMixin(object):
tween_factory = self.maybe_dotted(tween_factory)
- def is_string_or_iterable(v):
- if isinstance(v, string_types):
- return True
- if hasattr(v, '__iter__'):
- return True
-
for t, p in [('over', over), ('under', under)]:
if p is not None:
if not is_string_or_iterable(p):
diff --git a/pyramid/config/util.py b/pyramid/config/util.py
index 0fd9ef4a7..626e8d5fe 100644
--- a/pyramid/config/util.py
+++ b/pyramid/config/util.py
@@ -5,6 +5,7 @@ from pyramid.compat import (
bytes_,
getargspec,
is_nonstr_iter,
+ string_types,
)
from pyramid.compat import im_func
@@ -23,6 +24,12 @@ ActionInfo = ActionInfo # support bw compat imports
MAX_ORDER = 1 << 30
DEFAULT_PHASH = md5().hexdigest()
+def is_string_or_iterable(v):
+ if isinstance(v, string_types):
+ return True
+ if hasattr(v, '__iter__'):
+ return True
+
def as_sorted_tuple(val):
if not is_nonstr_iter(val):
val = (val,)
@@ -115,6 +122,10 @@ class PredicateList(object):
before=weighs_less_than,
)
+ def names(self):
+ # Return the list of valid predicate names.
+ return self.sorter.names
+
def make(self, config, **kw):
# Given a configurator and a list of keywords, a predicate list is
# computed. Elsewhere in the code, we evaluate predicates using a
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 20bcaa078..3f6a9080d 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -8,15 +8,11 @@ from zope.interface import (
Interface,
implementedBy,
implementer,
- provider,
)
from zope.interface.interfaces import IInterface
from pyramid.interfaces import (
- IAuthenticationPolicy,
- IAuthorizationPolicy,
- IDebugLogger,
IDefaultPermission,
IException,
IExceptionViewClassifier,
@@ -30,7 +26,8 @@ from pyramid.interfaces import (
IStaticURLInfo,
IView,
IViewClassifier,
- IViewMapper,
+ IViewDerivers,
+ IViewDeriverInfo,
IViewMapperFactory,
PHASE1_CONFIG,
)
@@ -43,11 +40,11 @@ from pyramid.compat import (
urlparse,
url_quote,
WIN,
- is_bound_method,
- is_unbound_method,
is_nonstr_iter,
)
+from pyramid.decorator import reify
+
from pyramid.exceptions import (
ConfigurationError,
PredicateMismatch,
@@ -64,472 +61,46 @@ from pyramid.registry import (
Deferred,
)
-from pyramid.response import Response
-
from pyramid.security import NO_PERMISSION_REQUIRED
from pyramid.static import static_view
from pyramid.url import parse_url_overrides
-from pyramid.view import (
- render_view_to_response,
- AppendSlashNotFoundViewFactory,
- )
+from pyramid.view import AppendSlashNotFoundViewFactory
+import pyramid.util
from pyramid.util import (
- object_description,
viewdefaults,
action_method,
+ TopologicalSorter,
)
import pyramid.config.predicates
+import pyramid.viewderivers
+
+from pyramid.viewderivers import (
+ INGRESS,
+ VIEW,
+ preserve_view_attrs,
+ view_description,
+ requestonly,
+ DefaultViewMapper,
+ wraps_view,
+)
from pyramid.config.util import (
DEFAULT_PHASH,
MAX_ORDER,
- takes_one_arg,
+ as_sorted_tuple,
)
urljoin = urlparse.urljoin
url_parse = urlparse.urlparse
-def view_description(view):
- try:
- return view.__text__
- except AttributeError:
- # custom view mappers might not add __text__
- return object_description(view)
-
-def wraps_view(wrapper):
- def inner(self, view):
- wrapper_view = wrapper(self, view)
- return preserve_view_attrs(view, wrapper_view)
- return inner
-
-def preserve_view_attrs(view, wrapper):
- if view is None:
- return wrapper
-
- if wrapper is view:
- return view
-
- original_view = getattr(view, '__original_view__', None)
-
- if original_view is None:
- original_view = view
-
- wrapper.__wraps__ = view
- wrapper.__original_view__ = original_view
- wrapper.__module__ = view.__module__
- wrapper.__doc__ = view.__doc__
-
- try:
- wrapper.__name__ = view.__name__
- except AttributeError:
- wrapper.__name__ = repr(view)
-
- # attrs that may not exist on "view", but, if so, must be attached to
- # "wrapped view"
- for attr in ('__permitted__', '__call_permissive__', '__permission__',
- '__predicated__', '__predicates__', '__accept__',
- '__order__', '__text__'):
- try:
- setattr(wrapper, attr, getattr(view, attr))
- except AttributeError:
- pass
-
- return wrapper
-
-class ViewDeriver(object):
- def __init__(self, **kw):
- self.kw = kw
- self.registry = kw['registry']
- self.authn_policy = self.registry.queryUtility(IAuthenticationPolicy)
- self.authz_policy = self.registry.queryUtility(IAuthorizationPolicy)
- self.logger = self.registry.queryUtility(IDebugLogger)
-
- def __call__(self, view):
- return self.attr_wrapped_view(
- self.predicated_view(
- self.authdebug_view(
- self.secured_view(
- self.owrapped_view(
- self.http_cached_view(
- self.decorated_view(
- self.rendered_view(
- self.mapped_view(
- view)))))))))
-
- @wraps_view
- def mapped_view(self, view):
- mapper = self.kw.get('mapper')
- if mapper is None:
- mapper = getattr(view, '__view_mapper__', None)
- if mapper is None:
- mapper = self.registry.queryUtility(IViewMapperFactory)
- if mapper is None:
- mapper = DefaultViewMapper
-
- mapped_view = mapper(**self.kw)(view)
- return mapped_view
-
- @wraps_view
- def owrapped_view(self, view):
- wrapper_viewname = self.kw.get('wrapper_viewname')
- viewname = self.kw.get('viewname')
- if not wrapper_viewname:
- return view
- def _owrapped_view(context, request):
- response = view(context, request)
- request.wrapped_response = response
- request.wrapped_body = response.body
- request.wrapped_view = view
- wrapped_response = render_view_to_response(context, request,
- wrapper_viewname)
- if wrapped_response is None:
- raise ValueError(
- 'No wrapper view named %r found when executing view '
- 'named %r' % (wrapper_viewname, viewname))
- return wrapped_response
- return _owrapped_view
-
- @wraps_view
- def http_cached_view(self, view):
- if self.registry.settings.get('prevent_http_cache', False):
- return view
-
- seconds = self.kw.get('http_cache')
-
- if seconds is None:
- return view
-
- options = {}
-
- if isinstance(seconds, (tuple, list)):
- try:
- seconds, options = seconds
- except ValueError:
- raise ConfigurationError(
- 'If http_cache parameter is a tuple or list, it must be '
- 'in the form (seconds, options); not %s' % (seconds,))
-
- def wrapper(context, request):
- response = view(context, request)
- prevent_caching = getattr(response.cache_control, 'prevent_auto',
- False)
- if not prevent_caching:
- response.cache_expires(seconds, **options)
- return response
-
- return wrapper
-
- @wraps_view
- def secured_view(self, view):
- permission = self.kw.get('permission')
- if permission == NO_PERMISSION_REQUIRED:
- # allow views registered within configurations that have a
- # default permission to explicitly override the default
- # permission, replacing it with no permission at all
- permission = None
-
- wrapped_view = view
- if (
- self.authn_policy and
- self.authz_policy and
- (permission is not None)
- ):
- def _permitted(context, request):
- principals = self.authn_policy.effective_principals(request)
- return self.authz_policy.permits(context, principals,
- permission)
- def _secured_view(context, request):
- result = _permitted(context, request)
- if result:
- return view(context, request)
- view_name = getattr(view, '__name__', view)
- msg = getattr(
- request, 'authdebug_message',
- 'Unauthorized: %s failed permission check' % view_name)
- raise HTTPForbidden(msg, result=result)
- _secured_view.__call_permissive__ = view
- _secured_view.__permitted__ = _permitted
- _secured_view.__permission__ = permission
- wrapped_view = _secured_view
-
- return wrapped_view
-
- @wraps_view
- def authdebug_view(self, view):
- wrapped_view = view
- settings = self.registry.settings
- permission = self.kw.get('permission')
- if settings and settings.get('debug_authorization', False):
- def _authdebug_view(context, request):
- view_name = getattr(request, 'view_name', None)
-
- if self.authn_policy and self.authz_policy:
- if permission is NO_PERMISSION_REQUIRED:
- msg = 'Allowed (NO_PERMISSION_REQUIRED)'
- elif permission is None:
- msg = 'Allowed (no permission registered)'
- else:
- principals = self.authn_policy.effective_principals(
- request)
- msg = str(self.authz_policy.permits(context,
- principals,
- permission))
- else:
- msg = 'Allowed (no authorization policy in use)'
-
- view_name = getattr(request, 'view_name', None)
- url = getattr(request, 'url', None)
- msg = ('debug_authorization of url %s (view name %r against '
- 'context %r): %s' % (url, view_name, context, msg))
- self.logger and self.logger.debug(msg)
- if request is not None:
- request.authdebug_message = msg
- return view(context, request)
-
- wrapped_view = _authdebug_view
-
- return wrapped_view
-
- @wraps_view
- def predicated_view(self, view):
- preds = self.kw.get('predicates', ())
- if not preds:
- return view
-
- def predicate_wrapper(context, request):
- for predicate in preds:
- if not predicate(context, request):
- view_name = getattr(view, '__name__', view)
- raise PredicateMismatch(
- 'predicate mismatch for view %s (%s)' % (
- view_name, predicate.text()))
- return view(context, request)
-
- def checker(context, request):
- return all((predicate(context, request) for predicate in
- preds))
- predicate_wrapper.__predicated__ = checker
- predicate_wrapper.__predicates__ = preds
- return predicate_wrapper
-
- @wraps_view
- def attr_wrapped_view(self, view):
- kw = self.kw
- accept, order, phash = (kw.get('accept', None),
- kw.get('order', MAX_ORDER),
- kw.get('phash', DEFAULT_PHASH))
- # this is a little silly but we don't want to decorate the original
- # function with attributes that indicate accept, order, and phash,
- # so we use a wrapper
- if (
- (accept is None) and
- (order == MAX_ORDER) and
- (phash == DEFAULT_PHASH)
- ):
- return view # defaults
- def attr_view(context, request):
- return view(context, request)
- attr_view.__accept__ = accept
- attr_view.__order__ = order
- attr_view.__phash__ = phash
- attr_view.__view_attr__ = self.kw.get('attr')
- attr_view.__permission__ = self.kw.get('permission')
- return attr_view
-
- @wraps_view
- def rendered_view(self, view):
- # one way or another this wrapper must produce a Response (unless
- # the renderer is a NullRendererHelper)
- renderer = self.kw.get('renderer')
- if renderer is None:
- # register a default renderer if you want super-dynamic
- # rendering. registering a default renderer will also allow
- # override_renderer to work if a renderer is left unspecified for
- # a view registration.
- return self._response_resolved_view(view)
- if renderer is renderers.null_renderer:
- return view
- return self._rendered_view(view, renderer)
-
- def _rendered_view(self, view, view_renderer):
- def rendered_view(context, request):
- result = view(context, request)
- if result.__class__ is Response: # potential common case
- response = result
- else:
- registry = self.registry
- # this must adapt, it can't do a simple interface check
- # (avoid trying to render webob responses)
- response = registry.queryAdapterOrSelf(result, IResponse)
- if response is None:
- attrs = getattr(request, '__dict__', {})
- if 'override_renderer' in attrs:
- # renderer overridden by newrequest event or other
- renderer_name = attrs.pop('override_renderer')
- renderer = renderers.RendererHelper(
- name=renderer_name,
- package=self.kw.get('package'),
- registry=registry)
- else:
- renderer = view_renderer.clone()
-
- if '__view__' in attrs:
- view_inst = attrs.pop('__view__')
- else:
- view_inst = getattr(view, '__original_view__', view)
- response = renderer.render_view(request, result, view_inst,
- context)
- return response
-
- return rendered_view
-
- def _response_resolved_view(self, view):
- registry = self.registry
-
- def viewresult_to_response(context, request):
- result = view(context, request)
- if result.__class__ is Response: # common case
- response = result
- else:
- response = registry.queryAdapterOrSelf(result, IResponse)
- if response is None:
- if result is None:
- append = (' You may have forgotten to return a value '
- 'from the view callable.')
- elif isinstance(result, dict):
- append = (' You may have forgotten to define a '
- 'renderer in the view configuration.')
- else:
- append = ''
-
- msg = ('Could not convert return value of the view '
- 'callable %s into a response object. '
- 'The value returned was %r.' + append)
-
- raise ValueError(msg % (view_description(view), result))
-
- return response
-
- return viewresult_to_response
-
- @wraps_view
- def decorated_view(self, view):
- decorator = self.kw.get('decorator')
- if decorator is None:
- return view
- return decorator(view)
-
-
-@implementer(IViewMapper)
-@provider(IViewMapperFactory)
-class DefaultViewMapper(object):
- def __init__(self, **kw):
- self.attr = kw.get('attr')
-
- def __call__(self, view):
- if is_unbound_method(view) and self.attr is None:
- raise ConfigurationError((
- 'Unbound method calls are not supported, please set the class '
- 'as your `view` and the method as your `attr`'
- ))
-
- if inspect.isclass(view):
- view = self.map_class(view)
- else:
- view = self.map_nonclass(view)
- return view
-
- def map_class(self, view):
- ronly = requestonly(view, self.attr)
- if ronly:
- mapped_view = self.map_class_requestonly(view)
- else:
- mapped_view = self.map_class_native(view)
- mapped_view.__text__ = 'method %s of %s' % (
- self.attr or '__call__', object_description(view))
- return mapped_view
-
- def map_nonclass(self, view):
- # We do more work here than appears necessary to avoid wrapping the
- # view unless it actually requires wrapping (to avoid function call
- # overhead).
- mapped_view = view
- ronly = requestonly(view, self.attr)
- if ronly:
- mapped_view = self.map_nonclass_requestonly(view)
- elif self.attr:
- mapped_view = self.map_nonclass_attr(view)
- if inspect.isroutine(mapped_view):
- # This branch will be true if the view is a function or a method.
- # We potentially mutate an unwrapped object here if it's a
- # function. We do this to avoid function call overhead of
- # injecting another wrapper. However, we must wrap if the
- # function is a bound method because we can't set attributes on a
- # bound method.
- if is_bound_method(view):
- _mapped_view = mapped_view
- def mapped_view(context, request):
- return _mapped_view(context, request)
- if self.attr is not None:
- mapped_view.__text__ = 'attr %s of %s' % (
- self.attr, object_description(view))
- else:
- mapped_view.__text__ = object_description(view)
- return mapped_view
-
- def map_class_requestonly(self, view):
- # its a class that has an __init__ which only accepts request
- attr = self.attr
- def _class_requestonly_view(context, request):
- inst = view(request)
- request.__view__ = inst
- if attr is None:
- response = inst()
- else:
- response = getattr(inst, attr)()
- return response
- return _class_requestonly_view
-
- def map_class_native(self, view):
- # its a class that has an __init__ which accepts both context and
- # request
- attr = self.attr
- def _class_view(context, request):
- inst = view(context, request)
- request.__view__ = inst
- if attr is None:
- response = inst()
- else:
- response = getattr(inst, attr)()
- return response
- return _class_view
-
- def map_nonclass_requestonly(self, view):
- # its a function that has a __call__ which accepts only a single
- # request argument
- attr = self.attr
- def _requestonly_view(context, request):
- if attr is None:
- response = view(request)
- else:
- response = getattr(view, attr)(request)
- return response
- return _requestonly_view
-
- def map_nonclass_attr(self, view):
- # its a function that has a __call__ which accepts both context and
- # request, but still has an attr
- def _attr_view(context, request):
- response = getattr(view, self.attr)(context, request)
- return response
- return _attr_view
-
-def requestonly(view, attr=None):
- return takes_one_arg(view, attr=attr, argname='request')
+DefaultViewMapper = DefaultViewMapper # bw-compat
+preserve_view_attrs = preserve_view_attrs # bw-compat
+requestonly = requestonly # bw-compat
+view_description = view_description # bw-compat
@implementer(IMultiView)
class MultiView(object):
@@ -642,8 +213,7 @@ class ViewsConfiguratorMixin(object):
http_cache=None,
match_param=None,
check_csrf=None,
- **predicates
- ):
+ **view_options):
""" Add a :term:`view configuration` to the current
configuration state. Arguments to ``add_view`` are broken
down below into *predicate* arguments and *non-predicate*
@@ -1084,17 +654,23 @@ class ViewsConfiguratorMixin(object):
obsoletes this argument, but it is kept around for backwards
compatibility.
- predicates
+ view_options:
- Pass a key/value pair here to use a third-party predicate
- registered via
- :meth:`pyramid.config.Configurator.add_view_predicate`. More than
- one key/value pair can be used at the same time. See
+ Pass a key/value pair here to use a third-party predicate or set a
+ value for a view deriver. See
+ :meth:`pyramid.config.Configurator.add_view_predicate` and
+ :meth:`pyramid.config.Configurator.add_view_deriver`. See
:ref:`view_and_route_predicates` for more information about
- third-party predicates.
+ third-party predicates and :ref:`view_derivers` for information
+ about view derivers.
.. versionadded: 1.4a1
+ .. versionchanged: 1.7
+
+ Support setting view deriver options. Previously, only custom
+ view predicate values could be supplied.
+
"""
if custom_predicates:
warnings.warn(
@@ -1117,7 +693,7 @@ class ViewsConfiguratorMixin(object):
def combine(*decorators):
def decorated(view_callable):
- # reversed() is allows a more natural ordering in the api
+ # reversed() allows a more natural ordering in the api
for decorator in reversed(decorators):
view_callable = decorator(view_callable)
return view_callable
@@ -1160,33 +736,45 @@ class ViewsConfiguratorMixin(object):
accept = accept.lower()
introspectables = []
- pvals = predicates.copy()
- pvals.update(
- dict(
- xhr=xhr,
- request_method=request_method,
- path_info=path_info,
- request_param=request_param,
- header=header,
- accept=accept,
- containment=containment,
- request_type=request_type,
- match_param=match_param,
- check_csrf=check_csrf,
- custom=predvalseq(custom_predicates),
- )
- )
+ ovals = view_options.copy()
+ ovals.update(dict(
+ xhr=xhr,
+ request_method=request_method,
+ path_info=path_info,
+ request_param=request_param,
+ header=header,
+ accept=accept,
+ containment=containment,
+ request_type=request_type,
+ match_param=match_param,
+ check_csrf=check_csrf,
+ custom=predvalseq(custom_predicates),
+ ))
def discrim_func():
# We need to defer the discriminator until we know what the phash
# is. It can't be computed any sooner because thirdparty
- # predicates may not yet exist when add_view is called.
+ # predicates/view derivers may not yet exist when add_view is
+ # called.
+ valid_predicates = predlist.names()
+ pvals = {}
+ dvals = {}
+
+ for (k, v) in ovals.items():
+ if k in valid_predicates:
+ pvals[k] = v
+ else:
+ dvals[k] = v
+
+ self._check_view_options(**dvals)
+
order, preds, phash = predlist.make(self, **pvals)
+
view_intr.update({
'phash': phash,
'order': order,
- 'predicates': preds
- })
+ 'predicates': preds,
+ })
return ('view', context, name, route_name, phash)
discriminator = Deferred(discrim_func)
@@ -1203,26 +791,25 @@ class ViewsConfiguratorMixin(object):
discriminator,
view_desc,
'view')
- view_intr.update(
- dict(name=name,
- context=context,
- containment=containment,
- request_param=request_param,
- request_methods=request_method,
- route_name=route_name,
- attr=attr,
- xhr=xhr,
- accept=accept,
- header=header,
- path_info=path_info,
- match_param=match_param,
- check_csrf=check_csrf,
- callable=view,
- mapper=mapper,
- decorator=decorator,
- )
- )
- view_intr.update(**predicates)
+ view_intr.update(dict(
+ name=name,
+ context=context,
+ containment=containment,
+ request_param=request_param,
+ request_methods=request_method,
+ route_name=route_name,
+ attr=attr,
+ xhr=xhr,
+ accept=accept,
+ header=header,
+ path_info=path_info,
+ match_param=match_param,
+ check_csrf=check_csrf,
+ callable=view,
+ mapper=mapper,
+ decorator=decorator,
+ ))
+ view_intr.update(view_options)
introspectables.append(view_intr)
predlist = self.get_predlist('view')
@@ -1258,23 +845,23 @@ class ViewsConfiguratorMixin(object):
phash = view_intr['phash']
# __no_permission_required__ handled by _secure_view
- deriver = ViewDeriver(
- registry=self.registry,
+ derived_view = self._derive_view(
+ view,
permission=permission,
predicates=preds,
attr=attr,
+ context=context,
renderer=renderer,
wrapper_viewname=wrapper,
viewname=name,
accept=accept,
order=order,
phash=phash,
- package=self.package,
- mapper=mapper,
decorator=decorator,
+ mapper=mapper,
http_cache=http_cache,
- )
- derived_view = deriver(view)
+ extra_options=ovals,
+ )
derived_view.__discriminator__ = lambda *arg: discriminator
# __discriminator__ is used by superdynamic systems
# that require it for introspection after manual view lookup;
@@ -1385,7 +972,7 @@ class ViewsConfiguratorMixin(object):
tmpl_intr is not None and
intrspc is not None and
intrspc.get('renderer factories', renderer_type) is not None
- ):
+ ):
# allow failure of registered template factories to be deferred
# until view execution, like other bad renderer factories; if
# we tried to relate this to an existing renderer factory
@@ -1427,12 +1014,35 @@ class ViewsConfiguratorMixin(object):
permission,
permission,
'permission'
- )
+ )
perm_intr['value'] = permission
perm_intr.relate('views', discriminator)
introspectables.append(perm_intr)
self.action(discriminator, register, introspectables=introspectables)
+ def _check_view_options(self, **kw):
+ # we only need to validate deriver options because the predicates
+ # were checked by the predlist
+ derivers = self.registry.getUtility(IViewDerivers)
+ for deriver in derivers.values():
+ for opt in getattr(deriver, 'options', []):
+ kw.pop(opt, None)
+ if kw:
+ raise ConfigurationError('Unknown view options: %s' % (kw,))
+
+ def _apply_view_derivers(self, info):
+ d = pyramid.viewderivers
+
+ # These derivers are not really derivers and so have fixed order
+ outer_derivers = [('attr_wrapped_view', d.attr_wrapped_view),
+ ('predicated_view', d.predicated_view)]
+
+ view = info.original_view
+ derivers = self.registry.getUtility(IViewDerivers)
+ for name, deriver in reversed(outer_derivers + derivers.sorted()):
+ view = wraps_view(deriver)(view, info)
+ return view
+
@action_method
def add_view_predicate(self, name, factory, weighs_more_than=None,
weighs_less_than=None):
@@ -1459,7 +1069,7 @@ class ViewsConfiguratorMixin(object):
factory,
weighs_more_than=weighs_more_than,
weighs_less_than=weighs_less_than
- )
+ )
def add_default_view_predicates(self):
p = pyramid.config.predicates
@@ -1477,9 +1087,119 @@ class ViewsConfiguratorMixin(object):
('physical_path', p.PhysicalPathPredicate),
('effective_principals', p.EffectivePrincipalsPredicate),
('custom', p.CustomPredicate),
- ):
+ ):
self.add_view_predicate(name, factory)
+ @action_method
+ def add_view_deriver(self, deriver, name=None, under=None, over=None):
+ """
+ .. versionadded:: 1.7
+
+ Add a :term:`view deriver` to the view pipeline. View derivers are
+ a feature used by extension authors to wrap views in custom code
+ controllable by view-specific options.
+
+ ``deriver`` should be a callable conforming to the
+ :class:`pyramid.interfaces.IViewDeriver` interface.
+
+ ``name`` should be the name of the view deriver. There are no
+ restrictions on the name of a view deriver. If left unspecified, the
+ name will be constructed from the name of the ``deriver``.
+
+ The ``under`` and ``over`` options can be used to control the ordering
+ of view derivers by providing hints about where in the view pipeline
+ the deriver is used. Each option may be a string or a list of strings.
+ At least one view deriver in each, the over and under directions, must
+ exist to fully satisfy the constraints.
+
+ ``under`` means closer to the user-defined :term:`view callable`,
+ and ``over`` means closer to view pipeline ingress.
+
+ The default value for ``over`` is ``rendered_view`` and ``under`` is
+ ``decorated_view``. This places the deriver somewhere between the two
+ in the view pipeline. If the deriver should be placed elsewhere in the
+ pipeline, such as above ``decorated_view``, then you MUST also specify
+ ``under`` to something earlier in the order, or a
+ ``CyclicDependencyError`` will be raised when trying to sort the
+ derivers.
+
+ See :ref:`view_derivers` for more information.
+
+ """
+ deriver = self.maybe_dotted(deriver)
+
+ if name is None:
+ name = deriver.__name__
+
+ if name in (INGRESS, VIEW):
+ raise ConfigurationError('%s is a reserved view deriver name'
+ % name)
+
+ if under is None:
+ under = 'decorated_view'
+
+ if over is None:
+ over = 'rendered_view'
+
+ over = as_sorted_tuple(over)
+ under = as_sorted_tuple(under)
+
+ if INGRESS in over:
+ raise ConfigurationError('%s cannot be over INGRESS' % name)
+
+ # ensure everything is always over mapped_view
+ if VIEW in over and name != 'mapped_view':
+ over = as_sorted_tuple(over + ('mapped_view',))
+
+ if VIEW in under:
+ raise ConfigurationError('%s cannot be under VIEW' % name)
+ if 'mapped_view' in under:
+ raise ConfigurationError('%s cannot be under "mapped_view"' % name)
+
+ discriminator = ('view deriver', name)
+ intr = self.introspectable(
+ 'view derivers',
+ name,
+ name,
+ 'view deriver')
+ intr['name'] = name
+ intr['deriver'] = deriver
+ intr['under'] = under
+ intr['over'] = over
+ def register():
+ derivers = self.registry.queryUtility(IViewDerivers)
+ if derivers is None:
+ derivers = TopologicalSorter(
+ default_before=None,
+ default_after=INGRESS,
+ first=INGRESS,
+ last=VIEW,
+ )
+ self.registry.registerUtility(derivers, IViewDerivers)
+ derivers.add(name, deriver, before=over, after=under)
+ self.action(discriminator, register, introspectables=(intr,),
+ order=PHASE1_CONFIG) # must be registered before add_view
+
+ def add_default_view_derivers(self):
+ d = pyramid.viewderivers
+ derivers = [
+ ('secured_view', d.secured_view),
+ ('owrapped_view', d.owrapped_view),
+ ('http_cached_view', d.http_cached_view),
+ ('decorated_view', d.decorated_view),
+ ('rendered_view', d.rendered_view),
+ ('mapped_view', d.mapped_view),
+ ]
+ last = INGRESS
+ for name, deriver in derivers:
+ self.add_view_deriver(
+ deriver,
+ name=name,
+ under=last,
+ over=VIEW,
+ )
+ last = name
+
def derive_view(self, view, attr=None, renderer=None):
"""
Create a :term:`view callable` using the function, instance,
@@ -1563,7 +1283,8 @@ class ViewsConfiguratorMixin(object):
attr=None, renderer=None, wrapper_viewname=None,
viewname=None, accept=None, order=MAX_ORDER,
phash=DEFAULT_PHASH, decorator=None,
- mapper=None, http_cache=None):
+ mapper=None, http_cache=None, context=None,
+ extra_options=None):
view = self.maybe_dotted(view)
mapper = self.maybe_dotted(mapper)
if isinstance(renderer, string_types):
@@ -1578,22 +1299,36 @@ class ViewsConfiguratorMixin(object):
package=self.package,
registry=self.registry)
- deriver = ViewDeriver(registry=self.registry,
- permission=permission,
- predicates=predicates,
- attr=attr,
- renderer=renderer,
- wrapper_viewname=wrapper_viewname,
- viewname=viewname,
- accept=accept,
- order=order,
- phash=phash,
- package=self.package,
- mapper=mapper,
- decorator=decorator,
- http_cache=http_cache)
-
- return deriver(view)
+ options = dict(
+ view=view,
+ context=context,
+ permission=permission,
+ attr=attr,
+ renderer=renderer,
+ wrapper=wrapper_viewname,
+ name=viewname,
+ accept=accept,
+ mapper=mapper,
+ decorator=decorator,
+ http_cache=http_cache,
+ )
+ if extra_options:
+ options.update(extra_options)
+
+ info = ViewDeriverInfo(
+ view=view,
+ registry=self.registry,
+ package=self.package,
+ predicates=predicates,
+ options=options,
+ )
+
+ # order and phash are only necessary for the predicated view and
+ # are not really view deriver options
+ info.order = order
+ info.phash = phash
+
+ return self._apply_view_derivers(info)
@viewdefaults
@action_method
@@ -1616,8 +1351,8 @@ class ViewsConfiguratorMixin(object):
decorator=None,
mapper=None,
match_param=None,
- **predicates
- ):
+ **view_options
+ ):
""" Add a forbidden view to the current configuration state. The
view will be called when Pyramid or application code raises a
:exc:`pyramid.httpexceptions.HTTPForbidden` exception and the set of
@@ -1646,11 +1381,11 @@ class ViewsConfiguratorMixin(object):
.. versionadded:: 1.3
"""
for arg in ('name', 'permission', 'context', 'for_', 'http_cache'):
- if arg in predicates:
+ if arg in view_options:
raise ConfigurationError(
'%s may not be used as an argument to add_forbidden_view'
% arg
- )
+ )
if view is None:
view = default_exceptionresponse_view
@@ -1675,8 +1410,8 @@ class ViewsConfiguratorMixin(object):
permission=NO_PERMISSION_REQUIRED,
attr=attr,
renderer=renderer,
- )
- settings.update(predicates)
+ )
+ settings.update(view_options)
return self.add_view(**settings)
set_forbidden_view = add_forbidden_view # deprecated sorta-bw-compat alias
@@ -1703,8 +1438,8 @@ class ViewsConfiguratorMixin(object):
mapper=None,
match_param=None,
append_slash=False,
- **predicates
- ):
+ **view_options
+ ):
""" Add a default Not Found View to the current configuration state.
The view will be called when Pyramid or application code raises an
:exc:`pyramid.httpexceptions.HTTPNotFound` exception (e.g. when a
@@ -1758,11 +1493,11 @@ class ViewsConfiguratorMixin(object):
.. versionadded:: 1.3
"""
for arg in ('name', 'permission', 'context', 'for_', 'http_cache'):
- if arg in predicates:
+ if arg in view_options:
raise ConfigurationError(
'%s may not be used as an argument to add_notfound_view'
% arg
- )
+ )
if view is None:
view = default_exceptionresponse_view
@@ -1785,8 +1520,8 @@ class ViewsConfiguratorMixin(object):
match_param=match_param,
route_name=route_name,
permission=NO_PERMISSION_REQUIRED,
- )
- settings.update(predicates)
+ )
+ settings.update(view_options)
if append_slash:
view = self._derive_view(view, attr=attr, renderer=renderer)
if IResponse.implementedBy(append_slash):
@@ -1980,8 +1715,20 @@ def isexception(o):
return (
isinstance(o, Exception) or
(inspect.isclass(o) and (issubclass(o, Exception)))
- )
+ )
+
+@implementer(IViewDeriverInfo)
+class ViewDeriverInfo(object):
+ def __init__(self, view, registry, package, predicates, options):
+ self.original_view = view
+ self.registry = registry
+ self.package = package
+ self.predicates = predicates or []
+ self.options = options or {}
+ @reify
+ def settings(self):
+ return self.registry.settings
@implementer(IStaticURLInfo)
class StaticURLInfo(object):
@@ -2066,7 +1813,7 @@ class StaticURLInfo(object):
# register a route using the computed view, permission, and
# pattern, plus any extras passed to us via add_static_view
- pattern = "%s*subpath" % name # name already ends with slash
+ pattern = "%s*subpath" % name # name already ends with slash
if config.route_prefix:
route_name = '__%s/%s' % (config.route_prefix, name)
else:
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index 9e5cbb6d3..64bb4b50c 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -1187,6 +1187,39 @@ class IJSONAdapter(Interface):
class IPredicateList(Interface):
""" Interface representing a predicate list """
+class IViewDeriver(Interface):
+ options = Attribute('A list of supported options to be passed to '
+ ':meth:`pyramid.config.Configurator.add_view`. '
+ 'This attribute is optional.')
+
+ def __call__(view, info):
+ """
+ Derive a new view from the supplied view.
+
+ View options, package information and registry are available on
+ ``info``, an instance of :class:`pyramid.interfaces.IViewDeriverInfo`.
+
+ The ``view`` is a callable accepting ``(context, request)``.
+
+ """
+
+class IViewDeriverInfo(Interface):
+ """ An object implementing this interface is passed to every
+ :term:`view deriver` during configuration."""
+ registry = Attribute('The "current" application registry when the '
+ 'view was created')
+ package = Attribute('The "current package" when the view '
+ 'configuration statement was found')
+ settings = Attribute('The deployment settings dictionary related '
+ 'to the current application')
+ options = Attribute('The view options passed to the view, including any '
+ 'default values that were not overriden')
+ predicates = Attribute('The list of predicates active on the view')
+ original_view = Attribute('The original view object being wrapped')
+
+class IViewDerivers(Interface):
+ """ Interface for view derivers list """
+
class ICacheBuster(Interface):
"""
A cache buster modifies the URL generation machinery for
diff --git a/pyramid/paster.py b/pyramid/paster.py
index 967543849..3916be8f0 100644
--- a/pyramid/paster.py
+++ b/pyramid/paster.py
@@ -52,25 +52,29 @@ def get_appsettings(config_uri, name=None, options=None, appconfig=appconfig):
relative_to=here_dir,
global_conf=options)
-def setup_logging(config_uri, fileConfig=fileConfig,
+def setup_logging(config_uri, global_conf=None,
+ fileConfig=fileConfig,
configparser=configparser):
"""
- Set up logging via the logging module's fileConfig function with the
- filename specified via ``config_uri`` (a string in the form
+ Set up logging via :func:`logging.config.fileConfig` with the filename
+ specified via ``config_uri`` (a string in the form
``filename#sectionname``).
ConfigParser defaults are specified for the special ``__file__``
and ``here`` variables, similar to PasteDeploy config loading.
+ Extra defaults can optionally be specified as a dict in ``global_conf``.
"""
path, _ = _getpathsec(config_uri, None)
parser = configparser.ConfigParser()
parser.read([path])
if parser.has_section('loggers'):
config_file = os.path.abspath(path)
- return fileConfig(
- config_file,
- dict(__file__=config_file, here=os.path.dirname(config_file))
- )
+ full_global_conf = dict(
+ __file__=config_file,
+ here=os.path.dirname(config_file))
+ if global_conf:
+ full_global_conf.update(global_conf)
+ return fileConfig(config_file, full_global_conf)
def _getpathsec(config_uri, name):
if '#' in config_uri:
diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py
index 431afe6f4..74bda1dce 100644
--- a/pyramid/scripts/pserve.py
+++ b/pyramid/scripts/pserve.py
@@ -362,7 +362,7 @@ a real process manager for your processes like Systemd, Circus, or Supervisor.
log_fn = None
if log_fn:
log_fn = os.path.join(base, log_fn)
- setup_logging(log_fn)
+ setup_logging(log_fn, global_conf=vars)
server = self.loadserver(server_spec, name=server_name,
relative_to=base, global_conf=vars)
diff --git a/pyramid/testing.py b/pyramid/testing.py
index 3cb5d17b9..ec06fe379 100644
--- a/pyramid/testing.py
+++ b/pyramid/testing.py
@@ -476,6 +476,7 @@ def setUp(registry=None, request=None, hook_zca=True, autocommit=True,
# method.
config.add_default_renderers()
config.add_default_view_predicates()
+ config.add_default_view_derivers()
config.add_default_route_predicates()
config.commit()
global have_zca
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index e89d43c9a..b2513c42c 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -2467,1135 +2467,7 @@ class TestMultiView(unittest.TestCase):
response = mv(context, request)
self.assertEqual(response, expected_response)
-class TestViewDeriver(unittest.TestCase):
- def setUp(self):
- self.config = testing.setUp()
-
- def tearDown(self):
- self.config = None
-
- def _makeOne(self, **kw):
- kw['registry'] = self.config.registry
- from pyramid.config.views import ViewDeriver
- return ViewDeriver(**kw)
-
- def _makeRequest(self):
- request = DummyRequest()
- request.registry = self.config.registry
- return request
-
- def _registerLogger(self):
- from pyramid.interfaces import IDebugLogger
- logger = DummyLogger()
- self.config.registry.registerUtility(logger, IDebugLogger)
- return logger
-
- def _registerSecurityPolicy(self, permissive):
- from pyramid.interfaces import IAuthenticationPolicy
- from pyramid.interfaces import IAuthorizationPolicy
- policy = DummySecurityPolicy(permissive)
- self.config.registry.registerUtility(policy, IAuthenticationPolicy)
- self.config.registry.registerUtility(policy, IAuthorizationPolicy)
-
- def test_function_returns_non_adaptable(self):
- def view(request):
- return None
- deriver = self._makeOne()
- result = deriver(view)
- self.assertFalse(result is view)
- try:
- result(None, None)
- except ValueError as e:
- self.assertEqual(
- e.args[0],
- 'Could not convert return value of the view callable function '
- 'pyramid.tests.test_config.test_views.view into a response '
- 'object. The value returned was None. You may have forgotten '
- 'to return a value from the view callable.'
- )
- else: # pragma: no cover
- raise AssertionError
-
- def test_function_returns_non_adaptable_dict(self):
- def view(request):
- return {'a':1}
- deriver = self._makeOne()
- result = deriver(view)
- self.assertFalse(result is view)
- try:
- result(None, None)
- except ValueError as e:
- self.assertEqual(
- e.args[0],
- "Could not convert return value of the view callable function "
- "pyramid.tests.test_config.test_views.view into a response "
- "object. The value returned was {'a': 1}. You may have "
- "forgotten to define a renderer in the view configuration."
- )
- else: # pragma: no cover
- raise AssertionError
-
- def test_instance_returns_non_adaptable(self):
- class AView(object):
- def __call__(self, request):
- return None
- view = AView()
- deriver = self._makeOne()
- result = deriver(view)
- self.assertFalse(result is view)
- try:
- result(None, None)
- except ValueError as e:
- msg = e.args[0]
- self.assertTrue(msg.startswith(
- 'Could not convert return value of the view callable object '
- '<pyramid.tests.test_config.test_views.'))
- self.assertTrue(msg.endswith(
- '> into a response object. The value returned was None. You '
- 'may have forgotten to return a value from the view callable.'))
- else: # pragma: no cover
- raise AssertionError
-
- def test_function_returns_true_Response_no_renderer(self):
- from pyramid.response import Response
- r = Response('Hello')
- def view(request):
- return r
- deriver = self._makeOne()
- result = deriver(view)
- self.assertFalse(result is view)
- response = result(None, None)
- self.assertEqual(response, r)
-
- def test_function_returns_true_Response_with_renderer(self):
- from pyramid.response import Response
- r = Response('Hello')
- def view(request):
- return r
- renderer = object()
- deriver = self._makeOne(renderer=renderer)
- result = deriver(view)
- self.assertFalse(result is view)
- response = result(None, None)
- self.assertEqual(response, r)
-
- def test_requestonly_default_method_returns_non_adaptable(self):
- request = DummyRequest()
- class AView(object):
- def __init__(self, request):
- pass
- def __call__(self):
- return None
- deriver = self._makeOne()
- result = deriver(AView)
- self.assertFalse(result is AView)
- try:
- result(None, request)
- except ValueError as e:
- self.assertEqual(
- e.args[0],
- 'Could not convert return value of the view callable '
- 'method __call__ of '
- 'class pyramid.tests.test_config.test_views.AView into a '
- 'response object. The value returned was None. You may have '
- 'forgotten to return a value from the view callable.'
- )
- else: # pragma: no cover
- raise AssertionError
-
- def test_requestonly_nondefault_method_returns_non_adaptable(self):
- request = DummyRequest()
- class AView(object):
- def __init__(self, request):
- pass
- def theviewmethod(self):
- return None
- deriver = self._makeOne(attr='theviewmethod')
- result = deriver(AView)
- self.assertFalse(result is AView)
- try:
- result(None, request)
- except ValueError as e:
- self.assertEqual(
- e.args[0],
- 'Could not convert return value of the view callable '
- 'method theviewmethod of '
- 'class pyramid.tests.test_config.test_views.AView into a '
- 'response object. The value returned was None. You may have '
- 'forgotten to return a value from the view callable.'
- )
- else: # pragma: no cover
- raise AssertionError
-
- def test_requestonly_function(self):
- response = DummyResponse()
- def view(request):
- return response
- deriver = self._makeOne()
- result = deriver(view)
- self.assertFalse(result is view)
- self.assertEqual(result(None, None), response)
-
- def test_requestonly_function_with_renderer(self):
- response = DummyResponse()
- class moo(object):
- def render_view(inself, req, resp, view_inst, ctx):
- self.assertEqual(req, request)
- self.assertEqual(resp, 'OK')
- self.assertEqual(view_inst, view)
- self.assertEqual(ctx, context)
- return response
- def clone(self):
- return self
- def view(request):
- return 'OK'
- deriver = self._makeOne(renderer=moo())
- result = deriver(view)
- self.assertFalse(result.__wraps__ is view)
- request = self._makeRequest()
- context = testing.DummyResource()
- self.assertEqual(result(context, request), response)
-
- def test_requestonly_function_with_renderer_request_override(self):
- def moo(info):
- def inner(value, system):
- self.assertEqual(value, 'OK')
- self.assertEqual(system['request'], request)
- self.assertEqual(system['context'], context)
- return b'moo'
- return inner
- def view(request):
- return 'OK'
- self.config.add_renderer('moo', moo)
- deriver = self._makeOne(renderer='string')
- result = deriver(view)
- self.assertFalse(result is view)
- request = self._makeRequest()
- request.override_renderer = 'moo'
- context = testing.DummyResource()
- self.assertEqual(result(context, request).body, b'moo')
-
- def test_requestonly_function_with_renderer_request_has_view(self):
- response = DummyResponse()
- class moo(object):
- def render_view(inself, req, resp, view_inst, ctx):
- self.assertEqual(req, request)
- self.assertEqual(resp, 'OK')
- self.assertEqual(view_inst, 'view')
- self.assertEqual(ctx, context)
- return response
- def clone(self):
- return self
- def view(request):
- return 'OK'
- deriver = self._makeOne(renderer=moo())
- result = deriver(view)
- self.assertFalse(result.__wraps__ is view)
- request = self._makeRequest()
- request.__view__ = 'view'
- context = testing.DummyResource()
- r = result(context, request)
- self.assertEqual(r, response)
- self.assertFalse(hasattr(request, '__view__'))
-
- def test_class_without_attr(self):
- response = DummyResponse()
- class View(object):
- def __init__(self, request):
- pass
- def __call__(self):
- return response
- deriver = self._makeOne()
- result = deriver(View)
- request = self._makeRequest()
- self.assertEqual(result(None, request), response)
- self.assertEqual(request.__view__.__class__, View)
-
- def test_class_with_attr(self):
- response = DummyResponse()
- class View(object):
- def __init__(self, request):
- pass
- def another(self):
- return response
- deriver = self._makeOne(attr='another')
- result = deriver(View)
- request = self._makeRequest()
- self.assertEqual(result(None, request), response)
- self.assertEqual(request.__view__.__class__, View)
-
- def test_as_function_context_and_request(self):
- def view(context, request):
- return 'OK'
- deriver = self._makeOne()
- result = deriver(view)
- self.assertTrue(result.__wraps__ is view)
- self.assertFalse(hasattr(result, '__call_permissive__'))
- self.assertEqual(view(None, None), 'OK')
-
- def test_as_function_requestonly(self):
- response = DummyResponse()
- def view(request):
- return response
- deriver = self._makeOne()
- result = deriver(view)
- self.assertFalse(result is view)
- self.assertEqual(view.__module__, result.__module__)
- self.assertEqual(view.__doc__, result.__doc__)
- self.assertEqual(view.__name__, result.__name__)
- self.assertFalse(hasattr(result, '__call_permissive__'))
- self.assertEqual(result(None, None), response)
-
- def test_as_newstyle_class_context_and_request(self):
- response = DummyResponse()
- class view(object):
- def __init__(self, context, request):
- pass
- def __call__(self):
- return response
- deriver = self._makeOne()
- result = deriver(view)
- self.assertFalse(result is view)
- self.assertEqual(view.__module__, result.__module__)
- self.assertEqual(view.__doc__, result.__doc__)
- self.assertEqual(view.__name__, result.__name__)
- self.assertFalse(hasattr(result, '__call_permissive__'))
- request = self._makeRequest()
- self.assertEqual(result(None, request), response)
- self.assertEqual(request.__view__.__class__, view)
-
- def test_as_newstyle_class_requestonly(self):
- response = DummyResponse()
- class view(object):
- def __init__(self, context, request):
- pass
- def __call__(self):
- return response
- deriver = self._makeOne()
- result = deriver(view)
- self.assertFalse(result is view)
- self.assertEqual(view.__module__, result.__module__)
- self.assertEqual(view.__doc__, result.__doc__)
- self.assertEqual(view.__name__, result.__name__)
- self.assertFalse(hasattr(result, '__call_permissive__'))
- request = self._makeRequest()
- self.assertEqual(result(None, request), response)
- self.assertEqual(request.__view__.__class__, view)
-
- def test_as_oldstyle_class_context_and_request(self):
- response = DummyResponse()
- class view:
- def __init__(self, context, request):
- pass
- def __call__(self):
- return response
- deriver = self._makeOne()
- result = deriver(view)
- self.assertFalse(result is view)
- self.assertEqual(view.__module__, result.__module__)
- self.assertEqual(view.__doc__, result.__doc__)
- self.assertEqual(view.__name__, result.__name__)
- self.assertFalse(hasattr(result, '__call_permissive__'))
- request = self._makeRequest()
- self.assertEqual(result(None, request), response)
- self.assertEqual(request.__view__.__class__, view)
-
- def test_as_oldstyle_class_requestonly(self):
- response = DummyResponse()
- class view:
- def __init__(self, context, request):
- pass
- def __call__(self):
- return response
- deriver = self._makeOne()
- result = deriver(view)
- self.assertFalse(result is view)
- self.assertEqual(view.__module__, result.__module__)
- self.assertEqual(view.__doc__, result.__doc__)
- self.assertEqual(view.__name__, result.__name__)
- self.assertFalse(hasattr(result, '__call_permissive__'))
- request = self._makeRequest()
- self.assertEqual(result(None, request), response)
- self.assertEqual(request.__view__.__class__, view)
-
- def test_as_instance_context_and_request(self):
- response = DummyResponse()
- class View:
- def __call__(self, context, request):
- return response
- view = View()
- deriver = self._makeOne()
- result = deriver(view)
- self.assertTrue(result.__wraps__ is view)
- self.assertFalse(hasattr(result, '__call_permissive__'))
- self.assertEqual(result(None, None), response)
-
- def test_as_instance_requestonly(self):
- response = DummyResponse()
- class View:
- def __call__(self, request):
- return response
- view = View()
- deriver = self._makeOne()
- result = deriver(view)
- self.assertFalse(result is view)
- self.assertEqual(view.__module__, result.__module__)
- self.assertEqual(view.__doc__, result.__doc__)
- self.assertTrue('test_views' in result.__name__)
- self.assertFalse(hasattr(result, '__call_permissive__'))
- self.assertEqual(result(None, None), response)
-
- def test_with_debug_authorization_no_authpol(self):
- response = DummyResponse()
- view = lambda *arg: response
- self.config.registry.settings = dict(
- debug_authorization=True, reload_templates=True)
- logger = self._registerLogger()
- deriver = self._makeOne(permission='view')
- result = deriver(view)
- self.assertEqual(view.__module__, result.__module__)
- self.assertEqual(view.__doc__, result.__doc__)
- self.assertEqual(view.__name__, result.__name__)
- self.assertFalse(hasattr(result, '__call_permissive__'))
- request = self._makeRequest()
- request.view_name = 'view_name'
- request.url = 'url'
- self.assertEqual(result(None, request), response)
- self.assertEqual(len(logger.messages), 1)
- self.assertEqual(logger.messages[0],
- "debug_authorization of url url (view name "
- "'view_name' against context None): Allowed "
- "(no authorization policy in use)")
-
- def test_with_debug_authorization_authn_policy_no_authz_policy(self):
- response = DummyResponse()
- view = lambda *arg: response
- self.config.registry.settings = dict(debug_authorization=True)
- from pyramid.interfaces import IAuthenticationPolicy
- policy = DummySecurityPolicy(False)
- self.config.registry.registerUtility(policy, IAuthenticationPolicy)
- logger = self._registerLogger()
- deriver = self._makeOne(permission='view')
- result = deriver(view)
- self.assertEqual(view.__module__, result.__module__)
- self.assertEqual(view.__doc__, result.__doc__)
- self.assertEqual(view.__name__, result.__name__)
- self.assertFalse(hasattr(result, '__call_permissive__'))
- request = self._makeRequest()
- request.view_name = 'view_name'
- request.url = 'url'
- self.assertEqual(result(None, request), response)
- self.assertEqual(len(logger.messages), 1)
- self.assertEqual(logger.messages[0],
- "debug_authorization of url url (view name "
- "'view_name' against context None): Allowed "
- "(no authorization policy in use)")
-
- def test_with_debug_authorization_authz_policy_no_authn_policy(self):
- response = DummyResponse()
- view = lambda *arg: response
- self.config.registry.settings = dict(debug_authorization=True)
- from pyramid.interfaces import IAuthorizationPolicy
- policy = DummySecurityPolicy(False)
- self.config.registry.registerUtility(policy, IAuthorizationPolicy)
- logger = self._registerLogger()
- deriver = self._makeOne(permission='view')
- result = deriver(view)
- self.assertEqual(view.__module__, result.__module__)
- self.assertEqual(view.__doc__, result.__doc__)
- self.assertEqual(view.__name__, result.__name__)
- self.assertFalse(hasattr(result, '__call_permissive__'))
- request = self._makeRequest()
- request.view_name = 'view_name'
- request.url = 'url'
- self.assertEqual(result(None, request), response)
- self.assertEqual(len(logger.messages), 1)
- self.assertEqual(logger.messages[0],
- "debug_authorization of url url (view name "
- "'view_name' against context None): Allowed "
- "(no authorization policy in use)")
-
- def test_with_debug_authorization_no_permission(self):
- response = DummyResponse()
- view = lambda *arg: response
- self.config.registry.settings = dict(
- debug_authorization=True, reload_templates=True)
- self._registerSecurityPolicy(True)
- logger = self._registerLogger()
- deriver = self._makeOne()
- result = deriver(view)
- self.assertEqual(view.__module__, result.__module__)
- self.assertEqual(view.__doc__, result.__doc__)
- self.assertEqual(view.__name__, result.__name__)
- self.assertFalse(hasattr(result, '__call_permissive__'))
- request = self._makeRequest()
- request.view_name = 'view_name'
- request.url = 'url'
- self.assertEqual(result(None, request), response)
- self.assertEqual(len(logger.messages), 1)
- self.assertEqual(logger.messages[0],
- "debug_authorization of url url (view name "
- "'view_name' against context None): Allowed ("
- "no permission registered)")
-
- def test_debug_auth_permission_authpol_permitted(self):
- response = DummyResponse()
- view = lambda *arg: response
- self.config.registry.settings = dict(
- debug_authorization=True, reload_templates=True)
- logger = self._registerLogger()
- self._registerSecurityPolicy(True)
- deriver = self._makeOne(permission='view')
- result = deriver(view)
- self.assertEqual(view.__module__, result.__module__)
- self.assertEqual(view.__doc__, result.__doc__)
- self.assertEqual(view.__name__, result.__name__)
- self.assertEqual(result.__call_permissive__.__wraps__, view)
- request = self._makeRequest()
- request.view_name = 'view_name'
- request.url = 'url'
- self.assertEqual(result(None, request), response)
- self.assertEqual(len(logger.messages), 1)
- self.assertEqual(logger.messages[0],
- "debug_authorization of url url (view name "
- "'view_name' against context None): True")
-
- def test_debug_auth_permission_authpol_permitted_no_request(self):
- response = DummyResponse()
- view = lambda *arg: response
- self.config.registry.settings = dict(
- debug_authorization=True, reload_templates=True)
- logger = self._registerLogger()
- self._registerSecurityPolicy(True)
- deriver = self._makeOne(permission='view')
- result = deriver(view)
- self.assertEqual(view.__module__, result.__module__)
- self.assertEqual(view.__doc__, result.__doc__)
- self.assertEqual(view.__name__, result.__name__)
- self.assertEqual(result.__call_permissive__.__wraps__, view)
- self.assertEqual(result(None, None), response)
- self.assertEqual(len(logger.messages), 1)
- self.assertEqual(logger.messages[0],
- "debug_authorization of url None (view name "
- "None against context None): True")
-
- def test_debug_auth_permission_authpol_denied(self):
- from pyramid.httpexceptions import HTTPForbidden
- response = DummyResponse()
- view = lambda *arg: response
- self.config.registry.settings = dict(
- debug_authorization=True, reload_templates=True)
- logger = self._registerLogger()
- self._registerSecurityPolicy(False)
- deriver = self._makeOne(permission='view')
- result = deriver(view)
- self.assertEqual(view.__module__, result.__module__)
- self.assertEqual(view.__doc__, result.__doc__)
- self.assertEqual(view.__name__, result.__name__)
- self.assertEqual(result.__call_permissive__.__wraps__, view)
- request = self._makeRequest()
- request.view_name = 'view_name'
- request.url = 'url'
- self.assertRaises(HTTPForbidden, result, None, request)
- self.assertEqual(len(logger.messages), 1)
- self.assertEqual(logger.messages[0],
- "debug_authorization of url url (view name "
- "'view_name' against context None): False")
-
- def test_debug_auth_permission_authpol_denied2(self):
- view = lambda *arg: 'OK'
- self.config.registry.settings = dict(
- debug_authorization=True, reload_templates=True)
- self._registerLogger()
- self._registerSecurityPolicy(False)
- deriver = self._makeOne(permission='view')
- result = deriver(view)
- self.assertEqual(view.__module__, result.__module__)
- self.assertEqual(view.__doc__, result.__doc__)
- self.assertEqual(view.__name__, result.__name__)
- request = self._makeRequest()
- request.view_name = 'view_name'
- request.url = 'url'
- permitted = result.__permitted__(None, None)
- self.assertEqual(permitted, False)
-
- def test_debug_auth_permission_authpol_overridden(self):
- from pyramid.security import NO_PERMISSION_REQUIRED
- response = DummyResponse()
- view = lambda *arg: response
- self.config.registry.settings = dict(
- debug_authorization=True, reload_templates=True)
- logger = self._registerLogger()
- self._registerSecurityPolicy(False)
- deriver = self._makeOne(permission=NO_PERMISSION_REQUIRED)
- result = deriver(view)
- self.assertEqual(view.__module__, result.__module__)
- self.assertEqual(view.__doc__, result.__doc__)
- self.assertEqual(view.__name__, result.__name__)
- self.assertFalse(hasattr(result, '__call_permissive__'))
- request = self._makeRequest()
- request.view_name = 'view_name'
- request.url = 'url'
- self.assertEqual(result(None, request), response)
- self.assertEqual(len(logger.messages), 1)
- self.assertEqual(logger.messages[0],
- "debug_authorization of url url (view name "
- "'view_name' against context None): "
- "Allowed (NO_PERMISSION_REQUIRED)")
-
- def test_secured_view_authn_policy_no_authz_policy(self):
- response = DummyResponse()
- view = lambda *arg: response
- self.config.registry.settings = {}
- from pyramid.interfaces import IAuthenticationPolicy
- policy = DummySecurityPolicy(False)
- self.config.registry.registerUtility(policy, IAuthenticationPolicy)
- deriver = self._makeOne(permission='view')
- result = deriver(view)
- self.assertEqual(view.__module__, result.__module__)
- self.assertEqual(view.__doc__, result.__doc__)
- self.assertEqual(view.__name__, result.__name__)
- self.assertFalse(hasattr(result, '__call_permissive__'))
- request = self._makeRequest()
- request.view_name = 'view_name'
- request.url = 'url'
- self.assertEqual(result(None, request), response)
-
- def test_secured_view_authz_policy_no_authn_policy(self):
- response = DummyResponse()
- view = lambda *arg: response
- self.config.registry.settings = {}
- from pyramid.interfaces import IAuthorizationPolicy
- policy = DummySecurityPolicy(False)
- self.config.registry.registerUtility(policy, IAuthorizationPolicy)
- deriver = self._makeOne(permission='view')
- result = deriver(view)
- self.assertEqual(view.__module__, result.__module__)
- self.assertEqual(view.__doc__, result.__doc__)
- self.assertEqual(view.__name__, result.__name__)
- self.assertFalse(hasattr(result, '__call_permissive__'))
- request = self._makeRequest()
- request.view_name = 'view_name'
- request.url = 'url'
- self.assertEqual(result(None, request), response)
-
- def test_secured_view_raises_forbidden_no_name(self):
- from pyramid.interfaces import IAuthenticationPolicy
- from pyramid.interfaces import IAuthorizationPolicy
- from pyramid.httpexceptions import HTTPForbidden
- response = DummyResponse()
- view = lambda *arg: response
- self.config.registry.settings = {}
- policy = DummySecurityPolicy(False)
- self.config.registry.registerUtility(policy, IAuthenticationPolicy)
- self.config.registry.registerUtility(policy, IAuthorizationPolicy)
- deriver = self._makeOne(permission='view')
- result = deriver(view)
- request = self._makeRequest()
- request.view_name = 'view_name'
- request.url = 'url'
- try:
- result(None, request)
- except HTTPForbidden as e:
- self.assertEqual(e.message,
- 'Unauthorized: <lambda> failed permission check')
- else: # pragma: no cover
- raise AssertionError
-
- def test_secured_view_raises_forbidden_with_name(self):
- from pyramid.interfaces import IAuthenticationPolicy
- from pyramid.interfaces import IAuthorizationPolicy
- from pyramid.httpexceptions import HTTPForbidden
- def myview(request): pass
- self.config.registry.settings = {}
- policy = DummySecurityPolicy(False)
- self.config.registry.registerUtility(policy, IAuthenticationPolicy)
- self.config.registry.registerUtility(policy, IAuthorizationPolicy)
- deriver = self._makeOne(permission='view')
- result = deriver(myview)
- request = self._makeRequest()
- request.view_name = 'view_name'
- request.url = 'url'
- try:
- result(None, request)
- except HTTPForbidden as e:
- self.assertEqual(e.message,
- 'Unauthorized: myview failed permission check')
- else: # pragma: no cover
- raise AssertionError
-
- def test_predicate_mismatch_view_has_no_name(self):
- from pyramid.exceptions import PredicateMismatch
- response = DummyResponse()
- view = lambda *arg: response
- def predicate1(context, request):
- return False
- predicate1.text = lambda *arg: 'text'
- deriver = self._makeOne(predicates=[predicate1])
- result = deriver(view)
- request = self._makeRequest()
- request.method = 'POST'
- try:
- result(None, None)
- except PredicateMismatch as e:
- self.assertEqual(e.detail,
- 'predicate mismatch for view <lambda> (text)')
- else: # pragma: no cover
- raise AssertionError
-
- def test_predicate_mismatch_view_has_name(self):
- from pyramid.exceptions import PredicateMismatch
- def myview(request): pass
- def predicate1(context, request):
- return False
- predicate1.text = lambda *arg: 'text'
- deriver = self._makeOne(predicates=[predicate1])
- result = deriver(myview)
- request = self._makeRequest()
- request.method = 'POST'
- try:
- result(None, None)
- except PredicateMismatch as e:
- self.assertEqual(e.detail,
- 'predicate mismatch for view myview (text)')
- else: # pragma: no cover
- raise AssertionError
-
- def test_predicate_mismatch_exception_has_text_in_detail(self):
- from pyramid.exceptions import PredicateMismatch
- def myview(request): pass
- def predicate1(context, request):
- return True
- predicate1.text = lambda *arg: 'pred1'
- def predicate2(context, request):
- return False
- predicate2.text = lambda *arg: 'pred2'
- deriver = self._makeOne(predicates=[predicate1, predicate2])
- result = deriver(myview)
- request = self._makeRequest()
- request.method = 'POST'
- try:
- result(None, None)
- except PredicateMismatch as e:
- self.assertEqual(e.detail,
- 'predicate mismatch for view myview (pred2)')
- else: # pragma: no cover
- raise AssertionError
-
- def test_with_predicates_all(self):
- response = DummyResponse()
- view = lambda *arg: response
- predicates = []
- def predicate1(context, request):
- predicates.append(True)
- return True
- def predicate2(context, request):
- predicates.append(True)
- return True
- deriver = self._makeOne(predicates=[predicate1, predicate2])
- result = deriver(view)
- request = self._makeRequest()
- request.method = 'POST'
- next = result(None, None)
- self.assertEqual(next, response)
- self.assertEqual(predicates, [True, True])
- def test_with_predicates_checker(self):
- view = lambda *arg: 'OK'
- predicates = []
- def predicate1(context, request):
- predicates.append(True)
- return True
- def predicate2(context, request):
- predicates.append(True)
- return True
- deriver = self._makeOne(predicates=[predicate1, predicate2])
- result = deriver(view)
- request = self._makeRequest()
- request.method = 'POST'
- next = result.__predicated__(None, None)
- self.assertEqual(next, True)
- self.assertEqual(predicates, [True, True])
-
- def test_with_predicates_notall(self):
- from pyramid.httpexceptions import HTTPNotFound
- view = lambda *arg: 'OK'
- predicates = []
- def predicate1(context, request):
- predicates.append(True)
- return True
- predicate1.text = lambda *arg: 'text'
- def predicate2(context, request):
- predicates.append(True)
- return False
- predicate2.text = lambda *arg: 'text'
- deriver = self._makeOne(predicates=[predicate1, predicate2])
- result = deriver(view)
- request = self._makeRequest()
- request.method = 'POST'
- self.assertRaises(HTTPNotFound, result, None, None)
- self.assertEqual(predicates, [True, True])
-
- def test_with_wrapper_viewname(self):
- from pyramid.response import Response
- from pyramid.interfaces import IView
- from pyramid.interfaces import IViewClassifier
- inner_response = Response('OK')
- def inner_view(context, request):
- return inner_response
- def outer_view(context, request):
- self.assertEqual(request.wrapped_response, inner_response)
- self.assertEqual(request.wrapped_body, inner_response.body)
- self.assertEqual(request.wrapped_view.__original_view__,
- inner_view)
- return Response(b'outer ' + request.wrapped_body)
- self.config.registry.registerAdapter(
- outer_view, (IViewClassifier, None, None), IView, 'owrap')
- deriver = self._makeOne(viewname='inner',
- wrapper_viewname='owrap')
- result = deriver(inner_view)
- self.assertFalse(result is inner_view)
- self.assertEqual(inner_view.__module__, result.__module__)
- self.assertEqual(inner_view.__doc__, result.__doc__)
- request = self._makeRequest()
- response = result(None, request)
- self.assertEqual(response.body, b'outer OK')
-
- def test_with_wrapper_viewname_notfound(self):
- from pyramid.response import Response
- inner_response = Response('OK')
- def inner_view(context, request):
- return inner_response
- deriver = self._makeOne(viewname='inner', wrapper_viewname='owrap')
- wrapped = deriver(inner_view)
- request = self._makeRequest()
- self.assertRaises(ValueError, wrapped, None, request)
-
- def test_as_newstyle_class_context_and_request_attr_and_renderer(self):
- response = DummyResponse()
- class renderer(object):
- def render_view(inself, req, resp, view_inst, ctx):
- self.assertEqual(req, request)
- self.assertEqual(resp, {'a':'1'})
- self.assertEqual(view_inst.__class__, View)
- self.assertEqual(ctx, context)
- return response
- def clone(self):
- return self
- class View(object):
- def __init__(self, context, request):
- pass
- def index(self):
- return {'a':'1'}
- deriver = self._makeOne(renderer=renderer(), attr='index')
- result = deriver(View)
- self.assertFalse(result is View)
- self.assertEqual(result.__module__, View.__module__)
- self.assertEqual(result.__doc__, View.__doc__)
- self.assertEqual(result.__name__, View.__name__)
- request = self._makeRequest()
- context = testing.DummyResource()
- self.assertEqual(result(context, request), response)
-
- def test_as_newstyle_class_requestonly_attr_and_renderer(self):
- response = DummyResponse()
- class renderer(object):
- def render_view(inself, req, resp, view_inst, ctx):
- self.assertEqual(req, request)
- self.assertEqual(resp, {'a':'1'})
- self.assertEqual(view_inst.__class__, View)
- self.assertEqual(ctx, context)
- return response
- def clone(self):
- return self
- class View(object):
- def __init__(self, request):
- pass
- def index(self):
- return {'a':'1'}
- deriver = self._makeOne(renderer=renderer(), attr='index')
- result = deriver(View)
- self.assertFalse(result is View)
- self.assertEqual(result.__module__, View.__module__)
- self.assertEqual(result.__doc__, View.__doc__)
- self.assertEqual(result.__name__, View.__name__)
- request = self._makeRequest()
- context = testing.DummyResource()
- self.assertEqual(result(context, request), response)
-
- def test_as_oldstyle_cls_context_request_attr_and_renderer(self):
- response = DummyResponse()
- class renderer(object):
- def render_view(inself, req, resp, view_inst, ctx):
- self.assertEqual(req, request)
- self.assertEqual(resp, {'a':'1'})
- self.assertEqual(view_inst.__class__, View)
- self.assertEqual(ctx, context)
- return response
- def clone(self):
- return self
- class View:
- def __init__(self, context, request):
- pass
- def index(self):
- return {'a':'1'}
- deriver = self._makeOne(renderer=renderer(), attr='index')
- result = deriver(View)
- self.assertFalse(result is View)
- self.assertEqual(result.__module__, View.__module__)
- self.assertEqual(result.__doc__, View.__doc__)
- self.assertEqual(result.__name__, View.__name__)
- request = self._makeRequest()
- context = testing.DummyResource()
- self.assertEqual(result(context, request), response)
-
- def test_as_oldstyle_cls_requestonly_attr_and_renderer(self):
- response = DummyResponse()
- class renderer(object):
- def render_view(inself, req, resp, view_inst, ctx):
- self.assertEqual(req, request)
- self.assertEqual(resp, {'a':'1'})
- self.assertEqual(view_inst.__class__, View)
- self.assertEqual(ctx, context)
- return response
- def clone(self):
- return self
- class View:
- def __init__(self, request):
- pass
- def index(self):
- return {'a':'1'}
- deriver = self._makeOne(renderer=renderer(), attr='index')
- result = deriver(View)
- self.assertFalse(result is View)
- self.assertEqual(result.__module__, View.__module__)
- self.assertEqual(result.__doc__, View.__doc__)
- self.assertEqual(result.__name__, View.__name__)
- request = self._makeRequest()
- context = testing.DummyResource()
- self.assertEqual(result(context, request), response)
-
- def test_as_instance_context_and_request_attr_and_renderer(self):
- response = DummyResponse()
- class renderer(object):
- def render_view(inself, req, resp, view_inst, ctx):
- self.assertEqual(req, request)
- self.assertEqual(resp, {'a':'1'})
- self.assertEqual(view_inst, view)
- self.assertEqual(ctx, context)
- return response
- def clone(self):
- return self
- class View:
- def index(self, context, request):
- return {'a':'1'}
- deriver = self._makeOne(renderer=renderer(), attr='index')
- view = View()
- result = deriver(view)
- self.assertFalse(result is view)
- self.assertEqual(result.__module__, view.__module__)
- self.assertEqual(result.__doc__, view.__doc__)
- request = self._makeRequest()
- context = testing.DummyResource()
- self.assertEqual(result(context, request), response)
-
- def test_as_instance_requestonly_attr_and_renderer(self):
- response = DummyResponse()
- class renderer(object):
- def render_view(inself, req, resp, view_inst, ctx):
- self.assertEqual(req, request)
- self.assertEqual(resp, {'a':'1'})
- self.assertEqual(view_inst, view)
- self.assertEqual(ctx, context)
- return response
- def clone(self):
- return self
- class View:
- def index(self, request):
- return {'a':'1'}
- deriver = self._makeOne(renderer=renderer(), attr='index')
- view = View()
- result = deriver(view)
- self.assertFalse(result is view)
- self.assertEqual(result.__module__, view.__module__)
- self.assertEqual(result.__doc__, view.__doc__)
- request = self._makeRequest()
- context = testing.DummyResource()
- self.assertEqual(result(context, request), response)
-
- def test_with_view_mapper_config_specified(self):
- response = DummyResponse()
- class mapper(object):
- def __init__(self, **kw):
- self.kw = kw
- def __call__(self, view):
- def wrapped(context, request):
- return response
- return wrapped
- def view(context, request): return 'NOTOK'
- deriver = self._makeOne(mapper=mapper)
- result = deriver(view)
- self.assertFalse(result.__wraps__ is view)
- self.assertEqual(result(None, None), response)
-
- def test_with_view_mapper_view_specified(self):
- from pyramid.response import Response
- response = Response()
- def mapper(**kw):
- def inner(view):
- def superinner(context, request):
- self.assertEqual(request, None)
- return response
- return superinner
- return inner
- def view(context, request): return 'NOTOK'
- view.__view_mapper__ = mapper
- deriver = self._makeOne()
- result = deriver(view)
- self.assertFalse(result.__wraps__ is view)
- self.assertEqual(result(None, None), response)
-
- def test_with_view_mapper_default_mapper_specified(self):
- from pyramid.response import Response
- response = Response()
- def mapper(**kw):
- def inner(view):
- def superinner(context, request):
- self.assertEqual(request, None)
- return response
- return superinner
- return inner
- self.config.set_view_mapper(mapper)
- def view(context, request): return 'NOTOK'
- deriver = self._makeOne()
- result = deriver(view)
- self.assertFalse(result.__wraps__ is view)
- self.assertEqual(result(None, None), response)
-
- def test_attr_wrapped_view_branching_default_phash(self):
- from pyramid.config.util import DEFAULT_PHASH
- def view(context, request): pass
- deriver = self._makeOne(phash=DEFAULT_PHASH)
- result = deriver(view)
- self.assertEqual(result.__wraps__, view)
-
- def test_attr_wrapped_view_branching_nondefault_phash(self):
- def view(context, request): pass
- deriver = self._makeOne(phash='nondefault')
- result = deriver(view)
- self.assertNotEqual(result, view)
-
- def test_http_cached_view_integer(self):
- import datetime
- from pyramid.response import Response
- response = Response('OK')
- def inner_view(context, request):
- return response
- deriver = self._makeOne(http_cache=3600)
- result = deriver(inner_view)
- self.assertFalse(result is inner_view)
- self.assertEqual(inner_view.__module__, result.__module__)
- self.assertEqual(inner_view.__doc__, result.__doc__)
- request = self._makeRequest()
- when = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
- result = result(None, request)
- self.assertEqual(result, response)
- headers = dict(result.headerlist)
- expires = parse_httpdate(headers['Expires'])
- assert_similar_datetime(expires, when)
- self.assertEqual(headers['Cache-Control'], 'max-age=3600')
-
- def test_http_cached_view_timedelta(self):
- import datetime
- from pyramid.response import Response
- response = Response('OK')
- def inner_view(context, request):
- return response
- deriver = self._makeOne(http_cache=datetime.timedelta(hours=1))
- result = deriver(inner_view)
- self.assertFalse(result is inner_view)
- self.assertEqual(inner_view.__module__, result.__module__)
- self.assertEqual(inner_view.__doc__, result.__doc__)
- request = self._makeRequest()
- when = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
- result = result(None, request)
- self.assertEqual(result, response)
- headers = dict(result.headerlist)
- expires = parse_httpdate(headers['Expires'])
- assert_similar_datetime(expires, when)
- self.assertEqual(headers['Cache-Control'], 'max-age=3600')
-
- def test_http_cached_view_tuple(self):
- import datetime
- from pyramid.response import Response
- response = Response('OK')
- def inner_view(context, request):
- return response
- deriver = self._makeOne(http_cache=(3600, {'public':True}))
- result = deriver(inner_view)
- self.assertFalse(result is inner_view)
- self.assertEqual(inner_view.__module__, result.__module__)
- self.assertEqual(inner_view.__doc__, result.__doc__)
- request = self._makeRequest()
- when = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
- result = result(None, request)
- self.assertEqual(result, response)
- headers = dict(result.headerlist)
- expires = parse_httpdate(headers['Expires'])
- assert_similar_datetime(expires, when)
- self.assertEqual(headers['Cache-Control'], 'max-age=3600, public')
-
- def test_http_cached_view_tuple_seconds_None(self):
- from pyramid.response import Response
- response = Response('OK')
- def inner_view(context, request):
- return response
- deriver = self._makeOne(http_cache=(None, {'public':True}))
- result = deriver(inner_view)
- self.assertFalse(result is inner_view)
- self.assertEqual(inner_view.__module__, result.__module__)
- self.assertEqual(inner_view.__doc__, result.__doc__)
- request = self._makeRequest()
- result = result(None, request)
- self.assertEqual(result, response)
- headers = dict(result.headerlist)
- self.assertFalse('Expires' in headers)
- self.assertEqual(headers['Cache-Control'], 'public')
-
- def test_http_cached_view_prevent_auto_set(self):
- from pyramid.response import Response
- response = Response()
- response.cache_control.prevent_auto = True
- def inner_view(context, request):
- return response
- deriver = self._makeOne(http_cache=3600)
- result = deriver(inner_view)
- request = self._makeRequest()
- result = result(None, request)
- self.assertEqual(result, response) # doesn't blow up
- headers = dict(result.headerlist)
- self.assertFalse('Expires' in headers)
- self.assertFalse('Cache-Control' in headers)
-
- def test_http_cached_prevent_http_cache_in_settings(self):
- self.config.registry.settings['prevent_http_cache'] = True
- from pyramid.response import Response
- response = Response()
- def inner_view(context, request):
- return response
- deriver = self._makeOne(http_cache=3600)
- result = deriver(inner_view)
- request = self._makeRequest()
- result = result(None, request)
- self.assertEqual(result, response)
- headers = dict(result.headerlist)
- self.assertFalse('Expires' in headers)
- self.assertFalse('Cache-Control' in headers)
-
- def test_http_cached_view_bad_tuple(self):
- deriver = self._makeOne(http_cache=(None,))
- def view(request): pass
- self.assertRaises(ConfigurationError, deriver, view)
class TestDefaultViewMapper(unittest.TestCase):
def setUp(self):
@@ -4271,24 +3143,6 @@ class DummyAccept(object):
def __contains__(self, val):
return val in self.matches
-class DummyLogger:
- def __init__(self):
- self.messages = []
- def info(self, msg):
- self.messages.append(msg)
- warn = info
- debug = info
-
-class DummySecurityPolicy:
- def __init__(self, permitted=True):
- self.permitted = permitted
-
- def effective_principals(self, request):
- return []
-
- def permits(self, context, principals, permission):
- return self.permitted
-
class DummyConfig:
def __init__(self):
self.registry = DummyRegistry()
diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py
index 5e341172c..22a5cde3d 100644
--- a/pyramid/tests/test_paster.py
+++ b/pyramid/tests/test_paster.py
@@ -105,18 +105,38 @@ class Test_get_appsettings(unittest.TestCase):
self.assertEqual(result['foo'], 'baz')
class Test_setup_logging(unittest.TestCase):
- def _callFUT(self, config_file):
+ def _callFUT(self, config_file, global_conf=None):
from pyramid.paster import setup_logging
dummy_cp = DummyConfigParserModule
- return setup_logging(config_file, self.fileConfig, dummy_cp)
+ return setup_logging(
+ config_uri=config_file,
+ global_conf=global_conf,
+ fileConfig=self.fileConfig,
+ configparser=dummy_cp,
+ )
- def test_it(self):
+ def test_it_no_global_conf(self):
config_file, dict = self._callFUT('/abc')
# os.path.abspath is a sop to Windows
self.assertEqual(config_file, os.path.abspath('/abc'))
self.assertEqual(dict['__file__'], os.path.abspath('/abc'))
self.assertEqual(dict['here'], os.path.abspath('/'))
+ def test_it_global_conf_empty(self):
+ config_file, dict = self._callFUT('/abc', global_conf={})
+ # os.path.abspath is a sop to Windows
+ self.assertEqual(config_file, os.path.abspath('/abc'))
+ self.assertEqual(dict['__file__'], os.path.abspath('/abc'))
+ self.assertEqual(dict['here'], os.path.abspath('/'))
+
+ def test_it_global_conf_not_empty(self):
+ config_file, dict = self._callFUT('/abc', global_conf={'key': 'val'})
+ # os.path.abspath is a sop to Windows
+ self.assertEqual(config_file, os.path.abspath('/abc'))
+ self.assertEqual(dict['__file__'], os.path.abspath('/abc'))
+ self.assertEqual(dict['here'], os.path.abspath('/'))
+ self.assertEqual(dict['key'], 'val')
+
def fileConfig(self, config_file, dict):
return config_file, dict
diff --git a/pyramid/tests/test_viewderivers.py b/pyramid/tests/test_viewderivers.py
new file mode 100644
index 000000000..1823beb4d
--- /dev/null
+++ b/pyramid/tests/test_viewderivers.py
@@ -0,0 +1,1442 @@
+import unittest
+from zope.interface import implementer
+
+from pyramid import testing
+from pyramid.exceptions import ConfigurationError
+from pyramid.interfaces import (
+ IResponse,
+ IRequest,
+ )
+
+class TestDeriveView(unittest.TestCase):
+
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ self.config = None
+ testing.tearDown()
+
+ def _makeRequest(self):
+ request = DummyRequest()
+ request.registry = self.config.registry
+ return request
+
+ def _registerLogger(self):
+ from pyramid.interfaces import IDebugLogger
+ logger = DummyLogger()
+ self.config.registry.registerUtility(logger, IDebugLogger)
+ return logger
+
+ def _registerSecurityPolicy(self, permissive):
+ from pyramid.interfaces import IAuthenticationPolicy
+ from pyramid.interfaces import IAuthorizationPolicy
+ policy = DummySecurityPolicy(permissive)
+ self.config.registry.registerUtility(policy, IAuthenticationPolicy)
+ self.config.registry.registerUtility(policy, IAuthorizationPolicy)
+
+ def test_function_returns_non_adaptable(self):
+ def view(request):
+ return None
+ result = self.config.derive_view(view)
+ self.assertFalse(result is view)
+ try:
+ result(None, None)
+ except ValueError as e:
+ self.assertEqual(
+ e.args[0],
+ 'Could not convert return value of the view callable function '
+ 'pyramid.tests.test_viewderivers.view into a response '
+ 'object. The value returned was None. You may have forgotten '
+ 'to return a value from the view callable.'
+ )
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_function_returns_non_adaptable_dict(self):
+ def view(request):
+ return {'a':1}
+ result = self.config.derive_view(view)
+ self.assertFalse(result is view)
+ try:
+ result(None, None)
+ except ValueError as e:
+ self.assertEqual(
+ e.args[0],
+ "Could not convert return value of the view callable function "
+ "pyramid.tests.test_viewderivers.view into a response "
+ "object. The value returned was {'a': 1}. You may have "
+ "forgotten to define a renderer in the view configuration."
+ )
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_instance_returns_non_adaptable(self):
+ class AView(object):
+ def __call__(self, request):
+ return None
+ view = AView()
+ result = self.config.derive_view(view)
+ self.assertFalse(result is view)
+ try:
+ result(None, None)
+ except ValueError as e:
+ msg = e.args[0]
+ self.assertTrue(msg.startswith(
+ 'Could not convert return value of the view callable object '
+ '<pyramid.tests.test_viewderivers.'))
+ self.assertTrue(msg.endswith(
+ '> into a response object. The value returned was None. You '
+ 'may have forgotten to return a value from the view callable.'))
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_function_returns_true_Response_no_renderer(self):
+ from pyramid.response import Response
+ r = Response('Hello')
+ def view(request):
+ return r
+ result = self.config.derive_view(view)
+ self.assertFalse(result is view)
+ response = result(None, None)
+ self.assertEqual(response, r)
+
+ def test_function_returns_true_Response_with_renderer(self):
+ from pyramid.response import Response
+ r = Response('Hello')
+ def view(request):
+ return r
+ renderer = object()
+ result = self.config.derive_view(view)
+ self.assertFalse(result is view)
+ response = result(None, None)
+ self.assertEqual(response, r)
+
+ def test_requestonly_default_method_returns_non_adaptable(self):
+ request = DummyRequest()
+ class AView(object):
+ def __init__(self, request):
+ pass
+ def __call__(self):
+ return None
+ result = self.config.derive_view(AView)
+ self.assertFalse(result is AView)
+ try:
+ result(None, request)
+ except ValueError as e:
+ self.assertEqual(
+ e.args[0],
+ 'Could not convert return value of the view callable '
+ 'method __call__ of '
+ 'class pyramid.tests.test_viewderivers.AView into a '
+ 'response object. The value returned was None. You may have '
+ 'forgotten to return a value from the view callable.'
+ )
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_requestonly_nondefault_method_returns_non_adaptable(self):
+ request = DummyRequest()
+ class AView(object):
+ def __init__(self, request):
+ pass
+ def theviewmethod(self):
+ return None
+ result = self.config.derive_view(AView, attr='theviewmethod')
+ self.assertFalse(result is AView)
+ try:
+ result(None, request)
+ except ValueError as e:
+ self.assertEqual(
+ e.args[0],
+ 'Could not convert return value of the view callable '
+ 'method theviewmethod of '
+ 'class pyramid.tests.test_viewderivers.AView into a '
+ 'response object. The value returned was None. You may have '
+ 'forgotten to return a value from the view callable.'
+ )
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_requestonly_function(self):
+ response = DummyResponse()
+ def view(request):
+ return response
+ result = self.config.derive_view(view)
+ self.assertFalse(result is view)
+ self.assertEqual(result(None, None), response)
+
+ def test_requestonly_function_with_renderer(self):
+ response = DummyResponse()
+ class moo(object):
+ def render_view(inself, req, resp, view_inst, ctx):
+ self.assertEqual(req, request)
+ self.assertEqual(resp, 'OK')
+ self.assertEqual(view_inst, view)
+ self.assertEqual(ctx, context)
+ return response
+ def clone(self):
+ return self
+ def view(request):
+ return 'OK'
+ result = self.config.derive_view(view, renderer=moo())
+ self.assertFalse(result.__wraps__ is view)
+ request = self._makeRequest()
+ context = testing.DummyResource()
+ self.assertEqual(result(context, request), response)
+
+ def test_requestonly_function_with_renderer_request_override(self):
+ def moo(info):
+ def inner(value, system):
+ self.assertEqual(value, 'OK')
+ self.assertEqual(system['request'], request)
+ self.assertEqual(system['context'], context)
+ return b'moo'
+ return inner
+ def view(request):
+ return 'OK'
+ self.config.add_renderer('moo', moo)
+ result = self.config.derive_view(view, renderer='string')
+ self.assertFalse(result is view)
+ request = self._makeRequest()
+ request.override_renderer = 'moo'
+ context = testing.DummyResource()
+ self.assertEqual(result(context, request).body, b'moo')
+
+ def test_requestonly_function_with_renderer_request_has_view(self):
+ response = DummyResponse()
+ class moo(object):
+ def render_view(inself, req, resp, view_inst, ctx):
+ self.assertEqual(req, request)
+ self.assertEqual(resp, 'OK')
+ self.assertEqual(view_inst, 'view')
+ self.assertEqual(ctx, context)
+ return response
+ def clone(self):
+ return self
+ def view(request):
+ return 'OK'
+ result = self.config.derive_view(view, renderer=moo())
+ self.assertFalse(result.__wraps__ is view)
+ request = self._makeRequest()
+ request.__view__ = 'view'
+ context = testing.DummyResource()
+ r = result(context, request)
+ self.assertEqual(r, response)
+ self.assertFalse(hasattr(request, '__view__'))
+
+ def test_class_without_attr(self):
+ response = DummyResponse()
+ class View(object):
+ def __init__(self, request):
+ pass
+ def __call__(self):
+ return response
+ result = self.config.derive_view(View)
+ request = self._makeRequest()
+ self.assertEqual(result(None, request), response)
+ self.assertEqual(request.__view__.__class__, View)
+
+ def test_class_with_attr(self):
+ response = DummyResponse()
+ class View(object):
+ def __init__(self, request):
+ pass
+ def another(self):
+ return response
+ result = self.config.derive_view(View, attr='another')
+ request = self._makeRequest()
+ self.assertEqual(result(None, request), response)
+ self.assertEqual(request.__view__.__class__, View)
+
+ def test_as_function_context_and_request(self):
+ def view(context, request):
+ return 'OK'
+ result = self.config.derive_view(view)
+ self.assertTrue(result.__wraps__ is view)
+ self.assertFalse(hasattr(result, '__call_permissive__'))
+ self.assertEqual(view(None, None), 'OK')
+
+ def test_as_function_requestonly(self):
+ response = DummyResponse()
+ def view(request):
+ return response
+ result = self.config.derive_view(view)
+ self.assertFalse(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertFalse(hasattr(result, '__call_permissive__'))
+ self.assertEqual(result(None, None), response)
+
+ def test_as_newstyle_class_context_and_request(self):
+ response = DummyResponse()
+ class view(object):
+ def __init__(self, context, request):
+ pass
+ def __call__(self):
+ return response
+ result = self.config.derive_view(view)
+ self.assertFalse(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertFalse(hasattr(result, '__call_permissive__'))
+ request = self._makeRequest()
+ self.assertEqual(result(None, request), response)
+ self.assertEqual(request.__view__.__class__, view)
+
+ def test_as_newstyle_class_requestonly(self):
+ response = DummyResponse()
+ class view(object):
+ def __init__(self, context, request):
+ pass
+ def __call__(self):
+ return response
+ result = self.config.derive_view(view)
+ self.assertFalse(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertFalse(hasattr(result, '__call_permissive__'))
+ request = self._makeRequest()
+ self.assertEqual(result(None, request), response)
+ self.assertEqual(request.__view__.__class__, view)
+
+ def test_as_oldstyle_class_context_and_request(self):
+ response = DummyResponse()
+ class view:
+ def __init__(self, context, request):
+ pass
+ def __call__(self):
+ return response
+ result = self.config.derive_view(view)
+ self.assertFalse(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertFalse(hasattr(result, '__call_permissive__'))
+ request = self._makeRequest()
+ self.assertEqual(result(None, request), response)
+ self.assertEqual(request.__view__.__class__, view)
+
+ def test_as_oldstyle_class_requestonly(self):
+ response = DummyResponse()
+ class view:
+ def __init__(self, context, request):
+ pass
+ def __call__(self):
+ return response
+ result = self.config.derive_view(view)
+ self.assertFalse(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertFalse(hasattr(result, '__call_permissive__'))
+ request = self._makeRequest()
+ self.assertEqual(result(None, request), response)
+ self.assertEqual(request.__view__.__class__, view)
+
+ def test_as_instance_context_and_request(self):
+ response = DummyResponse()
+ class View:
+ def __call__(self, context, request):
+ return response
+ view = View()
+ result = self.config.derive_view(view)
+ self.assertTrue(result.__wraps__ is view)
+ self.assertFalse(hasattr(result, '__call_permissive__'))
+ self.assertEqual(result(None, None), response)
+
+ def test_as_instance_requestonly(self):
+ response = DummyResponse()
+ class View:
+ def __call__(self, request):
+ return response
+ view = View()
+ result = self.config.derive_view(view)
+ self.assertFalse(result is view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertTrue('test_viewderivers' in result.__name__)
+ self.assertFalse(hasattr(result, '__call_permissive__'))
+ self.assertEqual(result(None, None), response)
+
+ def test_with_debug_authorization_no_authpol(self):
+ response = DummyResponse()
+ view = lambda *arg: response
+ self.config.registry.settings = dict(
+ debug_authorization=True, reload_templates=True)
+ logger = self._registerLogger()
+ result = self.config._derive_view(view, permission='view')
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertFalse(hasattr(result, '__call_permissive__'))
+ request = self._makeRequest()
+ request.view_name = 'view_name'
+ request.url = 'url'
+ self.assertEqual(result(None, request), response)
+ self.assertEqual(len(logger.messages), 1)
+ self.assertEqual(logger.messages[0],
+ "debug_authorization of url url (view name "
+ "'view_name' against context None): Allowed "
+ "(no authorization policy in use)")
+
+ def test_with_debug_authorization_authn_policy_no_authz_policy(self):
+ response = DummyResponse()
+ view = lambda *arg: response
+ self.config.registry.settings = dict(debug_authorization=True)
+ from pyramid.interfaces import IAuthenticationPolicy
+ policy = DummySecurityPolicy(False)
+ self.config.registry.registerUtility(policy, IAuthenticationPolicy)
+ logger = self._registerLogger()
+ result = self.config._derive_view(view, permission='view')
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertFalse(hasattr(result, '__call_permissive__'))
+ request = self._makeRequest()
+ request.view_name = 'view_name'
+ request.url = 'url'
+ self.assertEqual(result(None, request), response)
+ self.assertEqual(len(logger.messages), 1)
+ self.assertEqual(logger.messages[0],
+ "debug_authorization of url url (view name "
+ "'view_name' against context None): Allowed "
+ "(no authorization policy in use)")
+
+ def test_with_debug_authorization_authz_policy_no_authn_policy(self):
+ response = DummyResponse()
+ view = lambda *arg: response
+ self.config.registry.settings = dict(debug_authorization=True)
+ from pyramid.interfaces import IAuthorizationPolicy
+ policy = DummySecurityPolicy(False)
+ self.config.registry.registerUtility(policy, IAuthorizationPolicy)
+ logger = self._registerLogger()
+ result = self.config._derive_view(view, permission='view')
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertFalse(hasattr(result, '__call_permissive__'))
+ request = self._makeRequest()
+ request.view_name = 'view_name'
+ request.url = 'url'
+ self.assertEqual(result(None, request), response)
+ self.assertEqual(len(logger.messages), 1)
+ self.assertEqual(logger.messages[0],
+ "debug_authorization of url url (view name "
+ "'view_name' against context None): Allowed "
+ "(no authorization policy in use)")
+
+ def test_with_debug_authorization_no_permission(self):
+ response = DummyResponse()
+ view = lambda *arg: response
+ self.config.registry.settings = dict(
+ debug_authorization=True, reload_templates=True)
+ self._registerSecurityPolicy(True)
+ logger = self._registerLogger()
+ result = self.config._derive_view(view)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertFalse(hasattr(result, '__call_permissive__'))
+ request = self._makeRequest()
+ request.view_name = 'view_name'
+ request.url = 'url'
+ self.assertEqual(result(None, request), response)
+ self.assertEqual(len(logger.messages), 1)
+ self.assertEqual(logger.messages[0],
+ "debug_authorization of url url (view name "
+ "'view_name' against context None): Allowed ("
+ "no permission registered)")
+
+ def test_debug_auth_permission_authpol_permitted(self):
+ response = DummyResponse()
+ view = lambda *arg: response
+ self.config.registry.settings = dict(
+ debug_authorization=True, reload_templates=True)
+ logger = self._registerLogger()
+ self._registerSecurityPolicy(True)
+ result = self.config._derive_view(view, permission='view')
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertEqual(result.__call_permissive__.__wraps__, view)
+ request = self._makeRequest()
+ request.view_name = 'view_name'
+ request.url = 'url'
+ self.assertEqual(result(None, request), response)
+ self.assertEqual(len(logger.messages), 1)
+ self.assertEqual(logger.messages[0],
+ "debug_authorization of url url (view name "
+ "'view_name' against context None): True")
+
+ def test_debug_auth_permission_authpol_permitted_no_request(self):
+ response = DummyResponse()
+ view = lambda *arg: response
+ self.config.registry.settings = dict(
+ debug_authorization=True, reload_templates=True)
+ logger = self._registerLogger()
+ self._registerSecurityPolicy(True)
+ result = self.config._derive_view(view, permission='view')
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertEqual(result.__call_permissive__.__wraps__, view)
+ self.assertEqual(result(None, None), response)
+ self.assertEqual(len(logger.messages), 1)
+ self.assertEqual(logger.messages[0],
+ "debug_authorization of url None (view name "
+ "None against context None): True")
+
+ def test_debug_auth_permission_authpol_denied(self):
+ from pyramid.httpexceptions import HTTPForbidden
+ response = DummyResponse()
+ view = lambda *arg: response
+ self.config.registry.settings = dict(
+ debug_authorization=True, reload_templates=True)
+ logger = self._registerLogger()
+ self._registerSecurityPolicy(False)
+ result = self.config._derive_view(view, permission='view')
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertEqual(result.__call_permissive__.__wraps__, view)
+ request = self._makeRequest()
+ request.view_name = 'view_name'
+ request.url = 'url'
+ self.assertRaises(HTTPForbidden, result, None, request)
+ self.assertEqual(len(logger.messages), 1)
+ self.assertEqual(logger.messages[0],
+ "debug_authorization of url url (view name "
+ "'view_name' against context None): False")
+
+ def test_debug_auth_permission_authpol_denied2(self):
+ view = lambda *arg: 'OK'
+ self.config.registry.settings = dict(
+ debug_authorization=True, reload_templates=True)
+ self._registerLogger()
+ self._registerSecurityPolicy(False)
+ result = self.config._derive_view(view, permission='view')
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ request = self._makeRequest()
+ request.view_name = 'view_name'
+ request.url = 'url'
+ permitted = result.__permitted__(None, None)
+ self.assertEqual(permitted, False)
+
+ def test_debug_auth_permission_authpol_overridden(self):
+ from pyramid.security import NO_PERMISSION_REQUIRED
+ response = DummyResponse()
+ view = lambda *arg: response
+ self.config.registry.settings = dict(
+ debug_authorization=True, reload_templates=True)
+ logger = self._registerLogger()
+ self._registerSecurityPolicy(False)
+ result = self.config._derive_view(view, permission=NO_PERMISSION_REQUIRED)
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertFalse(hasattr(result, '__call_permissive__'))
+ request = self._makeRequest()
+ request.view_name = 'view_name'
+ request.url = 'url'
+ self.assertEqual(result(None, request), response)
+ self.assertEqual(len(logger.messages), 1)
+ self.assertEqual(logger.messages[0],
+ "debug_authorization of url url (view name "
+ "'view_name' against context None): "
+ "Allowed (NO_PERMISSION_REQUIRED)")
+
+ def test_secured_view_authn_policy_no_authz_policy(self):
+ response = DummyResponse()
+ view = lambda *arg: response
+ self.config.registry.settings = {}
+ from pyramid.interfaces import IAuthenticationPolicy
+ policy = DummySecurityPolicy(False)
+ self.config.registry.registerUtility(policy, IAuthenticationPolicy)
+ result = self.config._derive_view(view, permission='view')
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertFalse(hasattr(result, '__call_permissive__'))
+ request = self._makeRequest()
+ request.view_name = 'view_name'
+ request.url = 'url'
+ self.assertEqual(result(None, request), response)
+
+ def test_secured_view_authz_policy_no_authn_policy(self):
+ response = DummyResponse()
+ view = lambda *arg: response
+ self.config.registry.settings = {}
+ from pyramid.interfaces import IAuthorizationPolicy
+ policy = DummySecurityPolicy(False)
+ self.config.registry.registerUtility(policy, IAuthorizationPolicy)
+ result = self.config._derive_view(view, permission='view')
+ self.assertEqual(view.__module__, result.__module__)
+ self.assertEqual(view.__doc__, result.__doc__)
+ self.assertEqual(view.__name__, result.__name__)
+ self.assertFalse(hasattr(result, '__call_permissive__'))
+ request = self._makeRequest()
+ request.view_name = 'view_name'
+ request.url = 'url'
+ self.assertEqual(result(None, request), response)
+
+ def test_secured_view_raises_forbidden_no_name(self):
+ from pyramid.interfaces import IAuthenticationPolicy
+ from pyramid.interfaces import IAuthorizationPolicy
+ from pyramid.httpexceptions import HTTPForbidden
+ response = DummyResponse()
+ view = lambda *arg: response
+ self.config.registry.settings = {}
+ policy = DummySecurityPolicy(False)
+ self.config.registry.registerUtility(policy, IAuthenticationPolicy)
+ self.config.registry.registerUtility(policy, IAuthorizationPolicy)
+ result = self.config._derive_view(view, permission='view')
+ request = self._makeRequest()
+ request.view_name = 'view_name'
+ request.url = 'url'
+ try:
+ result(None, request)
+ except HTTPForbidden as e:
+ self.assertEqual(e.message,
+ 'Unauthorized: <lambda> failed permission check')
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_secured_view_raises_forbidden_with_name(self):
+ from pyramid.interfaces import IAuthenticationPolicy
+ from pyramid.interfaces import IAuthorizationPolicy
+ from pyramid.httpexceptions import HTTPForbidden
+ def myview(request): pass
+ self.config.registry.settings = {}
+ policy = DummySecurityPolicy(False)
+ self.config.registry.registerUtility(policy, IAuthenticationPolicy)
+ self.config.registry.registerUtility(policy, IAuthorizationPolicy)
+ result = self.config._derive_view(myview, permission='view')
+ request = self._makeRequest()
+ request.view_name = 'view_name'
+ request.url = 'url'
+ try:
+ result(None, request)
+ except HTTPForbidden as e:
+ self.assertEqual(e.message,
+ 'Unauthorized: myview failed permission check')
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_predicate_mismatch_view_has_no_name(self):
+ from pyramid.exceptions import PredicateMismatch
+ response = DummyResponse()
+ view = lambda *arg: response
+ def predicate1(context, request):
+ return False
+ predicate1.text = lambda *arg: 'text'
+ result = self.config._derive_view(view, predicates=[predicate1])
+ request = self._makeRequest()
+ request.method = 'POST'
+ try:
+ result(None, None)
+ except PredicateMismatch as e:
+ self.assertEqual(e.detail,
+ 'predicate mismatch for view <lambda> (text)')
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_predicate_mismatch_view_has_name(self):
+ from pyramid.exceptions import PredicateMismatch
+ def myview(request): pass
+ def predicate1(context, request):
+ return False
+ predicate1.text = lambda *arg: 'text'
+ result = self.config._derive_view(myview, predicates=[predicate1])
+ request = self._makeRequest()
+ request.method = 'POST'
+ try:
+ result(None, None)
+ except PredicateMismatch as e:
+ self.assertEqual(e.detail,
+ 'predicate mismatch for view myview (text)')
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_predicate_mismatch_exception_has_text_in_detail(self):
+ from pyramid.exceptions import PredicateMismatch
+ def myview(request): pass
+ def predicate1(context, request):
+ return True
+ predicate1.text = lambda *arg: 'pred1'
+ def predicate2(context, request):
+ return False
+ predicate2.text = lambda *arg: 'pred2'
+ result = self.config._derive_view(myview,
+ predicates=[predicate1, predicate2])
+ request = self._makeRequest()
+ request.method = 'POST'
+ try:
+ result(None, None)
+ except PredicateMismatch as e:
+ self.assertEqual(e.detail,
+ 'predicate mismatch for view myview (pred2)')
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_with_predicates_all(self):
+ response = DummyResponse()
+ view = lambda *arg: response
+ predicates = []
+ def predicate1(context, request):
+ predicates.append(True)
+ return True
+ def predicate2(context, request):
+ predicates.append(True)
+ return True
+ result = self.config._derive_view(view,
+ predicates=[predicate1, predicate2])
+ request = self._makeRequest()
+ request.method = 'POST'
+ next = result(None, None)
+ self.assertEqual(next, response)
+ self.assertEqual(predicates, [True, True])
+
+ def test_with_predicates_checker(self):
+ view = lambda *arg: 'OK'
+ predicates = []
+ def predicate1(context, request):
+ predicates.append(True)
+ return True
+ def predicate2(context, request):
+ predicates.append(True)
+ return True
+ result = self.config._derive_view(view,
+ predicates=[predicate1, predicate2])
+ request = self._makeRequest()
+ request.method = 'POST'
+ next = result.__predicated__(None, None)
+ self.assertEqual(next, True)
+ self.assertEqual(predicates, [True, True])
+
+ def test_with_predicates_notall(self):
+ from pyramid.httpexceptions import HTTPNotFound
+ view = lambda *arg: 'OK'
+ predicates = []
+ def predicate1(context, request):
+ predicates.append(True)
+ return True
+ predicate1.text = lambda *arg: 'text'
+ def predicate2(context, request):
+ predicates.append(True)
+ return False
+ predicate2.text = lambda *arg: 'text'
+ result = self.config._derive_view(view,
+ predicates=[predicate1, predicate2])
+ request = self._makeRequest()
+ request.method = 'POST'
+ self.assertRaises(HTTPNotFound, result, None, None)
+ self.assertEqual(predicates, [True, True])
+
+ def test_with_wrapper_viewname(self):
+ from pyramid.response import Response
+ from pyramid.interfaces import IView
+ from pyramid.interfaces import IViewClassifier
+ inner_response = Response('OK')
+ def inner_view(context, request):
+ return inner_response
+ def outer_view(context, request):
+ self.assertEqual(request.wrapped_response, inner_response)
+ self.assertEqual(request.wrapped_body, inner_response.body)
+ self.assertEqual(request.wrapped_view.__original_view__,
+ inner_view)
+ return Response(b'outer ' + request.wrapped_body)
+ self.config.registry.registerAdapter(
+ outer_view, (IViewClassifier, None, None), IView, 'owrap')
+ result = self.config._derive_view(inner_view, viewname='inner',
+ wrapper_viewname='owrap')
+ self.assertFalse(result is inner_view)
+ self.assertEqual(inner_view.__module__, result.__module__)
+ self.assertEqual(inner_view.__doc__, result.__doc__)
+ request = self._makeRequest()
+ response = result(None, request)
+ self.assertEqual(response.body, b'outer OK')
+
+ def test_with_wrapper_viewname_notfound(self):
+ from pyramid.response import Response
+ inner_response = Response('OK')
+ def inner_view(context, request):
+ return inner_response
+ wrapped = self.config._derive_view(inner_view, viewname='inner',
+ wrapper_viewname='owrap')
+ request = self._makeRequest()
+ self.assertRaises(ValueError, wrapped, None, request)
+
+ def test_as_newstyle_class_context_and_request_attr_and_renderer(self):
+ response = DummyResponse()
+ class renderer(object):
+ def render_view(inself, req, resp, view_inst, ctx):
+ self.assertEqual(req, request)
+ self.assertEqual(resp, {'a':'1'})
+ self.assertEqual(view_inst.__class__, View)
+ self.assertEqual(ctx, context)
+ return response
+ def clone(self):
+ return self
+ class View(object):
+ def __init__(self, context, request):
+ pass
+ def index(self):
+ return {'a':'1'}
+ result = self.config._derive_view(View,
+ renderer=renderer(), attr='index')
+ self.assertFalse(result is View)
+ self.assertEqual(result.__module__, View.__module__)
+ self.assertEqual(result.__doc__, View.__doc__)
+ self.assertEqual(result.__name__, View.__name__)
+ request = self._makeRequest()
+ context = testing.DummyResource()
+ self.assertEqual(result(context, request), response)
+
+ def test_as_newstyle_class_requestonly_attr_and_renderer(self):
+ response = DummyResponse()
+ class renderer(object):
+ def render_view(inself, req, resp, view_inst, ctx):
+ self.assertEqual(req, request)
+ self.assertEqual(resp, {'a':'1'})
+ self.assertEqual(view_inst.__class__, View)
+ self.assertEqual(ctx, context)
+ return response
+ def clone(self):
+ return self
+ class View(object):
+ def __init__(self, request):
+ pass
+ def index(self):
+ return {'a':'1'}
+ result = self.config.derive_view(View,
+ renderer=renderer(), attr='index')
+ self.assertFalse(result is View)
+ self.assertEqual(result.__module__, View.__module__)
+ self.assertEqual(result.__doc__, View.__doc__)
+ self.assertEqual(result.__name__, View.__name__)
+ request = self._makeRequest()
+ context = testing.DummyResource()
+ self.assertEqual(result(context, request), response)
+
+ def test_as_oldstyle_cls_context_request_attr_and_renderer(self):
+ response = DummyResponse()
+ class renderer(object):
+ def render_view(inself, req, resp, view_inst, ctx):
+ self.assertEqual(req, request)
+ self.assertEqual(resp, {'a':'1'})
+ self.assertEqual(view_inst.__class__, View)
+ self.assertEqual(ctx, context)
+ return response
+ def clone(self):
+ return self
+ class View:
+ def __init__(self, context, request):
+ pass
+ def index(self):
+ return {'a':'1'}
+ result = self.config.derive_view(View,
+ renderer=renderer(), attr='index')
+ self.assertFalse(result is View)
+ self.assertEqual(result.__module__, View.__module__)
+ self.assertEqual(result.__doc__, View.__doc__)
+ self.assertEqual(result.__name__, View.__name__)
+ request = self._makeRequest()
+ context = testing.DummyResource()
+ self.assertEqual(result(context, request), response)
+
+ def test_as_oldstyle_cls_requestonly_attr_and_renderer(self):
+ response = DummyResponse()
+ class renderer(object):
+ def render_view(inself, req, resp, view_inst, ctx):
+ self.assertEqual(req, request)
+ self.assertEqual(resp, {'a':'1'})
+ self.assertEqual(view_inst.__class__, View)
+ self.assertEqual(ctx, context)
+ return response
+ def clone(self):
+ return self
+ class View:
+ def __init__(self, request):
+ pass
+ def index(self):
+ return {'a':'1'}
+ result = self.config.derive_view(View,
+ renderer=renderer(), attr='index')
+ self.assertFalse(result is View)
+ self.assertEqual(result.__module__, View.__module__)
+ self.assertEqual(result.__doc__, View.__doc__)
+ self.assertEqual(result.__name__, View.__name__)
+ request = self._makeRequest()
+ context = testing.DummyResource()
+ self.assertEqual(result(context, request), response)
+
+ def test_as_instance_context_and_request_attr_and_renderer(self):
+ response = DummyResponse()
+ class renderer(object):
+ def render_view(inself, req, resp, view_inst, ctx):
+ self.assertEqual(req, request)
+ self.assertEqual(resp, {'a':'1'})
+ self.assertEqual(view_inst, view)
+ self.assertEqual(ctx, context)
+ return response
+ def clone(self):
+ return self
+ class View:
+ def index(self, context, request):
+ return {'a':'1'}
+ view = View()
+ result = self.config.derive_view(view,
+ renderer=renderer(), attr='index')
+ self.assertFalse(result is view)
+ self.assertEqual(result.__module__, view.__module__)
+ self.assertEqual(result.__doc__, view.__doc__)
+ request = self._makeRequest()
+ context = testing.DummyResource()
+ self.assertEqual(result(context, request), response)
+
+ def test_as_instance_requestonly_attr_and_renderer(self):
+ response = DummyResponse()
+ class renderer(object):
+ def render_view(inself, req, resp, view_inst, ctx):
+ self.assertEqual(req, request)
+ self.assertEqual(resp, {'a':'1'})
+ self.assertEqual(view_inst, view)
+ self.assertEqual(ctx, context)
+ return response
+ def clone(self):
+ return self
+ class View:
+ def index(self, request):
+ return {'a':'1'}
+ view = View()
+ result = self.config.derive_view(view,
+ renderer=renderer(), attr='index')
+ self.assertFalse(result is view)
+ self.assertEqual(result.__module__, view.__module__)
+ self.assertEqual(result.__doc__, view.__doc__)
+ request = self._makeRequest()
+ context = testing.DummyResource()
+ self.assertEqual(result(context, request), response)
+
+ def test_with_view_mapper_config_specified(self):
+ response = DummyResponse()
+ class mapper(object):
+ def __init__(self, **kw):
+ self.kw = kw
+ def __call__(self, view):
+ def wrapped(context, request):
+ return response
+ return wrapped
+ def view(context, request): return 'NOTOK'
+ result = self.config._derive_view(view, mapper=mapper)
+ self.assertFalse(result.__wraps__ is view)
+ self.assertEqual(result(None, None), response)
+
+ def test_with_view_mapper_view_specified(self):
+ from pyramid.response import Response
+ response = Response()
+ def mapper(**kw):
+ def inner(view):
+ def superinner(context, request):
+ self.assertEqual(request, None)
+ return response
+ return superinner
+ return inner
+ def view(context, request): return 'NOTOK'
+ view.__view_mapper__ = mapper
+ result = self.config.derive_view(view)
+ self.assertFalse(result.__wraps__ is view)
+ self.assertEqual(result(None, None), response)
+
+ def test_with_view_mapper_default_mapper_specified(self):
+ from pyramid.response import Response
+ response = Response()
+ def mapper(**kw):
+ def inner(view):
+ def superinner(context, request):
+ self.assertEqual(request, None)
+ return response
+ return superinner
+ return inner
+ self.config.set_view_mapper(mapper)
+ def view(context, request): return 'NOTOK'
+ result = self.config.derive_view(view)
+ self.assertFalse(result.__wraps__ is view)
+ self.assertEqual(result(None, None), response)
+
+ def test_attr_wrapped_view_branching_default_phash(self):
+ from pyramid.config.util import DEFAULT_PHASH
+ def view(context, request): pass
+ result = self.config._derive_view(view, phash=DEFAULT_PHASH)
+ self.assertEqual(result.__wraps__, view)
+
+ def test_attr_wrapped_view_branching_nondefault_phash(self):
+ def view(context, request): pass
+ result = self.config._derive_view(view, phash='nondefault')
+ self.assertNotEqual(result, view)
+
+ def test_http_cached_view_integer(self):
+ import datetime
+ from pyramid.response import Response
+ response = Response('OK')
+ def inner_view(context, request):
+ return response
+ result = self.config._derive_view(inner_view, http_cache=3600)
+ self.assertFalse(result is inner_view)
+ self.assertEqual(inner_view.__module__, result.__module__)
+ self.assertEqual(inner_view.__doc__, result.__doc__)
+ request = self._makeRequest()
+ when = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
+ result = result(None, request)
+ self.assertEqual(result, response)
+ headers = dict(result.headerlist)
+ expires = parse_httpdate(headers['Expires'])
+ assert_similar_datetime(expires, when)
+ self.assertEqual(headers['Cache-Control'], 'max-age=3600')
+
+ def test_http_cached_view_timedelta(self):
+ import datetime
+ from pyramid.response import Response
+ response = Response('OK')
+ def inner_view(context, request):
+ return response
+ result = self.config._derive_view(inner_view,
+ http_cache=datetime.timedelta(hours=1))
+ self.assertFalse(result is inner_view)
+ self.assertEqual(inner_view.__module__, result.__module__)
+ self.assertEqual(inner_view.__doc__, result.__doc__)
+ request = self._makeRequest()
+ when = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
+ result = result(None, request)
+ self.assertEqual(result, response)
+ headers = dict(result.headerlist)
+ expires = parse_httpdate(headers['Expires'])
+ assert_similar_datetime(expires, when)
+ self.assertEqual(headers['Cache-Control'], 'max-age=3600')
+
+ def test_http_cached_view_tuple(self):
+ import datetime
+ from pyramid.response import Response
+ response = Response('OK')
+ def inner_view(context, request):
+ return response
+ result = self.config._derive_view(inner_view,
+ http_cache=(3600, {'public':True}))
+ self.assertFalse(result is inner_view)
+ self.assertEqual(inner_view.__module__, result.__module__)
+ self.assertEqual(inner_view.__doc__, result.__doc__)
+ request = self._makeRequest()
+ when = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
+ result = result(None, request)
+ self.assertEqual(result, response)
+ headers = dict(result.headerlist)
+ expires = parse_httpdate(headers['Expires'])
+ assert_similar_datetime(expires, when)
+ self.assertEqual(headers['Cache-Control'], 'max-age=3600, public')
+
+ def test_http_cached_view_tuple_seconds_None(self):
+ from pyramid.response import Response
+ response = Response('OK')
+ def inner_view(context, request):
+ return response
+ result = self.config._derive_view(inner_view,
+ http_cache=(None, {'public':True}))
+ self.assertFalse(result is inner_view)
+ self.assertEqual(inner_view.__module__, result.__module__)
+ self.assertEqual(inner_view.__doc__, result.__doc__)
+ request = self._makeRequest()
+ result = result(None, request)
+ self.assertEqual(result, response)
+ headers = dict(result.headerlist)
+ self.assertFalse('Expires' in headers)
+ self.assertEqual(headers['Cache-Control'], 'public')
+
+ def test_http_cached_view_prevent_auto_set(self):
+ from pyramid.response import Response
+ response = Response()
+ response.cache_control.prevent_auto = True
+ def inner_view(context, request):
+ return response
+ result = self.config._derive_view(inner_view, http_cache=3600)
+ request = self._makeRequest()
+ result = result(None, request)
+ self.assertEqual(result, response) # doesn't blow up
+ headers = dict(result.headerlist)
+ self.assertFalse('Expires' in headers)
+ self.assertFalse('Cache-Control' in headers)
+
+ def test_http_cached_prevent_http_cache_in_settings(self):
+ self.config.registry.settings['prevent_http_cache'] = True
+ from pyramid.response import Response
+ response = Response()
+ def inner_view(context, request):
+ return response
+ result = self.config._derive_view(inner_view, http_cache=3600)
+ request = self._makeRequest()
+ result = result(None, request)
+ self.assertEqual(result, response)
+ headers = dict(result.headerlist)
+ self.assertFalse('Expires' in headers)
+ self.assertFalse('Cache-Control' in headers)
+
+ def test_http_cached_view_bad_tuple(self):
+ def view(request): pass
+ self.assertRaises(ConfigurationError, self.config._derive_view,
+ view, http_cache=(None,))
+
+
+class TestDerivationOrder(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ self.config = None
+ testing.tearDown()
+
+ def test_right_order_user_sorted(self):
+ from pyramid.interfaces import IViewDerivers
+
+ self.config.add_view_deriver(None, 'deriv1')
+ self.config.add_view_deriver(None, 'deriv2', 'decorated_view', 'deriv1')
+ self.config.add_view_deriver(None, 'deriv3', 'deriv2', 'deriv1')
+
+ derivers = self.config.registry.getUtility(IViewDerivers)
+ derivers_sorted = derivers.sorted()
+ dlist = [d for (d, _) in derivers_sorted]
+ self.assertEqual([
+ 'secured_view',
+ 'owrapped_view',
+ 'http_cached_view',
+ 'decorated_view',
+ 'deriv2',
+ 'deriv3',
+ 'deriv1',
+ 'rendered_view',
+ 'mapped_view',
+ ], dlist)
+
+ def test_right_order_implicit(self):
+ from pyramid.interfaces import IViewDerivers
+
+ self.config.add_view_deriver(None, 'deriv1')
+ self.config.add_view_deriver(None, 'deriv2')
+ self.config.add_view_deriver(None, 'deriv3')
+
+ derivers = self.config.registry.getUtility(IViewDerivers)
+ derivers_sorted = derivers.sorted()
+ dlist = [d for (d, _) in derivers_sorted]
+ self.assertEqual([
+ 'secured_view',
+ 'owrapped_view',
+ 'http_cached_view',
+ 'decorated_view',
+ 'deriv3',
+ 'deriv2',
+ 'deriv1',
+ 'rendered_view',
+ 'mapped_view',
+ ], dlist)
+
+ def test_right_order_under_rendered_view(self):
+ from pyramid.interfaces import IViewDerivers
+
+ self.config.add_view_deriver(None, 'deriv1', 'rendered_view', 'mapped_view')
+
+ derivers = self.config.registry.getUtility(IViewDerivers)
+ derivers_sorted = derivers.sorted()
+ dlist = [d for (d, _) in derivers_sorted]
+ self.assertEqual([
+ 'secured_view',
+ 'owrapped_view',
+ 'http_cached_view',
+ 'decorated_view',
+ 'rendered_view',
+ 'deriv1',
+ 'mapped_view',
+ ], dlist)
+
+
+ def test_right_order_under_rendered_view_others(self):
+ from pyramid.interfaces import IViewDerivers
+
+ self.config.add_view_deriver(None, 'deriv1', 'rendered_view', 'mapped_view')
+ self.config.add_view_deriver(None, 'deriv2')
+ self.config.add_view_deriver(None, 'deriv3')
+
+ derivers = self.config.registry.getUtility(IViewDerivers)
+ derivers_sorted = derivers.sorted()
+ dlist = [d for (d, _) in derivers_sorted]
+ self.assertEqual([
+ 'secured_view',
+ 'owrapped_view',
+ 'http_cached_view',
+ 'decorated_view',
+ 'deriv3',
+ 'deriv2',
+ 'rendered_view',
+ 'deriv1',
+ 'mapped_view',
+ ], dlist)
+
+
+class TestAddDeriver(unittest.TestCase):
+
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ self.config = None
+ testing.tearDown()
+
+ def test_add_single_deriver(self):
+ response = DummyResponse()
+ response.deriv = False
+ view = lambda *arg: response
+
+ def deriv(view, info):
+ self.assertFalse(response.deriv)
+ response.deriv = True
+ return view
+
+ result = self.config._derive_view(view)
+ self.assertFalse(response.deriv)
+ self.config.add_view_deriver(deriv, 'test_deriv')
+
+ result = self.config._derive_view(view)
+ self.assertTrue(response.deriv)
+
+ def test_override_deriver(self):
+ flags = {}
+
+ class AView:
+ def __init__(self):
+ self.response = DummyResponse()
+
+ def deriv1(view, info):
+ flags['deriv1'] = True
+ return view
+
+ def deriv2(view, info):
+ flags['deriv2'] = True
+ return view
+
+ view1 = AView()
+ self.config.add_view_deriver(deriv1, 'test_deriv')
+ result = self.config._derive_view(view1)
+ self.assertTrue(flags.get('deriv1'))
+ self.assertFalse(flags.get('deriv2'))
+
+ flags.clear()
+ view2 = AView()
+ self.config.add_view_deriver(deriv2, 'test_deriv')
+ result = self.config._derive_view(view2)
+ self.assertFalse(flags.get('deriv1'))
+ self.assertTrue(flags.get('deriv2'))
+
+ def test_override_mapped_view(self):
+ from pyramid.viewderivers import VIEW
+ response = DummyResponse()
+ view = lambda *arg: response
+ flags = {}
+
+ def deriv1(view, info):
+ flags['deriv1'] = True
+ return view
+
+ result = self.config._derive_view(view)
+ self.assertFalse(flags.get('deriv1'))
+
+ flags.clear()
+ self.config.add_view_deriver(
+ deriv1, name='mapped_view', under='rendered_view', over=VIEW)
+ result = self.config._derive_view(view)
+ self.assertTrue(flags.get('deriv1'))
+
+ def test_add_multi_derivers_ordered(self):
+ from pyramid.viewderivers import INGRESS
+ response = DummyResponse()
+ view = lambda *arg: response
+ response.deriv = []
+
+ def deriv1(view, info):
+ response.deriv.append('deriv1')
+ return view
+
+ def deriv2(view, info):
+ response.deriv.append('deriv2')
+ return view
+
+ def deriv3(view, info):
+ response.deriv.append('deriv3')
+ return view
+
+ self.config.add_view_deriver(deriv1, 'deriv1')
+ self.config.add_view_deriver(deriv2, 'deriv2', INGRESS, 'deriv1')
+ self.config.add_view_deriver(deriv3, 'deriv3', 'deriv2', 'deriv1')
+ result = self.config._derive_view(view)
+ self.assertEqual(response.deriv, ['deriv1', 'deriv3', 'deriv2'])
+
+ def test_add_deriver_without_name(self):
+ from pyramid.interfaces import IViewDerivers
+ def deriv1(view, info): pass
+ self.config.add_view_deriver(deriv1)
+ derivers = self.config.registry.getUtility(IViewDerivers)
+ self.assertTrue('deriv1' in derivers.names)
+
+ def test_add_deriver_reserves_ingress(self):
+ from pyramid.exceptions import ConfigurationError
+ from pyramid.viewderivers import INGRESS
+ def deriv1(view, info): pass
+ self.assertRaises(
+ ConfigurationError, self.config.add_view_deriver, deriv1, INGRESS)
+
+ def test_add_deriver_enforces_ingress_is_first(self):
+ from pyramid.exceptions import ConfigurationError
+ from pyramid.viewderivers import INGRESS
+ def deriv1(view, info): pass
+ try:
+ self.config.add_view_deriver(deriv1, over=INGRESS)
+ except ConfigurationError as ex:
+ self.assertTrue('cannot be over INGRESS' in ex.args[0])
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_add_deriver_enforces_view_is_last(self):
+ from pyramid.exceptions import ConfigurationError
+ from pyramid.viewderivers import VIEW
+ def deriv1(view, info): pass
+ try:
+ self.config.add_view_deriver(deriv1, under=VIEW)
+ except ConfigurationError as ex:
+ self.assertTrue('cannot be under VIEW' in ex.args[0])
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_add_deriver_enforces_mapped_view_is_last(self):
+ from pyramid.exceptions import ConfigurationError
+ def deriv1(view, info): pass
+ try:
+ self.config.add_view_deriver(deriv1, 'deriv1', under='mapped_view')
+ except ConfigurationError as ex:
+ self.assertTrue('cannot be under "mapped_view"' in ex.args[0])
+ else: # pragma: no cover
+ raise AssertionError
+
+
+class TestDeriverIntegration(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ self.config = None
+ testing.tearDown()
+
+ def _getViewCallable(self, config, ctx_iface=None, request_iface=None,
+ name=''):
+ from zope.interface import Interface
+ from pyramid.interfaces import IRequest
+ from pyramid.interfaces import IView
+ from pyramid.interfaces import IViewClassifier
+ from pyramid.interfaces import IExceptionViewClassifier
+ classifier = IViewClassifier
+ if ctx_iface is None:
+ ctx_iface = Interface
+ if request_iface is None:
+ request_iface = IRequest
+ return config.registry.adapters.lookup(
+ (classifier, request_iface, ctx_iface), IView, name=name,
+ default=None)
+
+ def _makeRequest(self, config):
+ request = DummyRequest()
+ request.registry = config.registry
+ return request
+
+ def test_view_options(self):
+ response = DummyResponse()
+ view = lambda *arg: response
+ response.deriv = []
+
+ def deriv1(view, info):
+ response.deriv.append(info.options['deriv1'])
+ return view
+ deriv1.options = ('deriv1',)
+
+ def deriv2(view, info):
+ response.deriv.append(info.options['deriv2'])
+ return view
+ deriv2.options = ('deriv2',)
+
+ self.config.add_view_deriver(deriv1, 'deriv1')
+ self.config.add_view_deriver(deriv2, 'deriv2')
+ self.config.add_view(view, deriv1='test1', deriv2='test2')
+
+ wrapper = self._getViewCallable(self.config)
+ request = self._makeRequest(self.config)
+ request.method = 'GET'
+ self.assertEqual(wrapper(None, request), response)
+ self.assertEqual(['test1', 'test2'], response.deriv)
+
+ def test_unexpected_view_options(self):
+ from pyramid.exceptions import ConfigurationError
+ def deriv1(view, info): pass
+ self.config.add_view_deriver(deriv1, 'deriv1')
+ self.assertRaises(
+ ConfigurationError,
+ lambda: self.config.add_view(lambda r: {}, deriv1='test1'))
+
+@implementer(IResponse)
+class DummyResponse(object):
+ content_type = None
+ default_content_type = None
+ body = None
+
+class DummyRequest:
+ subpath = ()
+ matchdict = None
+ request_iface = IRequest
+
+ def __init__(self, environ=None):
+ if environ is None:
+ environ = {}
+ self.environ = environ
+ self.params = {}
+ self.cookies = {}
+ self.response = DummyResponse()
+
+class DummyLogger:
+ def __init__(self):
+ self.messages = []
+ def info(self, msg):
+ self.messages.append(msg)
+ warn = info
+ debug = info
+
+class DummySecurityPolicy:
+ def __init__(self, permitted=True):
+ self.permitted = permitted
+
+ def effective_principals(self, request):
+ return []
+
+ def permits(self, context, principals, permission):
+ return self.permitted
+
+def parse_httpdate(s):
+ import datetime
+ # cannot use %Z, must use literal GMT; Jython honors timezone
+ # but CPython does not
+ return datetime.datetime.strptime(s, "%a, %d %b %Y %H:%M:%S GMT")
+
+def assert_similar_datetime(one, two):
+ for attr in ('year', 'month', 'day', 'hour', 'minute'):
+ one_attr = getattr(one, attr)
+ two_attr = getattr(two, attr)
+ if not one_attr == two_attr: # pragma: no cover
+ raise AssertionError('%r != %r in %s' % (one_attr, two_attr, attr))
diff --git a/pyramid/util.py b/pyramid/util.py
index e1113e0ec..fc1d52af5 100644
--- a/pyramid/util.py
+++ b/pyramid/util.py
@@ -381,6 +381,9 @@ class TopologicalSorter(object):
self.first = first
self.last = last
+ def values(self):
+ return self.name2val.values()
+
def remove(self, name):
""" Remove a node from the sort input """
self.names.remove(name)
diff --git a/pyramid/viewderivers.py b/pyramid/viewderivers.py
new file mode 100644
index 000000000..8061e5d4a
--- /dev/null
+++ b/pyramid/viewderivers.py
@@ -0,0 +1,459 @@
+import inspect
+
+from zope.interface import (
+ implementer,
+ provider,
+ )
+
+from pyramid.security import NO_PERMISSION_REQUIRED
+from pyramid.response import Response
+
+from pyramid.interfaces import (
+ IAuthenticationPolicy,
+ IAuthorizationPolicy,
+ IDebugLogger,
+ IResponse,
+ IViewMapper,
+ IViewMapperFactory,
+ )
+
+from pyramid.compat import (
+ is_bound_method,
+ is_unbound_method,
+ )
+
+from pyramid.config.util import (
+ DEFAULT_PHASH,
+ MAX_ORDER,
+ takes_one_arg,
+ )
+
+from pyramid.exceptions import (
+ ConfigurationError,
+ PredicateMismatch,
+ )
+from pyramid.httpexceptions import HTTPForbidden
+from pyramid.util import object_description
+from pyramid.view import render_view_to_response
+from pyramid import renderers
+
+
+def view_description(view):
+ try:
+ return view.__text__
+ except AttributeError:
+ # custom view mappers might not add __text__
+ return object_description(view)
+
+def requestonly(view, attr=None):
+ return takes_one_arg(view, attr=attr, argname='request')
+
+@implementer(IViewMapper)
+@provider(IViewMapperFactory)
+class DefaultViewMapper(object):
+ def __init__(self, **kw):
+ self.attr = kw.get('attr')
+
+ def __call__(self, view):
+ if is_unbound_method(view) and self.attr is None:
+ raise ConfigurationError((
+ 'Unbound method calls are not supported, please set the class '
+ 'as your `view` and the method as your `attr`'
+ ))
+
+ if inspect.isclass(view):
+ view = self.map_class(view)
+ else:
+ view = self.map_nonclass(view)
+ return view
+
+ def map_class(self, view):
+ ronly = requestonly(view, self.attr)
+ if ronly:
+ mapped_view = self.map_class_requestonly(view)
+ else:
+ mapped_view = self.map_class_native(view)
+ mapped_view.__text__ = 'method %s of %s' % (
+ self.attr or '__call__', object_description(view))
+ return mapped_view
+
+ def map_nonclass(self, view):
+ # We do more work here than appears necessary to avoid wrapping the
+ # view unless it actually requires wrapping (to avoid function call
+ # overhead).
+ mapped_view = view
+ ronly = requestonly(view, self.attr)
+ if ronly:
+ mapped_view = self.map_nonclass_requestonly(view)
+ elif self.attr:
+ mapped_view = self.map_nonclass_attr(view)
+ if inspect.isroutine(mapped_view):
+ # This branch will be true if the view is a function or a method.
+ # We potentially mutate an unwrapped object here if it's a
+ # function. We do this to avoid function call overhead of
+ # injecting another wrapper. However, we must wrap if the
+ # function is a bound method because we can't set attributes on a
+ # bound method.
+ if is_bound_method(view):
+ _mapped_view = mapped_view
+ def mapped_view(context, request):
+ return _mapped_view(context, request)
+ if self.attr is not None:
+ mapped_view.__text__ = 'attr %s of %s' % (
+ self.attr, object_description(view))
+ else:
+ mapped_view.__text__ = object_description(view)
+ return mapped_view
+
+ def map_class_requestonly(self, view):
+ # its a class that has an __init__ which only accepts request
+ attr = self.attr
+ def _class_requestonly_view(context, request):
+ inst = view(request)
+ request.__view__ = inst
+ if attr is None:
+ response = inst()
+ else:
+ response = getattr(inst, attr)()
+ return response
+ return _class_requestonly_view
+
+ def map_class_native(self, view):
+ # its a class that has an __init__ which accepts both context and
+ # request
+ attr = self.attr
+ def _class_view(context, request):
+ inst = view(context, request)
+ request.__view__ = inst
+ if attr is None:
+ response = inst()
+ else:
+ response = getattr(inst, attr)()
+ return response
+ return _class_view
+
+ def map_nonclass_requestonly(self, view):
+ # its a function that has a __call__ which accepts only a single
+ # request argument
+ attr = self.attr
+ def _requestonly_view(context, request):
+ if attr is None:
+ response = view(request)
+ else:
+ response = getattr(view, attr)(request)
+ return response
+ return _requestonly_view
+
+ def map_nonclass_attr(self, view):
+ # its a function that has a __call__ which accepts both context and
+ # request, but still has an attr
+ def _attr_view(context, request):
+ response = getattr(view, self.attr)(context, request)
+ return response
+ return _attr_view
+
+
+def wraps_view(wrapper):
+ def inner(view, info):
+ wrapper_view = wrapper(view, info)
+ return preserve_view_attrs(view, wrapper_view)
+ return inner
+
+def preserve_view_attrs(view, wrapper):
+ if view is None:
+ return wrapper
+
+ if wrapper is view:
+ return view
+
+ original_view = getattr(view, '__original_view__', None)
+
+ if original_view is None:
+ original_view = view
+
+ wrapper.__wraps__ = view
+ wrapper.__original_view__ = original_view
+ wrapper.__module__ = view.__module__
+ wrapper.__doc__ = view.__doc__
+
+ try:
+ wrapper.__name__ = view.__name__
+ except AttributeError:
+ wrapper.__name__ = repr(view)
+
+ # attrs that may not exist on "view", but, if so, must be attached to
+ # "wrapped view"
+ for attr in ('__permitted__', '__call_permissive__', '__permission__',
+ '__predicated__', '__predicates__', '__accept__',
+ '__order__', '__text__'):
+ try:
+ setattr(wrapper, attr, getattr(view, attr))
+ except AttributeError:
+ pass
+
+ return wrapper
+
+def mapped_view(view, info):
+ mapper = info.options.get('mapper')
+ if mapper is None:
+ mapper = getattr(view, '__view_mapper__', None)
+ if mapper is None:
+ mapper = info.registry.queryUtility(IViewMapperFactory)
+ if mapper is None:
+ mapper = DefaultViewMapper
+
+ mapped_view = mapper(**info.options)(view)
+ return mapped_view
+
+mapped_view.options = ('mapper', 'attr')
+
+def owrapped_view(view, info):
+ wrapper_viewname = info.options.get('wrapper')
+ viewname = info.options.get('name')
+ if not wrapper_viewname:
+ return view
+ def _owrapped_view(context, request):
+ response = view(context, request)
+ request.wrapped_response = response
+ request.wrapped_body = response.body
+ request.wrapped_view = view
+ wrapped_response = render_view_to_response(context, request,
+ wrapper_viewname)
+ if wrapped_response is None:
+ raise ValueError(
+ 'No wrapper view named %r found when executing view '
+ 'named %r' % (wrapper_viewname, viewname))
+ return wrapped_response
+ return _owrapped_view
+
+owrapped_view.options = ('name', 'wrapper')
+
+def http_cached_view(view, info):
+ if info.settings.get('prevent_http_cache', False):
+ return view
+
+ seconds = info.options.get('http_cache')
+
+ if seconds is None:
+ return view
+
+ options = {}
+
+ if isinstance(seconds, (tuple, list)):
+ try:
+ seconds, options = seconds
+ except ValueError:
+ raise ConfigurationError(
+ 'If http_cache parameter is a tuple or list, it must be '
+ 'in the form (seconds, options); not %s' % (seconds,))
+
+ def wrapper(context, request):
+ response = view(context, request)
+ prevent_caching = getattr(response.cache_control, 'prevent_auto',
+ False)
+ if not prevent_caching:
+ response.cache_expires(seconds, **options)
+ return response
+
+ return wrapper
+
+http_cached_view.options = ('http_cache',)
+
+def secured_view(view, info):
+ for wrapper in (_secured_view, _authdebug_view):
+ view = wraps_view(wrapper)(view, info)
+ return view
+
+secured_view.options = ('permission',)
+
+def _secured_view(view, info):
+ permission = info.options.get('permission')
+ if permission == NO_PERMISSION_REQUIRED:
+ # allow views registered within configurations that have a
+ # default permission to explicitly override the default
+ # permission, replacing it with no permission at all
+ permission = None
+
+ wrapped_view = view
+ authn_policy = info.registry.queryUtility(IAuthenticationPolicy)
+ authz_policy = info.registry.queryUtility(IAuthorizationPolicy)
+
+ if authn_policy and authz_policy and (permission is not None):
+ def _permitted(context, request):
+ principals = authn_policy.effective_principals(request)
+ return authz_policy.permits(context, principals, permission)
+ def _secured_view(context, request):
+ result = _permitted(context, request)
+ if result:
+ return view(context, request)
+ view_name = getattr(view, '__name__', view)
+ msg = getattr(
+ request, 'authdebug_message',
+ 'Unauthorized: %s failed permission check' % view_name)
+ raise HTTPForbidden(msg, result=result)
+ _secured_view.__call_permissive__ = view
+ _secured_view.__permitted__ = _permitted
+ _secured_view.__permission__ = permission
+ wrapped_view = _secured_view
+
+ return wrapped_view
+
+def _authdebug_view(view, info):
+ wrapped_view = view
+ settings = info.settings
+ permission = info.options.get('permission')
+ authn_policy = info.registry.queryUtility(IAuthenticationPolicy)
+ authz_policy = info.registry.queryUtility(IAuthorizationPolicy)
+ logger = info.registry.queryUtility(IDebugLogger)
+ if settings and settings.get('debug_authorization', False):
+ def _authdebug_view(context, request):
+ view_name = getattr(request, 'view_name', None)
+
+ if authn_policy and authz_policy:
+ if permission is NO_PERMISSION_REQUIRED:
+ msg = 'Allowed (NO_PERMISSION_REQUIRED)'
+ elif permission is None:
+ msg = 'Allowed (no permission registered)'
+ else:
+ principals = authn_policy.effective_principals(request)
+ msg = str(authz_policy.permits(
+ context, principals, permission))
+ else:
+ msg = 'Allowed (no authorization policy in use)'
+
+ view_name = getattr(request, 'view_name', None)
+ url = getattr(request, 'url', None)
+ msg = ('debug_authorization of url %s (view name %r against '
+ 'context %r): %s' % (url, view_name, context, msg))
+ if logger:
+ logger.debug(msg)
+ if request is not None:
+ request.authdebug_message = msg
+ return view(context, request)
+
+ wrapped_view = _authdebug_view
+
+ return wrapped_view
+
+def predicated_view(view, info):
+ preds = info.predicates
+ if not preds:
+ return view
+ def predicate_wrapper(context, request):
+ for predicate in preds:
+ if not predicate(context, request):
+ view_name = getattr(view, '__name__', view)
+ raise PredicateMismatch(
+ 'predicate mismatch for view %s (%s)' % (
+ view_name, predicate.text()))
+ return view(context, request)
+ def checker(context, request):
+ return all((predicate(context, request) for predicate in
+ preds))
+ predicate_wrapper.__predicated__ = checker
+ predicate_wrapper.__predicates__ = preds
+ return predicate_wrapper
+
+def attr_wrapped_view(view, info):
+ accept, order, phash = (info.options.get('accept', None),
+ getattr(info, 'order', MAX_ORDER),
+ getattr(info, 'phash', DEFAULT_PHASH))
+ # this is a little silly but we don't want to decorate the original
+ # function with attributes that indicate accept, order, and phash,
+ # so we use a wrapper
+ if (
+ (accept is None) and
+ (order == MAX_ORDER) and
+ (phash == DEFAULT_PHASH)
+ ):
+ return view # defaults
+ def attr_view(context, request):
+ return view(context, request)
+ attr_view.__accept__ = accept
+ attr_view.__order__ = order
+ attr_view.__phash__ = phash
+ attr_view.__view_attr__ = info.options.get('attr')
+ attr_view.__permission__ = info.options.get('permission')
+ return attr_view
+
+attr_wrapped_view.options = ('accept', 'attr', 'permission')
+
+def rendered_view(view, info):
+ # one way or another this wrapper must produce a Response (unless
+ # the renderer is a NullRendererHelper)
+ renderer = info.options.get('renderer')
+ if renderer is None:
+ # register a default renderer if you want super-dynamic
+ # rendering. registering a default renderer will also allow
+ # override_renderer to work if a renderer is left unspecified for
+ # a view registration.
+ def viewresult_to_response(context, request):
+ result = view(context, request)
+ if result.__class__ is Response: # common case
+ response = result
+ else:
+ response = info.registry.queryAdapterOrSelf(result, IResponse)
+ if response is None:
+ if result is None:
+ append = (' You may have forgotten to return a value '
+ 'from the view callable.')
+ elif isinstance(result, dict):
+ append = (' You may have forgotten to define a '
+ 'renderer in the view configuration.')
+ else:
+ append = ''
+
+ msg = ('Could not convert return value of the view '
+ 'callable %s into a response object. '
+ 'The value returned was %r.' + append)
+
+ raise ValueError(msg % (view_description(view), result))
+
+ return response
+
+ return viewresult_to_response
+
+ if renderer is renderers.null_renderer:
+ return view
+
+ def rendered_view(context, request):
+ result = view(context, request)
+ if result.__class__ is Response: # potential common case
+ response = result
+ else:
+ # this must adapt, it can't do a simple interface check
+ # (avoid trying to render webob responses)
+ response = info.registry.queryAdapterOrSelf(result, IResponse)
+ if response is None:
+ attrs = getattr(request, '__dict__', {})
+ if 'override_renderer' in attrs:
+ # renderer overridden by newrequest event or other
+ renderer_name = attrs.pop('override_renderer')
+ view_renderer = renderers.RendererHelper(
+ name=renderer_name,
+ package=info.package,
+ registry=info.registry)
+ else:
+ view_renderer = renderer.clone()
+ if '__view__' in attrs:
+ view_inst = attrs.pop('__view__')
+ else:
+ view_inst = getattr(view, '__original_view__', view)
+ response = view_renderer.render_view(
+ request, result, view_inst, context)
+ return response
+
+ return rendered_view
+
+rendered_view.options = ('renderer',)
+
+def decorated_view(view, info):
+ decorator = info.options.get('decorator')
+ if decorator is None:
+ return view
+ return decorator(view)
+
+decorated_view.options = ('decorator',)
+
+VIEW = 'VIEW'
+INGRESS = 'INGRESS'