diff options
| author | cewing <cris@crisewing.com> | 2017-05-22 12:09:41 -0700 |
|---|---|---|
| committer | cewing <cris@crisewing.com> | 2017-05-22 12:09:41 -0700 |
| commit | b033b966420b673bf0222c3576d3238773433d0f (patch) | |
| tree | ba5d63e118ba5ebbad901b5c1558adff04224686 /docs | |
| parent | 7c680930d09d20bfa05249e01553e6488e61f1ca (diff) | |
| parent | 8c4d422965b633f31967ceed1e6cc25cc616d0bf (diff) | |
| download | pyramid-b033b966420b673bf0222c3576d3238773433d0f.tar.gz pyramid-b033b966420b673bf0222c3576d3238773433d0f.tar.bz2 pyramid-b033b966420b673bf0222c3576d3238773433d0f.zip | |
Merge branch 'master' into issue.2614
Diffstat (limited to 'docs')
387 files changed, 5387 insertions, 3501 deletions
diff --git a/docs/_static/pyramid_request_processing.graffle b/docs/_static/pyramid_request_processing.graffle index 56e4e13f2..5ccbf4ea4 100644 --- a/docs/_static/pyramid_request_processing.graffle +++ b/docs/_static/pyramid_request_processing.graffle @@ -59,8 +59,128 @@ <key>GraphicsList</key> <array> <dict> + <key>Class</key> + <string>LineGraphic</string> + <key>ControlPoints</key> + <array> + <string>{0, 6.9840087890625}</string> + <string>{0, -9}</string> + </array> + <key>FontInfo</key> + <dict> + <key>Color</key> + <dict> + <key>w</key> + <string>0</string> + </dict> + <key>Font</key> + <string>Helvetica</string> + <key>Size</key> + <real>12</real> + </dict> + <key>Head</key> + <dict> + <key>ID</key> + <integer>169378</integer> + <key>Info</key> + <integer>2</integer> + </dict> + <key>ID</key> + <integer>169517</integer> + <key>Layer</key> + <integer>0</integer> + <key>Points</key> + <array> + <string>{155.00000381469727, 109.82143211364746}</string> + <string>{155.00000254313238, 133.68303707668386}</string> + </array> + <key>Style</key> + <dict> + <key>stroke</key> + <dict> + <key>Bezier</key> + <true/> + <key>Color</key> + <dict> + <key>b</key> + <string>0.0980392</string> + <key>g</key> + <string>0.0980392</string> + <key>r</key> + <string>0.0980392</string> + </dict> + <key>HeadArrow</key> + <string>SharpArrow</string> + <key>Legacy</key> + <true/> + <key>LineType</key> + <integer>1</integer> + <key>TailArrow</key> + <string>0</string> + </dict> + </dict> + <key>Tail</key> + <dict> + <key>ID</key> + <integer>169516</integer> + </dict> + </dict> + <dict> + <key>Bounds</key> + <string>{{102.16667175292969, 87.276790618896484}, {105.66666412353516, 22.544641494750977}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>169516</integer> + <key>Layer</key> + <integer>0</integer> + <key>Magnets</key> + <array> + <string>{0, 1}</string> + <string>{0, -1}</string> + <string>{1, 0}</string> + <string>{-1, 0}</string> + </array> + <key>Shape</key> + <string>Rectangle</string> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>Color</key> + <dict> + <key>b</key> + <string>0.999208</string> + <key>g</key> + <string>0.811343</string> + <key>r</key> + <string>0.644457</string> + </dict> + </dict> + <key>shadow</key> + <dict> + <key>Draws</key> + <string>NO</string> + <key>ShadowVector</key> + <string>{2, 2}</string> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc + +\f0\fs20 \cf0 execution policy}</string> + <key>VerticalPad</key> + <integer>0</integer> + </dict> + </dict> + <dict> <key>Bounds</key> - <string>{{238.74999618530273, 294.65604172230951}, {105.66668701171875, 18.656048080136394}}</string> + <string>{{238.74999618530273, 325.15604172230951}, {105.66668701171875, 18.656048080136394}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>FontInfo</key> @@ -146,8 +266,8 @@ <integer>0</integer> <key>Points</key> <array> - <string>{154.9999760464211, 209.11365574251681}</string> - <string>{239.8333613077798, 209.14732074737549}</string> + <string>{154.99997604642331, 241.21656873817005}</string> + <string>{239.8333613077798, 240.64732074737549}</string> </array> <key>Style</key> <dict> @@ -182,7 +302,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{239.83336130777977, 197.875}, {105.66666412353516, 22.544641494750977}}</string> + <string>{{239.83336130777977, 229.375}, {105.66666412353516, 22.544641494750977}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>ID</key> @@ -259,8 +379,8 @@ <integer>0</integer> <key>Points</key> <array> - <string>{344.41668319702148, 411.88506673894034}</string> - <string>{375.5, 411.77232108797347}</string> + <string>{344.41668319702148, 442.38506673894034}</string> + <string>{375.5, 442.27232108797347}</string> </array> <key>Style</key> <dict> @@ -317,8 +437,8 @@ <integer>0</integer> <key>Points</key> <array> - <string>{155.00000254313238, 459.27667544230695}</string> - <string>{238.5002713470962, 456.52468399152298}</string> + <string>{155.00000254313238, 489.77667544230695}</string> + <string>{238.5002713470962, 487.02468399152298}</string> </array> <key>Style</key> <dict> @@ -377,8 +497,8 @@ <integer>0</integer> <key>Points</key> <array> - <string>{155.00000254313238, 482.12574895537085}</string> - <string>{238.52297468463752, 508.35839132916635}</string> + <string>{155.00000254313238, 512.6257489553708}</string> + <string>{238.52297468463752, 538.85839132916635}</string> </array> <key>Style</key> <dict> @@ -413,7 +533,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{238.74999618530273, 275.99999999999994}, {105.75002924601222, 18.656048080136394}}</string> + <string>{{238.74999618530273, 306.49999999999994}, {105.75002924601222, 18.656048080136394}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>FontInfo</key> @@ -475,7 +595,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{238.74999618530273, 421.15071036499205}, {105.66668701171875, 18.656048080136394}}</string> + <string>{{238.74999618530273, 451.65071036499205}, {105.66668701171875, 18.656048080136394}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>FontInfo</key> @@ -537,7 +657,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{238.74999618530273, 312.65604172230951}, {105.66668701171875, 18.656048080136394}}</string> + <string>{{238.74999618530273, 343.15604172230951}, {105.66668701171875, 18.656048080136394}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>FontInfo</key> @@ -599,7 +719,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{238.74999618530273, 402.55704269887212}, {105.66668701171875, 18.656048080136394}}</string> + <string>{{238.74999618530273, 433.05704269887212}, {105.66668701171875, 18.656048080136394}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>FontInfo</key> @@ -659,7 +779,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{238.74999618530273, 383.90099016834085}, {105.66668701171875, 18.656048080136394}}</string> + <string>{{238.74999618530273, 414.40099016834085}, {105.66668701171875, 18.656048080136394}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>FontInfo</key> @@ -719,7 +839,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{238.74999618530273, 350.36561209044055}, {105.66668701171875, 33.089282989501953}}</string> + <string>{{238.74999618530273, 380.86561209044055}, {105.66668701171875, 33.089282989501953}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>FontInfo</key> @@ -779,7 +899,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{238.74999618530273, 331.26348241170439}, {105.66668701171875, 18.656048080136394}}</string> + <string>{{238.74999618530273, 361.76348241170439}, {105.66668701171875, 18.656048080136394}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>FontInfo</key> @@ -865,8 +985,8 @@ <integer>0</integer> <key>Points</key> <array> - <string>{155.00000254313238, 470.25295298442387}</string> - <string>{238.33861159880226, 482.4262543949045}</string> + <string>{155.00000254313238, 500.75295298442387}</string> + <string>{238.33861159880226, 512.9262543949045}</string> </array> <key>Style</key> <dict> @@ -901,7 +1021,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{238.83336130777977, 471.22620192028251}, {105.66666412353516, 22.544641494750977}}</string> + <string>{{238.83336130777977, 501.72620192028251}, {105.66666412353516, 22.544641494750977}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>ID</key> @@ -980,8 +1100,8 @@ <integer>0</integer> <key>Points</key> <array> - <string>{154.99998733539806, 128.68025330008533}</string> - <string>{239.83340199788393, 128.59152244387357}</string> + <string>{154.99998724282403, 165.00131810958592}</string> + <string>{239.83340199788393, 164.59152244387357}</string> </array> <key>Style</key> <dict> @@ -1016,7 +1136,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{239.83340199788395, 117.31920169649808}, {105.66666412353516, 22.544641494750977}}</string> + <string>{{239.83340199788395, 153.31920169649808}, {105.66666412353516, 22.544641494750977}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>ID</key> @@ -1074,7 +1194,7 @@ <array> <dict> <key>Bounds</key> - <string>{{102.1666056315114, 148.28868579864499}, {105.66669464111328, 33.08929443359375}}</string> + <string>{{102.1666056315114, 181.78868579864499}, {105.66669464111328, 33.08929443359375}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>ID</key> @@ -1125,7 +1245,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{102.1666056315114, 181.37798023223874}, {105.66669464111328, 17.244049072265625}}</string> + <string>{{102.1666056315114, 214.87798023223874}, {105.66669464111328, 17.244049072265625}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>ID</key> @@ -1193,7 +1313,7 @@ <array> <dict> <key>Bounds</key> - <string>{{102.16666158040482, 272}, {105.66666412353516, 33.08929443359375}}</string> + <string>{{102.16666158040482, 302.5}, {105.66666412353516, 33.08929443359375}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>ID</key> @@ -1244,7 +1364,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{102.16666158040482, 305.08929443359375}, {105.66666412353516, 17.244049072265625}}</string> + <string>{{102.16666158040482, 335.58929443359375}, {105.66666412353516, 17.244049072265625}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>ID</key> @@ -1321,8 +1441,8 @@ <integer>0</integer> <key>Points</key> <array> - <string>{238.74999618530282, 439.80675844512831}</string> - <string>{207.66666666666765, 385.656005859375}</string> + <string>{238.74999618530282, 470.30675844512831}</string> + <string>{207.66666666666765, 416.156005859375}</string> </array> <key>Style</key> <dict> @@ -1371,8 +1491,8 @@ <integer>0</integer> <key>Points</key> <array> - <string>{239.25039065750093, 276.57837549845181}</string> - <string>{207.66666666666777, 353.07514659563753}</string> + <string>{239.25039065750093, 307.07837549845181}</string> + <string>{207.66666666666777, 383.57514659563753}</string> </array> <key>Style</key> <dict> @@ -1436,8 +1556,8 @@ <integer>0</integer> <key>Points</key> <array> - <string>{155.00000254313238, 386.66442959065108}</string> - <string>{155.00000254313238, 422.21209462483216}</string> + <string>{155.00000254313238, 417.16442959065108}</string> + <string>{155.00000254313238, 452.71209462483216}</string> </array> <key>Style</key> <dict> @@ -1472,7 +1592,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{102.16667048136482, 353.07514659563753}, {105.66666412353516, 33.089282989501953}}</string> + <string>{{102.16667048136482, 383.57514659563753}, {105.66666412353516, 33.089282989501953}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>ID</key> @@ -1555,8 +1675,8 @@ <integer>0</integer> <key>Points</key> <array> - <string>{154.9999936421724, 258.44082431579938}</string> - <string>{238.8333613077798, 258.45536063967575}</string> + <string>{154.9999936421724, 288.94082431579938}</string> + <string>{238.8333613077798, 288.95536063967575}</string> </array> <key>Style</key> <dict> @@ -1969,7 +2089,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{375.5, 400.5}, {105.66666412353516, 22.544642175946908}}</string> + <string>{{375.5, 431}, {105.66666412353516, 22.544642175946908}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>ID</key> @@ -2053,8 +2173,8 @@ <integer>0</integer> <key>Points</key> <array> - <string>{155.00000170434049, 119.22767858295661}</string> - <string>{154.99995295206804, 148.28868579864499}</string> + <string>{155.00000157307935, 156.72767858475962}</string> + <string>{154.99995295206804, 181.78868579864499}</string> </array> <key>Style</key> <dict> @@ -2110,7 +2230,9 @@ <key>Head</key> <dict> <key>ID</key> - <integer>169378</integer> + <integer>169516</integer> + <key>Info</key> + <integer>2</integer> </dict> <key>ID</key> <integer>169385</integer> @@ -2119,7 +2241,7 @@ <key>Points</key> <array> <string>{155.00000254313238, 67.727678571434836}</string> - <string>{155.00000254313238, 96.18303707668386}</string> + <string>{155.00000381469727, 87.276790618896484}</string> </array> <key>Style</key> <dict> @@ -2156,7 +2278,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{102.16667048136482, 509.6179466247504}, {105.66666412353516, 22.544641494750977}}</string> + <string>{{102.16667048136482, 540.1179466247504}, {105.66666412353516, 22.544641494750977}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>ID</key> @@ -2209,7 +2331,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{239, 497.23589324949899}, {105.66666412353516, 22.544641494750977}}</string> + <string>{{239, 527.73589324949899}, {105.66666412353516, 22.544641494750977}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>ID</key> @@ -2262,7 +2384,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{239, 445.23589324949717}, {105.66666412353516, 22.544641494750977}}</string> + <string>{{239, 475.73589324949717}, {105.66666412353516, 22.544641494750977}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>ID</key> @@ -2315,7 +2437,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{102.16667048136482, 422.21209462483216}, {105.66666412353516, 22.544641494750977}}</string> + <string>{{102.16667048136482, 452.71209462483216}, {105.66666412353516, 22.544641494750977}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>ID</key> @@ -2368,7 +2490,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{238.83336130777977, 247.18303989230026}, {105.66666412353516, 22.544641494750977}}</string> + <string>{{238.83336130777977, 277.68303989230026}, {105.66666412353516, 22.544641494750977}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>ID</key> @@ -2421,7 +2543,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{102.16667048136482, 222.18303707668389}, {105.66666412353516, 22.544641494750977}}</string> + <string>{{102.16667048136482, 252.68303707668389}, {105.66666412353516, 22.544641494750977}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>ID</key> @@ -2474,7 +2596,7 @@ </dict> <dict> <key>Bounds</key> - <string>{{102.16667048136482, 96.18303707668386}, {105.66666412353516, 22.544641494750977}}</string> + <string>{{102.16667048136482, 133.68303707668386}, {105.66666412353516, 22.544641494750977}}</string> <key>Class</key> <string>ShapedGraphic</string> <key>ID</key> @@ -2609,8 +2731,8 @@ <integer>0</integer> <key>Points</key> <array> - <string>{154.99995295206804, 198.62202930450437}</string> - <string>{155.00000254313238, 222.18303707668389}</string> + <string>{154.99995295206804, 232.12202930450437}</string> + <string>{155.00000254313238, 252.68303707668389}</string> </array> <key>Style</key> <dict> @@ -2678,8 +2800,8 @@ <integer>0</integer> <key>Points</key> <array> - <string>{154.9999936421724, 245.22767856643924}</string> - <string>{154.9999936421724, 272}</string> + <string>{154.9999936421724, 275.72767856643924}</string> + <string>{154.9999936421724, 302.5}</string> </array> <key>Style</key> <dict> @@ -2743,8 +2865,8 @@ <integer>0</integer> <key>Points</key> <array> - <string>{154.9999936421724, 322.33334350585938}</string> - <string>{155.00000254313238, 353.07514659563753}</string> + <string>{154.9999936421724, 352.83334350585938}</string> + <string>{155.00000254313238, 383.57514659563753}</string> </array> <key>Style</key> <dict> @@ -2812,8 +2934,8 @@ <integer>0</integer> <key>Points</key> <array> - <string>{155.00000254313238, 444.75673611958314}</string> - <string>{155.00000254313238, 509.6179466247504}</string> + <string>{155.00000254313238, 475.25673611958314}</string> + <string>{155.00000254313238, 540.1179466247504}</string> </array> <key>Style</key> <dict> @@ -9868,7 +9990,7 @@ <key>MasterSheets</key> <array/> <key>ModificationDate</key> - <string>2016-04-13 08:32:47 +0000</string> + <string>2017-04-10 09:33:14 +0000</string> <key>Modifier</key> <string>Steve Piercy</string> <key>NotesVisible</key> @@ -9949,7 +10071,7 @@ </dict> </array> <key>Frame</key> - <string>{{35, 93}, {2284, 1325}}</string> + <string>{{35, 93}, {1632, 1325}}</string> <key>ListView</key> <true/> <key>OutlineWidth</key> @@ -9963,15 +10085,15 @@ <key>SidebarWidth</key> <integer>163</integer> <key>VisibleRegion</key> - <string>{{110.125, 77.875}, {239.125, 146.375}}</string> + <string>{{-27, 33.5}, {630.5, 593}}</string> <key>Zoom</key> - <real>8</real> + <real>2</real> <key>ZoomValues</key> <array> <array> <string>Request Processing</string> + <real>2</real> <real>8</real> - <real>4</real> </array> </array> </dict> diff --git a/docs/_static/pyramid_request_processing.png b/docs/_static/pyramid_request_processing.png Binary files differindex 2f44f4824..d62b172b0 100644 --- a/docs/_static/pyramid_request_processing.png +++ b/docs/_static/pyramid_request_processing.png diff --git a/docs/_static/pyramid_request_processing.svg b/docs/_static/pyramid_request_processing.svg index 03f6d56fa..38fa7348d 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>2016-04-13 08:32Z</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="400.5" width="105.666664" height="22.544642" fill="#dfbeff"/><rect x="375.5" y="400.5" width="105.666664" height="22.544642" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(380.5 405.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="276.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="439.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="331.26348" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="331.26348" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 334.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="350.36561" width="105.66669" height="33.089283" fill="#ffff6c"/><rect x="238.75" y="350.36561" width="105.66669" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 360.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="383.901" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="383.901" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 387.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="402.55704" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="402.55704" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 405.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="312.65604" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="312.65604" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 315.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="421.1507" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="421.1507" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 424.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="276" width="105.75003" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="276" width="105.75003" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 279.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="411.88507" x2="375.5" y2="411.77232" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="239.83336" y="197.875" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="239.83336" y="197.875" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244.83336 203.14732)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="12.44759" y="10" textLength="35.57129">BeforeT</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="47.652668" y="10" textLength="35.566406">raversal</tspan></text><line x1="154.99998" y1="209.11366" x2="239.83336" y2="209.14732" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.75" y="294.65604" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="294.65604" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 297.98407)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="17.27182" y="10" textLength="61.123047">CSRF checks</tspan></text></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 563" width="424pt" height="563pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2017-04-10 09:33Z</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 475.25674 C 155 481.14061 155 516.7592 155 533.21617" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 352.83334 C 154.99999 358.22413 155 368.24646 155 376.6775" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 275.72768 C 154.99999 281.0417 154.99999 288.43189 154.99999 295.60145" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99995 232.12203 C 154.99995 237.07358 154.99998 240.81523 154.99999 245.78103" 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="133.68304" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="133.68304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 138.95536)" 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="252.68304" width="105.666664" height="22.544641" fill="#d2ffd0"/><rect x="102.16667" y="252.68304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 257.95536)" 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="277.68304" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="277.68304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 282.95536)" 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="452.7121" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="452.7121" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 457.98442)" 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="475.7359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="475.7359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 481.0082)" 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="527.7359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="527.7359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 533.0082)" 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="540.11795" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="540.11795" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 545.39027)" 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 72.60557 155 75.81567 155 80.374542" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 155 156.72768 C 155 161.9728 154.999975 168.29494 154.99996 174.89142" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="375.5" y="431" width="105.666664" height="22.544642" fill="#dfbeff"/><rect x="375.5" y="431" width="105.666664" height="22.544642" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(380.5 436.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="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="288.94082" x2="238.83336" y2="288.95536" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="102.16667" y="383.57515" width="105.666664" height="33.089283" fill="#ffff6c"/><rect x="102.16667" y="383.57515" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 394.11979)" 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 417.16443 C 155 422.67252 155 436.0052 155 445.80935" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="239.25039" y1="307.07838" x2="207.66667" y2="383.57515" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><line x1="238.75" y1="470.30676" x2="207.66667" y2="416.156" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><rect x="102.16666" y="335.5893" width="105.666664" height="17.244049" fill="#d2ffd0"/><rect x="102.16666" y="335.5893" width="105.666664" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 338.21132)" 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="302.5" width="105.666664" height="33.089294" fill="#d2ffd0"/><rect x="102.16666" y="302.5" width="105.666664" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 313.04465)" 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="214.87798" width="105.666695" height="17.244049" fill="#d2ffd0"/><rect x="102.166606" y="214.87798" width="105.666695" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 217.5)" 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="181.78869" width="105.666695" height="33.089294" fill="#d2ffd0"/><rect x="102.166606" y="181.78869" width="105.666695" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 192.33333)" 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="153.3192" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="239.8334" y="153.3192" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244.8334 158.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="165.00132" x2="239.8334" y2="164.59152" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.83336" y="501.7262" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="501.7262" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 506.99852)" 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="500.75295" x2="238.33861" y2="512.92625" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.75" y="361.76348" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="361.76348" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 365.0915)" 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="380.86561" width="105.66669" height="33.089283" fill="#ffff6c"/><rect x="238.75" y="380.86561" width="105.66669" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 391.41025)" 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="414.401" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="414.401" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 417.72901)" 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="433.05704" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="433.05704" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 436.38507)" 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="343.15604" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="343.15604" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 346.48407)" 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="451.6507" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="451.6507" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 454.97873)" 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="306.5" width="105.75003" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="306.5" width="105.75003" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 309.82802)" 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="512.62575" x2="238.52297" y2="538.8584" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="155" y1="489.77668" x2="238.50027" y2="487.02468" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="344.41668" y1="442.38507" x2="375.5" y2="442.27232" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="239.83336" y="229.375" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="239.83336" y="229.375" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244.83336 234.64732)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="12.44759" y="10" textLength="35.57129">BeforeT</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="47.652668" y="10" textLength="35.566406">raversal</tspan></text><line x1="154.99998" y1="241.21657" x2="239.83336" y2="240.64732" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.75" y="325.15604" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="325.15604" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 328.48407)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="17.27182" y="10" textLength="61.123047">CSRF checks</tspan></text><rect x="102.16667" y="87.27679" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="87.27679" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 92.54911)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="12.25716" y="10" textLength="71.152344">execution policy</tspan></text><path d="M 155 109.82143 C 155 114.961226 155 120.58499 155 126.78393" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g></g></svg> diff --git a/docs/api/config.rst b/docs/api/config.rst index ab3ff0fe1..a785b64ad 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -26,6 +26,7 @@ .. automethod:: add_view .. automethod:: add_notfound_view .. automethod:: add_forbidden_view + .. automethod:: add_exception_view :methodcategory:`Adding an Event Subscriber` @@ -36,6 +37,7 @@ .. automethod:: set_authentication_policy .. automethod:: set_authorization_policy .. automethod:: set_default_csrf_options + .. automethod:: set_csrf_storage_policy .. automethod:: set_default_permission .. automethod:: add_permission @@ -69,6 +71,7 @@ .. automethod:: add_subscriber_predicate .. automethod:: add_view_predicate .. automethod:: add_view_deriver + .. automethod:: set_execution_policy .. automethod:: set_request_factory .. automethod:: set_root_factory .. automethod:: set_session_factory diff --git a/docs/api/csrf.rst b/docs/api/csrf.rst new file mode 100644 index 000000000..38501546e --- /dev/null +++ b/docs/api/csrf.rst @@ -0,0 +1,23 @@ +.. _csrf_module: + +:mod:`pyramid.csrf` +------------------- + +.. automodule:: pyramid.csrf + + .. autoclass:: LegacySessionCSRFStoragePolicy + :members: + + .. autoclass:: SessionCSRFStoragePolicy + :members: + + .. autoclass:: CookieCSRFStoragePolicy + :members: + + .. autofunction:: get_csrf_token + + .. autofunction:: new_csrf_token + + .. autofunction:: check_csrf_origin + + .. autofunction:: check_csrf_token diff --git a/docs/api/i18n.rst b/docs/api/i18n.rst index 3b9abbc1d..7a61246df 100644 --- a/docs/api/i18n.rst +++ b/docs/api/i18n.rst @@ -6,6 +6,7 @@ .. automodule:: pyramid.i18n .. autoclass:: TranslationString + :noindex: .. autofunction:: TranslationStringFactory diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index 521d65d2b..e542a6be0 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -44,6 +44,9 @@ Other Interfaces .. autointerface:: IRoutePregenerator :members: + .. autointerface:: ICSRFStoragePolicy + :members: + .. autointerface:: ISession :members: @@ -65,6 +68,9 @@ Other Interfaces .. autointerface:: IResponseFactory :members: + .. autointerface:: IRouter + :members: + .. autointerface:: IViewMapperFactory :members: diff --git a/docs/api/paster.rst b/docs/api/paster.rst index 27bc81a1f..f0784d0f8 100644 --- a/docs/api/paster.rst +++ b/docs/api/paster.rst @@ -7,8 +7,8 @@ .. autofunction:: bootstrap - .. autofunction:: get_app(config_uri, name=None, options=None) + .. autofunction:: get_app - .. autofunction:: get_appsettings(config_uri, name=None, options=None) + .. autofunction:: get_appsettings - .. autofunction:: setup_logging(config_uri, global_conf=None) + .. autofunction:: setup_logging diff --git a/docs/api/session.rst b/docs/api/session.rst index 56c4f52d7..53bae7c52 100644 --- a/docs/api/session.rst +++ b/docs/api/session.rst @@ -9,10 +9,6 @@ .. autofunction:: signed_deserialize - .. autofunction:: check_csrf_origin - - .. autofunction:: check_csrf_token - .. autofunction:: SignedCookieSessionFactory .. autofunction:: UnencryptedCookieSessionFactoryConfig diff --git a/docs/api/view.rst b/docs/api/view.rst index d8e429552..e41212012 100644 --- a/docs/api/view.rst +++ b/docs/api/view.rst @@ -23,4 +23,6 @@ .. autoclass:: forbidden_view_config :members: + .. autoclass:: exception_view_config + :members: diff --git a/docs/conf.py b/docs/conf.py index c3a7170fc..e63019c63 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -49,12 +49,13 @@ book = os.environ.get('BOOK') # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ + 'repoze.sphinx.autointerface', 'sphinx.ext.autodoc', 'sphinx.ext.doctest', - 'repoze.sphinx.autointerface', - 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx', - 'sphinxcontrib.programoutput', + 'sphinx.ext.todo', + 'sphinx.ext.viewcode', + 'sphinxcontrib.autoprogram', # enable pylons_sphinx_latesturl when this branch is no longer "latest" # 'pylons_sphinx_latesturl', ] @@ -63,11 +64,14 @@ extensions = [ intersphinx_mapping = { 'colander': ('http://docs.pylonsproject.org/projects/colander/en/latest', None), 'cookbook': ('http://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/', None), + 'cookiecutter': ('https://cookiecutter.readthedocs.io/en/latest/', None), 'deform': ('http://docs.pylonsproject.org/projects/deform/en/latest', None), 'jinja2': ('http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/', None), + 'plaster': ('http://docs.pylonsproject.org/projects/plaster/en/latest/', None), 'pylonswebframework': ('http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/', None), 'python': ('https://docs.python.org/3', None), - 'pytest': ('http://pytest.org/latest/', None), + 'pytest': ('https://pytest.org/en/latest/', None), + 'sphinx': ('http://www.sphinx-doc.org/en/latest', None), 'sqla': ('http://docs.sqlalchemy.org/en/latest', None), 'tm': ('http://docs.pylonsproject.org/projects/pyramid-tm/en/latest/', None), 'toolbar': ('http://docs.pylonsproject.org/projects/pyramid-debugtoolbar/en/latest', None), @@ -78,7 +82,8 @@ intersphinx_mapping = { 'webtest': ('http://webtest.pythonpaste.org/en/latest', None), 'who': ('http://repozewho.readthedocs.org/en/latest', None), 'zcml': ('http://docs.pylonsproject.org/projects/pyramid-zcml/en/latest', None), - 'zcomponent': ('http://zopecomponent.readthedocs.io/en/stable/', None), + 'zcomponent': ('http://zopecomponent.readthedocs.io/en/latest/', None), + 'zinterface': ('http://zopeinterface.readthedocs.io/en/latest/', None), } @@ -119,6 +124,9 @@ exclude_patterns = ['_themes/README.rst', ] # unit titles (such as .. function::). add_module_names = False +# Add support for todo items +todo_include_todos = True + # The name of the Pygments (syntax highlighting) style to use. #pygments_style = book and 'bw' or 'tango' if book: @@ -139,6 +147,7 @@ if book: # 'whatsnew-1.5': 'index', # 'whatsnew-1.6': 'index', # 'whatsnew-1.7': 'index', +# 'whatsnew-1.8': 'index', # 'tutorials/gae/index': 'index', # 'api/chameleon_text': 'api', # 'api/chameleon_zpt': 'api', @@ -191,10 +200,10 @@ latex_documents = [ # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -latex_use_parts = True +latex_toplevel_sectioning = "section" # If false, no module index is generated. -latex_use_modindex = False +latex_domain_indices = False ## Say, for a moment that you have a twoside document that needs a 3cm ## inner margin to allow for binding and at least two centimetres the diff --git a/docs/conventions.rst b/docs/conventions.rst deleted file mode 100644 index de041da04..000000000 --- a/docs/conventions.rst +++ /dev/null @@ -1,107 +0,0 @@ -Typographical Conventions -========================= - -Literals, filenames, and function arguments are presented using the -following style: - - ``argument1`` - -Warnings which represent limitations and need-to-know information -related to a topic or concept are presented in the following style: - - .. warning:: - - This is a warning. - -Notes which represent additional information related to a topic or -concept are presented in the following style: - - .. note:: - - This is a note. - -We present Python method names using the following style: - - :meth:`pyramid.config.Configurator.add_view` - -We present Python class names, module names, attributes, and global -variables using the following style: - - :class:`pyramid.config.Configurator.registry` - -References to glossary terms are presented using the following style: - - :term:`Pylons` - -URLs are presented using the following style: - - `Pylons <http://www.pylonsproject.org>`_ - -References to sections and chapters are presented using the following -style: - - :ref:`traversal_chapter` - -Code and configuration file blocks are presented in the following style: - - .. code-block:: python - :linenos: - - def foo(abc): - pass - -Example blocks representing UNIX shell commands are prefixed with a ``$`` -character, e.g.: - - .. code-block:: bash - - $ $VENV/bin/py.test -q - -See :term:`venv` for the meaning of ``$VENV``. - -Example blocks representing Windows commands are prefixed with a drive letter -with an optional directory name, e.g.: - - .. code-block:: doscon - - c:\examples> %VENV%\Scripts\py.test -q - -See :term:`venv` for the meaning of ``%VENV%``. - -When a command that should be typed on one line is too long to fit on a page, -the backslash ``\`` is used to indicate that the following printed line should -be part of the command: - - .. code-block:: bash - - $VENV/bin/py.test tutorial/tests.py --cov-report term-missing \ - --cov=tutorial -q - -A sidebar, which presents a concept tangentially related to content discussed -on a page, is rendered like so: - -.. sidebar:: This is a sidebar - - Sidebar information. - -When multiple objects are imported from the same package, the following -convention is used: - - .. code-block:: python - - from foo import ( - bar, - baz, - ) - -It may look unusual, but it has advantages: - -* It allows one to swap out the higher-level package ``foo`` for something else - that provides the similar API. An example would be swapping out one database - for another (e.g., graduating from SQLite to PostgreSQL). - -* Looks more neat in cases where a large number of objects get imported from - that package. - -* Adding or removing imported objects from the package is quicker and results - in simpler diffs. diff --git a/docs/designdefense.rst b/docs/designdefense.rst index 5f65671bb..0a72ff27d 100644 --- a/docs/designdefense.rst +++ b/docs/designdefense.rst @@ -591,7 +591,7 @@ extensibility because it must be deployed in multiple locations. Pyramid Is Too Big ------------------ -"The :app:`Pyramid` compressed tarball is larger than 2MB. It must beenormous!" +"The :app:`Pyramid` compressed tarball is larger than 2MB. It must be enormous!" No. We just ship it with docs, test code, and scaffolding. Here's a breakdown of what's included in subdirectories of the package tree: diff --git a/docs/glossary.rst b/docs/glossary.rst index 9b41b4359..2e5276554 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -366,6 +366,14 @@ Glossary :term:`WSGI` components together declaratively within an ``.ini`` file. It was developed by Ian Bicking. + plaster + `plaster <http://docs.pylonsproject.org/projects/plaster/en/latest/>`_ is + a library used by :app:`Pyramid` which acts as an abstraction between + command-line scripts and the file format used to load the :term:`WSGI` + components and application settings. By default :app:`Pyramid` ships + with the ``plaster_pastedeploy`` library installed which provides + integrated support for loading a :term:`PasteDeploy` INI file. + Chameleon `chameleon <https://chameleon.readthedocs.org/en/latest/>`_ is an attribute language template compiler which supports the :term:`ZPT` @@ -568,7 +576,7 @@ Glossary :ref:`adding_and_overriding_renderers` for more information. mod_wsgi - `mod_wsgi <https://code.google.com/archive/p/modwsgi>`_ is an Apache + `mod_wsgi <https://modwsgi.readthedocs.io>`_ is an Apache module developed by Graham Dumpleton. It allows :term:`WSGI` applications (such as applications developed using :app:`Pyramid`) to be served using the Apache web server. @@ -891,6 +899,11 @@ Glossary :meth:`pyramid.config.Configurator.set_session_factory` for more information. + CSRF storage policy + A utility that implements :class:`pyramid.interfaces.ICSRFStoragePolicy` + which is responsible for allocating CSRF tokens to a user and verifying + that a provided token is acceptable. + Mako `Mako <http://www.makotemplates.org/>`_ is a template language which refines the familiar ideas of componentized layout and inheritance @@ -992,7 +1005,7 @@ Glossary pages rendered by your application, displaying request, routing, and database information. :mod:`pyramid_debugtoolbar` is configured into the ``development.ini`` of all applications which use a Pyramid - :term:`scaffold`. For more information, see + :term:`cookiecutter`. For more information, see http://docs.pylonsproject.org/projects/pyramid_debugtoolbar/en/latest/. scaffold @@ -1000,6 +1013,10 @@ Glossary application and helps users to quickly get started writing larger applications. Scaffolds are usually used via the ``pcreate`` command. + .. deprecated:: 1.8 + + .. seealso:: See also :term:`cookiecutter`. + pyramid_exclog A package which logs Pyramid application exception (error) information to a standard Python logger. This add-on is most useful when @@ -1058,8 +1075,8 @@ Glossary :class:`pyramid.interfaces.IAssetDescriptor`. Waitress - A :term:`WSGI` server that runs on UNIX and Windows under Python 2.6+ - and Python 3.2+. Projects generated via Pyramid scaffolding use + A :term:`WSGI` server that runs on UNIX and Windows under Python 2.7+ + and Python 3.3+. Projects generated via Pyramid cookiecutters use Waitress as a WGSI server. See http://docs.pylonsproject.org/projects/waitress/en/latest/ for detailed information. @@ -1133,4 +1150,25 @@ Glossary Python Packaging Authority The `Python Packaging Authority (PyPA) <https://www.pypa.io/en/latest/>`_ is a working group that maintains many of the relevant projects in Python - packaging.
\ No newline at end of file + packaging. + + cookiecutter + A command-line utility that creates projects from :ref:`cookiecutters <cookiecutter:readme>` (project templates), e.g., creating a Python package project from a Python package project template. + + Pyramid cookiecutters include: + + * `pyramid-cookiecutter-alchemy <https://github.com/Pylons/pyramid-cookiecutter-alchemy>`_ + * `pyramid-cookiecutter-starter <https://github.com/Pylons/pyramid-cookiecutter-starter>`_ + * `pyramid-cookiecutter-zodb <https://github.com/Pylons/pyramid-cookiecutter-zodb>`_ + + .. versionadded:: 1.8 + + .. seealso:: See also :term:`scaffold`. + + coverage + A measurement of code coverage, usually expressed as a percentage of which lines of code have been executed over which lines are executable, typically run during test execution. + + execution policy + A policy which wraps the :term:`router` by creating the request object + and sending it through the request pipeline. + See :class:`pyramid.config.Configurator.set_execution_policy`. diff --git a/docs/index.rst b/docs/index.rst index 02c35866a..7d3393548 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,7 +5,7 @@ The Pyramid Web Framework ========================= :app:`Pyramid` is a small, fast, down-to-earth Python web framework. It is -developed as part of the `Pylons Project <http://www.pylonsproject.org/>`_. +developed as part of the `Pylons Project <http://pylonsproject.org/>`_. It is licensed under a `BSD-like license <http://repoze.org/license.html>`_. Here is one of the simplest :app:`Pyramid` applications you can make: @@ -145,6 +145,7 @@ Narrative documentation in chapter form explaining how to use :app:`Pyramid`. narr/extending narr/advconfig narr/extconfig + narr/cookiecutters narr/scaffolding narr/upgrading narr/threadlocals @@ -184,6 +185,8 @@ Change History .. toctree:: :maxdepth: 1 + whatsnew-1.9 + whatsnew-1.8 whatsnew-1.7 whatsnew-1.6 whatsnew-1.5 @@ -213,13 +216,13 @@ Copyright, Trademarks, and Attributions copyright -Typographical Conventions -========================= +Typographical Conventions and Style Guide +========================================= .. toctree:: :maxdepth: 1 - conventions + typographical-conventions Index and Glossary diff --git a/docs/latexindex.rst b/docs/latexindex.rst index 05199d313..0a6b3400f 100644 --- a/docs/latexindex.rst +++ b/docs/latexindex.rst @@ -15,7 +15,7 @@ Front Matter :maxdepth: 1 copyright - conventions + typographical-conventions authorintro designdefense @@ -78,6 +78,7 @@ Narrative Documentation narr/extending narr/advconfig narr/extconfig + narr/cookiecutters narr/scaffolding narr/upgrading narr/threadlocals diff --git a/docs/narr/MyProject/CHANGES.txt b/docs/narr/MyProject/CHANGES.txt deleted file mode 100644 index 35a34f332..000000000 --- a/docs/narr/MyProject/CHANGES.txt +++ /dev/null @@ -1,4 +0,0 @@ -0.0 ---- - -- Initial version diff --git a/docs/narr/MyProject/README.txt b/docs/narr/MyProject/README.txt deleted file mode 100644 index 70759eba1..000000000 --- a/docs/narr/MyProject/README.txt +++ /dev/null @@ -1,12 +0,0 @@ -MyProject README -================== - -Getting Started ---------------- - -- cd <directory containing this file> - -- $VENV/bin/pip install -e . - -- $VENV/bin/pserve development.ini - diff --git a/docs/narr/MyProject/setup.py b/docs/narr/MyProject/setup.py deleted file mode 100644 index a911eff6d..000000000 --- a/docs/narr/MyProject/setup.py +++ /dev/null @@ -1,49 +0,0 @@ -import os - -from setuptools import setup, find_packages - -here = os.path.abspath(os.path.dirname(__file__)) -with open(os.path.join(here, 'README.txt')) as f: - README = f.read() -with open(os.path.join(here, 'CHANGES.txt')) as f: - CHANGES = f.read() - -requires = [ - 'pyramid', - 'pyramid_chameleon', - 'pyramid_debugtoolbar', - 'waitress', - ] - -tests_require = [ - 'WebTest >= 1.3.1', # py3 compat - 'pytest', # includes virtualenv - 'pytest-cov', - ] - -setup(name='MyProject', - version='0.0', - description='MyProject', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web pyramid pylons', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - extras_require={ - 'testing': tests_require, - }, - install_requires=requires, - entry_points="""\ - [paste.app_factory] - main = myproject:main - """, - ) diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index 58f547fc9..f5a2f9684 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -28,7 +28,7 @@ asset: The use of assets is quite common in most web development projects. For example, when you create a :app:`Pyramid` application using one of the -available scaffolds, as described in :ref:`creating_a_project`, the directory +available :term:`cookiecutter`\ s, as described in :ref:`creating_a_project`, the directory representing the application contains a Python :term:`package`. Within that Python package, there are directories full of files which are static assets. For example, there's a ``static`` directory which contains ``.css``, ``.js``, diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 242bc7ec7..98663cca6 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -839,7 +839,7 @@ In general, you can make your script into a console script by doing the following: - Use an existing distribution (such as one you've already created via - ``pcreate``) or create a new distribution that possesses at least one package + ``cookiecutter``) or create a new distribution that possesses at least one package or module. It should, within any module within the distribution, house a callable (usually a function) that takes no arguments and which runs any of the code you wish to run. diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst index cde166b21..ee54e3acd 100644 --- a/docs/narr/configuration.rst +++ b/docs/narr/configuration.rst @@ -54,7 +54,7 @@ configured imperatively: server.serve_forever() We won't talk much about what this application does yet. Just note that the -"configuration' statements take place underneath the ``if __name__ == +configuration statements take place underneath the ``if __name__ == '__main__':`` stanza in the form of method calls on a :term:`Configurator` object (e.g., ``config.add_view(...)``). These statements take place one after the other, and are executed in order, so the full power of Python, including diff --git a/docs/narr/cookiecutters.rst b/docs/narr/cookiecutters.rst new file mode 100644 index 000000000..abedb25b9 --- /dev/null +++ b/docs/narr/cookiecutters.rst @@ -0,0 +1,22 @@ +.. _cookiecutters: + +Pyramid cookiecutters +===================== + +.. versionadded:: 1.8 + +A :term:`cookiecutter` is a command-line utility that creates projects from :ref:`cookiecutters <cookiecutter:readme>` (project templates), e.g., creating a Python package project from a Python package project template. + +Pyramid cookiecutters have replaced the now deprecated Pyramid scaffolds, and should be used going forward. Pyramid cookiecutters released under the Pylons Project include: + +* `pyramid-cookiecutter-alchemy <https://github.com/Pylons/pyramid-cookiecutter-alchemy>`_ +* `pyramid-cookiecutter-starter <https://github.com/Pylons/pyramid-cookiecutter-starter>`_ +* `pyramid-cookiecutter-zodb <https://github.com/Pylons/pyramid-cookiecutter-zodb>`_ + +.. seealso:: + + See also `Cookiecutter Installation <https://cookiecutter.readthedocs.io/en/latest/installation.html>`_ and `Cookiecutter Features <https://cookiecutter.readthedocs.io/en/latest/readme.html#features>`_. Development of cookiecutters is documented under `Learn the Basics of Cookiecutter by Creating a Cookiecutter <https://cookiecutter.readthedocs.io/en/latest/first_steps.html>`_. + +.. seealso:: + + See also :term:`scaffold`. diff --git a/docs/narr/extconfig.rst b/docs/narr/extconfig.rst index babfa0a98..4009ec1dc 100644 --- a/docs/narr/extconfig.rst +++ b/docs/narr/extconfig.rst @@ -260,6 +260,7 @@ Pre-defined Phases - :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.override_asset` - :meth:`pyramid.config.Configurator.set_authorization_policy` - :meth:`pyramid.config.Configurator.set_default_csrf_options` - :meth:`pyramid.config.Configurator.set_default_permission` diff --git a/docs/narr/extending.rst b/docs/narr/extending.rst index 9dc042024..bee30ec1a 100644 --- a/docs/narr/extending.rst +++ b/docs/narr/extending.rst @@ -190,7 +190,7 @@ The general pattern for extending an existing application looks something like this: - Create a new Python package. The easiest way to do this is to create a new - :app:`Pyramid` application using the scaffold mechanism. See + :app:`Pyramid` application using a :term:`cookiecutter`. See :ref:`creating_a_project` for more information. - In the new package, create Python files containing views and other overridden diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index b22b31bf9..81c8e81d7 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -116,14 +116,6 @@ callable: .. note:: - Both :meth:`pyramid.config.Configurator.add_notfound_view` and - :class:`pyramid.view.notfound_view_config` are new as of Pyramid 1.3. - Older Pyramid documentation instructed users to use ``add_view`` instead, - with a ``context`` of ``HTTPNotFound``. This still works; the convenience - method and decorator are just wrappers around this functionality. - -.. warning:: - When a Not Found View callable accepts an argument list as described in :ref:`request_and_context_view_definitions`, the ``context`` passed as the first argument to the view callable will be the @@ -131,6 +123,13 @@ callable: available, the resource context will still be available as ``request.context``. +.. warning:: + + The :term:`Not Found View` callables are only invoked when a + :exc:`~pyramid.httpexceptions.HTTPNotFound` exception is raised. If the + exception is returned from a view then it will be treated as a regular + response object and it will not trigger the custom view. + .. index:: single: forbidden view @@ -161,7 +160,7 @@ forbidden view: def main(globals, **settings): config = Configurator() - config.add_forbidden_view(forbidden_view) + config.add_forbidden_view(forbidden) If instead you prefer to use decorators and a :term:`scan`, you can use the :class:`pyramid.view.forbidden_view_config` decorator to mark a view callable @@ -210,6 +209,13 @@ Here's some sample code that implements a minimal forbidden view: whether the ``pyramid.debug_authorization`` environment setting is true or false. +.. warning:: + + The :term:`forbidden view` callables are only invoked when a + :exc:`~pyramid.httpexceptions.HTTPForbidden` exception is raised. If the + exception is returned from a view then it will be treated as a regular + response object and it will not trigger the custom view. + .. index:: single: request factory @@ -744,7 +750,9 @@ The API that must be implemented by a class that provides """ Accept the resource and request and set self.physical_path and self.virtual_path """ self.virtual_path = some_function_of(resource, request) + self.virtual_path_tuple = some_function_of(resource, request) self.physical_path = some_other_function_of(resource, request) + self.physical_path_tuple = some_function_of(resource, request) The default context URL generator is available for perusal as the class :class:`pyramid.traversal.ResourceURL` in the `traversal module @@ -1561,7 +1569,7 @@ event type. def __call__(self, event): return event.request.path.startswith(self.val) -Once you've created a subscriber predicate, it may registered via +Once you've created a subscriber predicate, it may be registered via :meth:`pyramid.config.Configurator.add_subscriber_predicate`. For example: .. code-block:: python diff --git a/docs/narr/install.rst b/docs/narr/install.rst index 570cb2285..2a25ad84d 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -22,7 +22,7 @@ the following sections. .. sidebar:: Python Versions As of this writing, :app:`Pyramid` is tested against Python 2.7, - Python 3.4, Python 3.5, PyPy. + Python 3.4, Python 3.5, Python 3.6, and PyPy. :app:`Pyramid` is known to run on all popular UNIX-like systems such as Linux, Mac OS X, and FreeBSD, as well as on Windows platforms. It is also known to @@ -91,29 +91,22 @@ If your Windows system doesn't have a Python interpreter, you'll need to install it by downloading a Python 3.x-series interpreter executable from `python.org's download section <https://www.python.org/downloads/>`_ (the files labeled "Windows Installer"). Once you've downloaded it, double click on the -executable, and select appropriate options during the installation process. To +executable and select appropriate options during the installation process. To standardize this documentation, we used the GUI installer and selected the following options: - Screen 1: Install Python 3.x.x (32- or 64-bit) - - Check "Install launcher for all users (recommended)" - - Check "Add Python 3.x to PATH" - - Click "Customize installation" -- Screen 2: Optional Features - - Check all options - - Click "Next" -- Screen 3: Advanced Options - - Check all options - - Customize install location: "C:\\Python3x", where "x" is the minor - version of Python - - Click "Next" - -You might also need to download and install the Python for Windows extensions. + - Check "Install launcher for all users (recommended)". + - Check "Add Python 3.x to PATH". + - Click "Install Now". +- Screen 2: User Account Control + - Click "Yes". .. seealso:: See the official Python documentation :ref:`Using Python on Windows <python:using-on-windows>` for full details. -.. seealso:: Download and install the `Python for Windows extensions +.. seealso:: You might also need to download and install the `Python for + Windows extensions <https://sourceforge.net/projects/pywin32/files/pywin32/>`_. Carefully read the README.txt file at the end of the list of builds, and follow its directions. Make sure you get the proper 32- or 64-bit build and Python @@ -123,15 +116,26 @@ You might also need to download and install the Python for Windows extensions. <https://docs.python.org/3/using/windows.html#launcher>`_ provides a command ``py`` that allows users to run any installed version of Python. -.. warning:: +.. warning:: After you install Python on Windows, you might need to add the + directory where Python and other programs—such as pip, setuptools, and + cookiecutter—are installed to your environment's ``Path``. This will make it + possible to invoke them from a command prompt. - After you install Python on Windows, you might need to add the - ``c:\Python3x`` directory to your environment's ``Path``, where ``x`` is the - minor version of installed Python, in order to make it possible to invoke - Python from a command prompt by typing ``python``. To do so, right click - ``My Computer``, select ``Properties`` --> ``Advanced Tab`` --> - ``Environment Variables``, and add that directory to the end of the ``Path`` - environment variable. + To do so, search for "Environment Variables" on your computer (on Windows + 10, it is under ``System Properties`` --> ``Advanced``) and add that + directory to the ``Path`` environment variable, using the GUI to edit path + segments. + + Example segments should look like + ``C:\Users\<username>\AppData\Local\Programs\Python3x-32``, where you have + your username instead of ``<username>``, and your version of Python and + whether it is 32- or 64-bit. Additionally ensure you have the path segment + ending with ``\Scripts``, i.e., + ``C:\Users\<username>\AppData\Local\Programs\Python3x-32\Scripts``, and for + user-installed Python programs, ``%APPDATA%\Python\Python3x\Scripts``. + + You may need to restart your command prompt session to load the environment + variables. .. seealso:: See `Configuring Python (on Windows) <https://docs.python.org/3/using/windows.html#configuring-python>`_ for @@ -231,9 +235,9 @@ After installing Python as described previously in .. code-block:: doscon + c:\> cd \ c:\> set VENV=c:\env - # replace "x" with your minor version of Python 3 - c:\> c:\Python3x\python -m venv %VENV% + c:\> python -m venv %VENV% c:\> cd %VENV% You can either follow the use of the environment variable ``%VENV%``, or @@ -260,5 +264,5 @@ What Gets Installed When you install :app:`Pyramid`, various libraries such as WebOb, PasteDeploy, and others are installed. -Additionally, as chronicled in :ref:`project_narr`, scaffolds will be -registered, which make it easy to start a new :app:`Pyramid` project. +Additionally, as chronicled in :ref:`project_narr`, :term:`cookiecutter`\ s will be +used, which make it easy to start a new :app:`Pyramid` project. diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index adf955a97..5eda0fcf4 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -213,6 +213,9 @@ packages, SQL queries, logging statements and more. When your application has an error, an interactive debugger allows you to poke around from your browser to find out what happened. +To use the Pyramid debug toolbar, build your project with a Pyramid +:term:`cookiecutter`. + Example: :ref:`debug_toolbar`. Debug with power @@ -376,7 +379,7 @@ By configuring your view to use a renderer, you tell Pyramid to use the behalf. The string passed as ``renderer=`` above is an :term:`asset specification`. -Asset specifications are omnipresent in Pyramid. They allow for more reliable +Asset specifications are widely used in Pyramid. They allow for more reliable customization. See :ref:`intro_asset_specs` for more information. Example: :ref:`renderers_chapter`. @@ -412,27 +415,19 @@ Example: :ref:`events_chapter` and :ref:`event_types`. Build international applications ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Pyramid ships with features that allow you to write applications for -international audiences. You can mark text in your source files and templates -and build catalogs of messages to be translated. You can translate these -catalogs into other languages. Users may then indicate their preference, and -see your application in their language. - -Many systems which offer internationalization suffer from a common problem. A -message in your code may have the same text as one in some other package. -Messages can conflict with each-other, leading to translation errors. Pyramid -solves this problem by using translation *domains*. Each application can have -its own translation domain. Messages in one domain cannot conflict with -messages in another. Problem solved. +Pyramid ships with internationalization-related features in its core: +localization, pluralization, and creating message catalogs from source files +and templates. Pyramid allows for a plurality of message catalogs via the use +of translation domains. You can create a system that has its own translations +without conflict with other translations in other domains. Example: :ref:`i18n_chapter`. Build efficient applications ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Views in dynamic web applications can be expensive or slow to build. Pyramid -allows you to save the results of such a view by *caching* the rendered -response. Indicate in configuration that you want a view to be cached:: +Pyramid provides an easy way to *cache* the results of slow or expensive views. +You can indicate in view configuration that you want a view to be cached:: @view_config(http_cache=3600) # 60 minutes def myview(request): ... @@ -446,7 +441,7 @@ documentation for more information. Build fast applications ~~~~~~~~~~~~~~~~~~~~~~~ -The Pyramid core is fast. It has been engineered from the ground up for speed. +The Pyramid core is fast. It has been engineered from the ground up for speed. It only does as much work as absolutely necessary when you ask it to get a job done. If you need speed from your application, Pyramid is the right choice for you. @@ -456,15 +451,11 @@ Example: http://blog.curiasolutions.com/pages/the-great-web-framework-shootout.h Store session data ~~~~~~~~~~~~~~~~~~ -HTTP is a *stateless* protocol. No request can have knowledge of any other -request. But it is often desireable to associate data with a particular user. -Think of a shopping cart that remembers the items you have added to it even as -you move through the shopping site finding other items to add. - -Pyramid allows you to use *sessions* to solve this problem. Many other -frameworks also support sessions. But Pyramid allows you to plug in your own -custom sessioning system. So long as your system conforms to a documented -interface, you can drop it in in place of the provided system. +Pyramid has built-in support for HTTP sessions, so you can associate data with +specific users between requests. Lots of other frameworks also support +sessions. But Pyramid allows you to plug in your own custom sessioning system. +So long as your system conforms to a documented interface, you can drop it in +in place of the provided system. Currently there is a binding package for the third-party Redis sessioning system that does exactly this. But if you have a specialized need (perhaps you diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst index c7b4b9d6f..9cc5b4ed8 100644 --- a/docs/narr/logging.rst +++ b/docs/narr/logging.rst @@ -9,14 +9,14 @@ to send log messages to loggers that you've configured. .. warning:: - This chapter assumes you've used a :term:`scaffold` to create a project + This chapter assumes you've used a :term:`cookiecutter` to create a project which contains ``development.ini`` and ``production.ini`` files which help - configure logging. All of the scaffolds which ship with :app:`Pyramid` do - this. If you're not using a scaffold, or if you've used a third-party - scaffold which does not create these files, the configuration information in + configure logging. All of the Pyramid cookiecutters provided by the Pylons Project do + this. If you're not using a cookiecutter, or if you've used a third-party + cookiecutter which does not create these files, the configuration information in this chapter may not be applicable. -.. index: +.. index:: pair: settings; logging pair: .ini; logging pair: logging; configuration @@ -26,11 +26,11 @@ to send log messages to loggers that you've configured. Logging Configuration --------------------- -A :app:`Pyramid` project created from a :term:`scaffold` is configured to allow +A :app:`Pyramid` project created from a :term:`cookiecutter` is configured to allow you to send messages to :mod:`Python standard library logging package <logging>` loggers from within your application. In particular, the :term:`PasteDeploy` ``development.ini`` and ``production.ini`` files created -when you use a scaffold include a basic configuration for the Python +when you use a cookiecutter include a basic configuration for the Python :mod:`logging` package. PasteDeploy ``.ini`` files use the Python standard library :mod:`ConfigParser @@ -43,94 +43,20 @@ from when you run ``pserve``. The ``pserve`` command calls the :func:`pyramid.paster.setup_logging` function, a thin wrapper around the :func:`logging.config.fileConfig` using the specified ``.ini`` file, if it contains a ``[loggers]`` section (all of the -scaffold-generated ``.ini`` files do). ``setup_logging`` reads the logging +cookiecutter-generated ``.ini`` files do). ``setup_logging`` reads the logging configuration from the ini file upon which ``pserve`` was invoked. Default logging configuration is provided in both the default -``development.ini`` and the ``production.ini`` file. The logging configuration +``development.ini`` and the ``production.ini`` files. If you use ``pyramid-cookiecutter-starter`` to generate a Pyramid project with the name of the package as ``hello_world``, then the logging configuration in the ``development.ini`` file is as follows: -.. code-block:: ini - :linenos: - - # Begin logging configuration - - [loggers] - keys = root, {{package_logger}} - - [handlers] - keys = console - - [formatters] - keys = generic - - [logger_root] - level = INFO - handlers = console - - [logger_{{package_logger}}] - level = DEBUG - handlers = - qualname = {{package}} - - [handler_console] - class = StreamHandler - args = (sys.stderr,) - level = NOTSET - formatter = generic - - [formatter_generic] - format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - - # End logging configuration +.. literalinclude:: myproject/development.ini + :language: ini + :lineno-match: + :lines: 29- The ``production.ini`` file uses the ``WARN`` level in its logger -configuration, but it is otherwise identical. - -The name ``{{package_logger}}`` above will be replaced with the name of your -project's :term:`package`, which is derived from the name you provide to your -project. For instance, if you do: - -.. code-block:: text - :linenos: - - pcreate -s starter MyApp - -The logging configuration will literally be: - -.. code-block:: ini - :linenos: - - # Begin logging configuration - - [loggers] - keys = root, myapp - - [handlers] - keys = console - - [formatters] - keys = generic - - [logger_root] - level = INFO - handlers = console - - [logger_myapp] - level = DEBUG - handlers = - qualname = myapp - - [handler_console] - class = StreamHandler - args = (sys.stderr,) - level = NOTSET - formatter = generic - - [formatter_generic] - format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - - # End logging configuration +configuration instead of ``DEBUG``, but it is otherwise identical. In this logging configuration: @@ -149,7 +75,7 @@ that ask for a logger (via ``logging.getLogger``) that has a name which begins with anything except your project's package name (e.g., ``myapp``). The logger with the same name as your package name is reserved for your own usage in your :app:`Pyramid` application. Its existence means that you can log to a known -logging location from any :app:`Pyramid` application generated via a scaffold. +logging location from any :app:`Pyramid` application generated via a cookiecutter. :app:`Pyramid` and many other libraries (such as Beaker, SQLAlchemy, Paste) log a number of messages to the root logger for debugging purposes. Switching the @@ -162,9 +88,9 @@ root logger level to ``DEBUG`` reveals them: level = DEBUG handlers = console -Some scaffolds configure additional loggers for additional subsystems they use +Some cookiecutters configure additional loggers for additional subsystems they use (such as SQLALchemy). Take a look at the ``production.ini`` and -``development.ini`` files rendered when you create a project from a scaffold. +``development.ini`` files rendered when you create a project from a cookiecutter. Sending Logging Messages ------------------------ @@ -327,14 +253,14 @@ translogger and your application in it. For instance, change from this: .. code-block:: ini [app:main] - use = egg:MyProject + use = egg:myproject To this: .. code-block:: ini [app:mypyramidapp] - use = egg:MyProject + use = egg:myproject [filter:translogger] use = egg:Paste#translogger diff --git a/docs/narr/myproject/.coveragerc b/docs/narr/myproject/.coveragerc new file mode 100644 index 000000000..f0c31d6d7 --- /dev/null +++ b/docs/narr/myproject/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = myproject +omit = myproject/test* diff --git a/docs/narr/myproject/CHANGES.txt b/docs/narr/myproject/CHANGES.txt new file mode 100644 index 000000000..14b902fd1 --- /dev/null +++ b/docs/narr/myproject/CHANGES.txt @@ -0,0 +1,4 @@ +0.0 +--- + +- Initial version. diff --git a/docs/narr/MyProject/MANIFEST.in b/docs/narr/myproject/MANIFEST.in index fa1692163..1c24b8c0c 100644 --- a/docs/narr/MyProject/MANIFEST.in +++ b/docs/narr/myproject/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include myproject *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include myproject *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/narr/myproject/README.txt b/docs/narr/myproject/README.txt new file mode 100644 index 000000000..2ffc0acba --- /dev/null +++ b/docs/narr/myproject/README.txt @@ -0,0 +1,29 @@ +MyProject +========= + +Getting Started +--------------- + +- Change directory into your newly created project. + + cd MyProject + +- Create a Python virtual environment. + + python3 -m venv env + +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/narr/MyProject/development.ini b/docs/narr/myproject/development.ini index 94fece8ce..20a8a4868 100644 --- a/docs/narr/MyProject/development.ini +++ b/docs/narr/myproject/development.ini @@ -1,10 +1,10 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] -use = egg:MyProject +use = egg:myproject pyramid.reload_templates = true pyramid.debug_authorization = false @@ -24,12 +24,11 @@ pyramid.includes = [server:main] use = egg:waitress#main -host = 127.0.0.1 -port = 6543 +listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/narr/MyProject/myproject/__init__.py b/docs/narr/myproject/myproject/__init__.py index ad5ecbc6f..49dde36d4 100644 --- a/docs/narr/MyProject/myproject/__init__.py +++ b/docs/narr/myproject/myproject/__init__.py @@ -5,7 +5,7 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(settings=settings) - config.include('pyramid_chameleon') + config.include('pyramid_jinja2') config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('home', '/') config.scan() diff --git a/docs/narr/MyProject/myproject/static/pyramid-16x16.png b/docs/narr/myproject/myproject/static/pyramid-16x16.png Binary files differindex 979203112..979203112 100644 --- a/docs/narr/MyProject/myproject/static/pyramid-16x16.png +++ b/docs/narr/myproject/myproject/static/pyramid-16x16.png diff --git a/docs/narr/MyProject/myproject/static/pyramid.png b/docs/narr/myproject/myproject/static/pyramid.png Binary files differindex 4ab837be9..4ab837be9 100644 --- a/docs/narr/MyProject/myproject/static/pyramid.png +++ b/docs/narr/myproject/myproject/static/pyramid.png diff --git a/docs/narr/MyProject/myproject/static/theme.css b/docs/narr/myproject/myproject/static/theme.css index be50ad420..0f4b1a4d4 100644 --- a/docs/narr/MyProject/myproject/static/theme.css +++ b/docs/narr/myproject/myproject/static/theme.css @@ -72,10 +72,12 @@ p { color: #f2b7bd; font-weight: 400; } -.starter-template .links ul li a { - color: #ffffff; +.starter-template .links ul li a, a { + color: #f2b7bd; + text-decoration: underline; } -.starter-template .links ul li a:hover { +.starter-template .links ul li a:hover, a:hover { + color: #ffffff; text-decoration: underline; } .starter-template .links ul li .icon-muted { diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt b/docs/narr/myproject/myproject/templates/layout.jinja2 index f8cbe2e2c..bfac9e64e 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt +++ b/docs/narr/myproject/myproject/templates/layout.jinja2 @@ -1,20 +1,20 @@ <!DOCTYPE html> -<html lang="${request.locale_name}"> +<html lang="{{request.locale_name}}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="description" content="pyramid web application"> <meta name="author" content="Pylons Project"> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> + <link rel="shortcut icon" href="{{request.static_url('myproject:static/pyramid-16x16.png')}}"> - <title>ZODB Scaffold for The Pyramid Web Framework</title> + <title>Cookiecutter Starter project for the Pyramid Web Framework</title> <!-- Bootstrap core CSS --> <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> <!-- Custom styles for this scaffold --> - <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> + <link href="{{request.static_url('myproject:static/theme.css')}}" rel="stylesheet"> <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> <!--[if lt IE 9]> @@ -29,22 +29,19 @@ <div class="container"> <div class="row"> <div class="col-md-2"> - <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> + <img class="logo img-responsive" src="{{request.static_url('myproject:static/pyramid.png') }}" alt="pyramid web framework"> </div> <div class="col-md-10"> - <div class="content"> - <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1> - <p class="lead">Welcome to <span class="font-normal">${project}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p> - </div> + {% block content %} + <p>No content</p> + {% endblock content %} </div> </div> <div class="row"> <div class="links"> <ul> - <li class="current-version">Generated by v1.7</li> - <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li> <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> - <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> </ul> </div> diff --git a/docs/narr/myproject/myproject/templates/mytemplate.jinja2 b/docs/narr/myproject/myproject/templates/mytemplate.jinja2 new file mode 100644 index 000000000..ce042215d --- /dev/null +++ b/docs/narr/myproject/myproject/templates/mytemplate.jinja2 @@ -0,0 +1,8 @@ +{% extends "layout.jinja2" %} + +{% block content %} +<div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1> + <p class="lead">Welcome to <span class="font-normal">MyProject</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p> +</div> +{% endblock content %} diff --git a/docs/narr/MyProject/myproject/tests.py b/docs/narr/myproject/myproject/tests.py index fd414cced..fd414cced 100644 --- a/docs/narr/MyProject/myproject/tests.py +++ b/docs/narr/myproject/myproject/tests.py diff --git a/docs/narr/MyProject/myproject/views.py b/docs/narr/myproject/myproject/views.py index c383c5716..9e9ec4320 100644 --- a/docs/narr/MyProject/myproject/views.py +++ b/docs/narr/myproject/myproject/views.py @@ -1,6 +1,6 @@ from pyramid.view import view_config -@view_config(route_name='home', renderer='templates/mytemplate.pt') +@view_config(route_name='home', renderer='templates/mytemplate.jinja2') def my_view(request): return {'project': 'MyProject'} diff --git a/docs/quick_tutorial/scaffolds/production.ini b/docs/narr/myproject/production.ini index 1418e6bf6..13be488e7 100644 --- a/docs/quick_tutorial/scaffolds/production.ini +++ b/docs/narr/myproject/production.ini @@ -4,7 +4,7 @@ ### [app:main] -use = egg:scaffolds +use = egg:myproject pyramid.reload_templates = false pyramid.debug_authorization = false @@ -18,8 +18,7 @@ pyramid.default_locale_name = en [server:main] use = egg:waitress#main -host = 0.0.0.0 -port = 6543 +listen = *:6543 ### # logging configuration @@ -27,7 +26,7 @@ port = 6543 ### [loggers] -keys = root, scaffolds +keys = root, myproject [handlers] keys = console @@ -39,10 +38,10 @@ keys = generic level = WARN handlers = console -[logger_scaffolds] +[logger_myproject] level = WARN handlers = -qualname = scaffolds +qualname = myproject [handler_console] class = StreamHandler @@ -51,4 +50,4 @@ level = NOTSET formatter = generic [formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s +format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s diff --git a/docs/narr/myproject/pytest.ini b/docs/narr/myproject/pytest.ini new file mode 100644 index 000000000..b1b5f4c38 --- /dev/null +++ b/docs/narr/myproject/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = myproject +python_files = *.py diff --git a/docs/narr/myproject/setup.py b/docs/narr/myproject/setup.py new file mode 100644 index 000000000..00e377349 --- /dev/null +++ b/docs/narr/myproject/setup.py @@ -0,0 +1,51 @@ +import os + +from setuptools import setup, find_packages + +here = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(here, 'README.txt')) as f: + README = f.read() +with open(os.path.join(here, 'CHANGES.txt')) as f: + CHANGES = f.read() + +requires = [ + 'pyramid', + 'pyramid_jinja2', + 'pyramid_debugtoolbar', + 'waitress', +] + +tests_require = [ + 'WebTest >= 1.3.1', # py3 compat + 'pytest', + 'pytest-cov', +] + +setup( + name='myproject', + version='0.0', + description='MyProject', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = myproject:main', + ], + }, +) diff --git a/docs/narr/paste.rst b/docs/narr/paste.rst index 0a217e6e3..26cb1bfa5 100644 --- a/docs/narr/paste.rst +++ b/docs/narr/paste.rst @@ -3,7 +3,7 @@ PasteDeploy Configuration Files =============================== -Packages generated via a :term:`scaffold` make use of a system created by Ian +Packages generated via a :term:`cookiecutter` make use of a system created by Ian Bicking named :term:`PasteDeploy`. PasteDeploy defines a way to declare :term:`WSGI` application configuration in an ``.ini`` file. @@ -14,7 +14,7 @@ runner ``pserve``, as well as other commands such as ``pviews``, ``pshell``, PasteDeploy is not a particularly integral part of Pyramid. It's possible to create a Pyramid application which does not use PasteDeploy at all. We show a Pyramid application that doesn't use PasteDeploy in :ref:`firstapp_chapter`. -However, all Pyramid scaffolds render PasteDeploy configuration files, to +However, all Pyramid cookiecutters render PasteDeploy configuration files, to provide new developers with a standardized way of setting deployment values, and to provide new users with a standardized way of starting, stopping, and debugging an application. @@ -26,12 +26,7 @@ documentation, see http://pythonpaste.org/deploy/. PasteDeploy ----------- -:term:`PasteDeploy` is the system that Pyramid uses to allow :term:`deployment -settings` to be specified using an ``.ini`` configuration file format. It also -allows the ``pserve`` command to work. Its configuration format provides a -convenient place to define application :term:`deployment settings` and WSGI -server settings, and its server runner allows you to stop and start a Pyramid -application easily. +:term:`plaster` is the system that Pyramid uses to load settings from configuration files. The most common format for these files is an ``.ini`` format structured in a way defined by :term:`PasteDeploy`. The format supports mechanisms to define WSGI app :term:`deployment settings`, WSGI server settings and logging. This allows the ``pserve`` command to work, allowing you to stop and start a Pyramid application easily. .. _pastedeploy_entry_points: @@ -40,25 +35,25 @@ Entry Points and PasteDeploy ``.ini`` Files In the :ref:`project_narr` chapter, we breezed over the meaning of a configuration line in the ``deployment.ini`` file. This was the ``use = -egg:MyProject`` line in the ``[app:main]`` section. We breezed over it because +egg:myproject`` line in the ``[app:main]`` section. We breezed over it because it's pretty confusing and "too much information" for an introduction to the system. We'll try to give it a bit of attention here. Let's see the config file again: -.. literalinclude:: MyProject/development.ini +.. literalinclude:: myproject/development.ini :language: ini :linenos: -The line in ``[app:main]`` above that says ``use = egg:MyProject`` is actually -shorthand for a longer spelling: ``use = egg:MyProject#main``. The ``#main`` +The line in ``[app:main]`` above that says ``use = egg:myproject`` is actually +shorthand for a longer spelling: ``use = egg:myproject#main``. The ``#main`` part is omitted for brevity, as ``#main`` is a default defined by PasteDeploy. -``egg:MyProject#main`` is a string which has meaning to PasteDeploy. It points +``egg:myproject#main`` is a string which has meaning to PasteDeploy. It points at a :term:`setuptools` :term:`entry point` named ``main`` defined in the -``MyProject`` project. +``myproject`` project. Take a look at the generated ``setup.py`` file for this project. -.. literalinclude:: MyProject/setup.py +.. literalinclude:: myproject/setup.py :language: python :linenos: @@ -66,21 +61,21 @@ Note that ``entry_points`` is assigned a string which looks a lot like an ``.ini`` file. This string representation of an ``.ini`` file has a section named ``[paste.app_factory]``. Within this section, there is a key named ``main`` (the entry point name) which has a value ``myproject:main``. The -*key* ``main`` is what our ``egg:MyProject#main`` value of the ``use`` section +*key* ``main`` is what our ``egg:myproject#main`` value of the ``use`` section in our config file is pointing at, although it is actually shortened to -``egg:MyProject`` there. The value represents a :term:`dotted Python name` +``egg:myproject`` there. The value represents a :term:`dotted Python name` path, which refers to a callable in our ``myproject`` package's ``__init__.py`` module. -The ``egg:`` prefix in ``egg:MyProject`` indicates that this is an entry point +The ``egg:`` prefix in ``egg:myproject`` indicates that this is an entry point *URI* specifier, where the "scheme" is "egg". An "egg" is created when you run ``setup.py install`` or ``setup.py develop`` within your project. In English, this entry point can thus be referred to as a "PasteDeploy -application factory in the ``MyProject`` project which has the entry point +application factory in the ``myproject`` project which has the entry point named ``main`` where the entry point refers to a ``main`` function in the ``mypackage`` module". Indeed, if you open up the ``__init__.py`` module -generated within any scaffold-generated package, you'll see a ``main`` +generated within any cookiecutter-generated package, you'll see a ``main`` function. This is the function called by :term:`PasteDeploy` when the ``pserve`` command is invoked against our application. It accepts a global configuration object and *returns* an instance of our application. @@ -96,3 +91,8 @@ applications, servers, and :term:`middleware` defined within the configuration file. The values in a ``[DEFAULT]`` section will be passed to your application's ``main`` function as ``global_config`` (see the reference to the ``main`` function in :ref:`init_py`). + +Alternative Configuration File Formats +-------------------------------------- + +It is possible to use different file formats with :app:`Pyramid` if you do not like :term:`PasteDeploy`. Under the hood all command-line scripts such as ``pserve`` and ``pshell`` pass the ``config_uri`` (e.g. ``development.ini`` or ``production.ini``) to the :term:`plaster` library which performs a lookup for an appropriate parser. For ``.ini`` files it uses PasteDeploy but you can register your own configuration formats that plaster will find instead. diff --git a/docs/narr/project.png b/docs/narr/project.png Binary files differindex e1afd97d4..0e17e57ab 100644 --- a/docs/narr/project.png +++ b/docs/narr/project.png diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 71bd176f6..a150afc6b 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -5,94 +5,132 @@ Creating a :app:`Pyramid` Project As we saw in :ref:`firstapp_chapter`, it's possible to create a :app:`Pyramid` application completely manually. However, it's usually more convenient to use -a :term:`scaffold` to generate a basic :app:`Pyramid` :term:`project`. +a :term:`cookiecutter` to generate a basic :app:`Pyramid` :term:`project`. A project is a directory that contains at least one Python :term:`package`. -You'll use a scaffold to create a project, and you'll create your application +You'll use a cookiecutter to create a project, and you'll create your application logic within a package that lives inside the project. Even if your application is extremely simple, it is useful to place code that drives the application within a package, because (1) a package is more easily extended with new code, and (2) an application that lives inside a package can also be distributed more easily than one which does not live within a package. -:app:`Pyramid` comes with a variety of scaffolds that you can use to generate a -project. Each scaffold makes different configuration assumptions about what +The Pylons Project provides several :app:`Pyramid` cookiecutters that you can use to generate a +project. Each cookiecutter makes different configuration assumptions about what type of application you're trying to construct. -These scaffolds are rendered using the ``pcreate`` command that is installed as -part of Pyramid. +These cookiecutters are rendered using the ``cookiecutter`` command that you may install. + +.. seealso:: + + See also `Cookiecutter Installation <https://cookiecutter.readthedocs.io/en/latest/installation.html>`_. + .. index:: - single: scaffolds - single: starter scaffold - single: zodb scaffold - single: alchemy scaffold + single: cookiecutters + single: pyramid-cookiecutter-starter + single: pyramid-cookiecutter-zodb + single: pyramid-cookiecutter-alchemy -.. _additional_paster_scaffolds: +.. _additional_cookiecutters: -Scaffolds Included with :app:`Pyramid` --------------------------------------- +:app:`Pyramid` cookiecutters +---------------------------- + +Pyramid cookiecutters released under the Pylons Project differ from each other on a number of axes: + +- the persistence mechanism they offer (no persistence mechanism, :term:`SQLAlchemy` with SQLite, or :term:`ZODB`) -The convenience scaffolds included with :app:`Pyramid` differ from each other -on a number of axes: +- the mechanism they use to map URLs to code (:term:`URL dispatch` or :term:`traversal`) -- the persistence mechanism they offer (no persistence mechanism, :term:`ZODB`, - or :term:`SQLAlchemy`) +- templating libraries (:term:`Jinja2`, :term:`Chameleon`, or :term:`Mako`) -- the mechanism they use to map URLs to code (:term:`traversal` or :term:`URL - dispatch`) +* `pyramid-cookiecutter-starter <https://github.com/Pylons/pyramid-cookiecutter-starter>`_ +* `pyramid-cookiecutter-alchemy <https://github.com/Pylons/pyramid-cookiecutter-alchemy>`_ +* `pyramid-cookiecutter-zodb <https://github.com/Pylons/pyramid-cookiecutter-zodb>`_ -The included scaffolds are these: +These cookiecutters include: -``starter`` - URL mapping via :term:`URL dispatch` and no persistence mechanism +``pyramid-cookiecutter-starter`` + :term:`URL dispatch` for routing and either :term:`Jinja2`, :term:`Chameleon`, or :term:`Mako` for templating -``zodb`` - URL mapping via :term:`traversal` and persistence via :term:`ZODB` +``pyramid-cookiecutter-alchemy`` + SQLite for persistent storage, :term:`SQLAlchemy` for an ORM, :term:`URL dispatch` for routing, and :term:`Jinja2` for templating. -``alchemy`` - URL mapping via :term:`URL dispatch` and persistence via :term:`SQLAlchemy` +``pyramid-cookiecutter-zodb`` + :term:`ZODB` for persistent storage, :term:`traversal` for routing, and :term:`Chameleon` for templating .. index:: single: creating a project single: project - single: pcreate + single: cookiecutter .. _creating_a_project: Creating the Project -------------------- -.. seealso:: See also the output of :ref:`pcreate --help <pcreate_script>`. - In :ref:`installing_chapter`, you created a virtual Python environment via the -``venv`` command. To start a :app:`Pyramid` :term:`project`, use the -``pcreate`` command installed within the virtual environment. We'll choose the -``starter`` scaffold for this purpose. When we invoke ``pcreate``, it will -create a directory that represents our project. +``venv`` command. We called the virtual environment directory +``env`` and set an environment variable ``VENV`` to its path. + +We assume that you :ref:`previously installed cookiecutter <cookiecutters>`, following its installation instructions. + +We'll choose ``pyramid-cookiecutter-starter`` to start the project. When we invoke ``cookiecutter``, it will create a directory that represents our project. -In :ref:`installing_chapter` we called the virtual environment directory -``env``. The following commands assume that our current working directory is -the ``env`` directory. +We assume our current working directory is the value of ``VENV``. + +On all platforms, generate a project using cookiecutter. + +.. code-block:: bash -The below example uses the ``pcreate`` command to create a project with the -``starter`` scaffold. + $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter + +If prompted for the first item, accept the default ``yes`` by hitting return. + +.. code-block:: text + + You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. + Is it okay to delete and re-clone it? [yes]: yes + project_name [Pyramid Scaffold]: myproject + repo_name [myproject]: myproject + Select template_language: + 1 - jinja2 + 2 - chameleon + 3 - mako + Choose from 1, 2, 3 [1]: 1 + +We then run through the following commands. On UNIX: .. code-block:: bash - $ $VENV/bin/pcreate -s starter MyProject + # Reset our environment variable for a new virtual environment. + $ export VENV=~/env/myproject/env + # Change directory into your newly created project. + $ cd myproject + # Create a new virtual environment... + $ python3 -m venv $VENV + # ...where we upgrade packaging tools. + $ env/bin/pip install --upgrade pip setuptools Or on Windows: .. code-block:: doscon - c:\> %VENV%\Scripts\pcreate -s starter MyProject - -As a result of invoking the ``pcreate`` command, a directory named -``MyProject`` is created. That directory is a :term:`project` directory. The + # Reset our environment variable for a new virtual environment. + c:\> set VENV=c:\env\myproject\env + # Change directory into your newly created project. + c:\> cd myproject + # Create a new virtual environment... + c:\myproject> python -m venv %VENV% + # ...where we upgrade packaging tools. + c:\myproject> %VENV%\Scripts\pip install --upgrade pip setuptools + +As a result of invoking the ``cookiecutter`` command, a directory named +``myproject`` is created. That directory is a :term:`project` directory. The ``setup.py`` file in that directory can be used to distribute your application, or install your application for deployment or development. @@ -107,15 +145,15 @@ debugger (to prevent inappropriate access and disclosure), and turns off a number of debugging settings. You can use this file to put your application into production. -The ``MyProject`` project directory contains an additional subdirectory named +The ``myproject`` project directory contains an additional subdirectory named ``myproject`` (note the case difference) representing a Python :term:`package` which holds very simple :app:`Pyramid` sample code. This is where you'll edit your application's Python code and templates. -We created this project within an ``env`` virtual environment directory. +We created this project in a directory next to its virtual environment directory. However, note that this is not mandatory. The project directory can go more or less anywhere on your filesystem. You don't need to put it in a special "web -server" directory, and you don't need to put it within a virtual environment +server" directory. You could put it within a virtual environment directory. The author uses Linux mainly, and tends to put project directories which he creates within his ``~/projects`` directory. On Windows, it's a good idea to put project directories within a directory that contains no space @@ -125,7 +163,7 @@ projects in ``C:\projects``. .. warning:: - You'll need to avoid using ``pcreate`` to create a project with the same + You'll need to avoid using ``cookiecutter`` to create a project with the same name as a Python standard library component. In particular, this means you should avoid using the names ``site`` or ``test``, both of which conflict with Python standard library packages. You should also avoid using the name @@ -144,7 +182,7 @@ newly created project directory and use the Python interpreter from the invoke the command ``pip install -e .``, which installs the project in development mode (``-e`` is for "editable") into the current directory (``.``). -The file named ``setup.py`` will be in the root of the pcreate-generated +The file named ``setup.py`` will be in the root of the cookiecutter-generated project directory. The ``python`` you're invoking should be the one that lives in the ``bin`` (or ``Scripts`` on Windows) directory of your virtual Python environment. Your terminal's current working directory *must* be the newly @@ -154,25 +192,24 @@ On UNIX: .. code-block:: bash - $ cd MyProject - $ $VENV/bin/pip install -e . + $ $VENV/bin/pip install -e . Or on Windows: .. code-block:: doscon - c:\> cd MyProject - c:\> %VENV%\Scripts\pip install -e . + c:\env\myproject> %VENV%\Scripts\pip install -e . Elided output from a run of this command on UNIX is shown below: .. code-block:: bash - $ cd MyProject - $ $VENV/bin/pip install -e . - ... - Successfully installed Chameleon-2.24 Mako-1.0.4 MyProject \ - pyramid-chameleon-0.3 pyramid-debugtoolbar-2.4.2 pyramid-mako-1.0.2 + Running setup.py develop for myproject + Successfully installed Jinja2-2.8 Mako-1.0.6 MarkupSafe-0.23 \ + PasteDeploy-1.5.2 Pygments-2.1.3 WebOb-1.7.0 myproject pyramid-1.7.3 \ + pyramid-debugtoolbar-3.0.5 pyramid-jinja2-2.7 pyramid-mako-1.0.2 \ + repoze.lru-0.6 translationstring-1.3 venusian-1.0 waitress-1.0.1 \ + zope.deprecation-4.2.0 zope.interface-4.3.3 This will install a :term:`distribution` representing your project into the virtual environment interpreter's library set so it can be found by ``import`` @@ -199,7 +236,7 @@ On Windows: .. code-block:: doscon - c:\> %VENV%\Scripts\pip install -e ".[testing]" + c:\env\myproject> %VENV%\Scripts\pip install -e ".[testing]" Once the testing requirements are installed, then you can run the tests using the ``py.test`` command that was just installed in the ``bin`` directory of @@ -215,7 +252,7 @@ On Windows: .. code-block:: doscon - c:\> %VENV%\Scripts\py.test -q + c:\env\myproject> %VENV%\Scripts\py.test -q Here's sample output from a test run on UNIX: @@ -225,9 +262,7 @@ Here's sample output from a test run on UNIX: .. 2 passed in 0.47 seconds -The tests themselves are found in the ``tests.py`` module in your ``pcreate`` -generated project. Within a project generated by the ``starter`` scaffold, -only two sample tests exist. +The tests themselves are found in the ``tests.py`` module in your ``cookiecutter``-generated project. Within a project generated by the ``pyramid-cookiecutter-starter`` cookiecutter, only two sample tests exist. .. note:: @@ -242,7 +277,7 @@ to ``py.test``: $ $VENV/bin/py.test --cov -q -Scaffolds include configuration defaults for ``py.test`` and test coverage. +Cookiecutters include configuration defaults for ``py.test`` and test coverage. These configuration files are ``pytest.ini`` and ``.coveragerc``, located at the root of your package. Without these defaults, we would need to specify the path to the module on which we want to run tests and coverage. @@ -280,31 +315,31 @@ On UNIX: On Windows: -.. code-block:: text +.. code-block:: doscon - c:\> %VENV%\Scripts\pserve development.ini + c:\env\myproject> %VENV%\Scripts\pserve development.ini Here's sample output from a run of ``pserve`` on UNIX: .. code-block:: bash $ $VENV/bin/pserve development.ini - Starting server in PID 16208. - serving on http://127.0.0.1:6543 + Starting server in PID 77171. + Serving on http://localhost:6543 + Serving on http://localhost:6543 Access is restricted such that only a browser running on the same machine as Pyramid will be able to access your Pyramid application. However, if you want to open access to other machines on the same network, then edit the -``development.ini`` file, and replace the ``host`` value in the -``[server:main]`` section, changing it from ``127.0.0.1`` to ``0.0.0.0``. For -example: +``development.ini`` file, and replace the ``listen`` value in the +``[server:main]`` section, changing it from ``localhost:6543`` to ``*:6543`` +(this is equivalent to ``0.0.0.0:6543 [::]:6543``). For example: .. code-block:: ini [server:main] use = egg:waitress#main - host = 0.0.0.0 - port = 6543 + listen = *:6543 Now when you use ``pserve`` to start the application, it will respond to requests on *all* IP addresses possessed by your system, not just requests to @@ -316,19 +351,20 @@ the case, if you use a browser running on the same system as Pyramid, it will be able to access the application via ``http://127.0.0.1:6543/`` as well as via ``http://192.168.1.50:6543/``. However, *other people* on other computers on the same network will also be able to visit your Pyramid application in their -browser by visiting ``http://192.168.1.50:6543/``. +browser by visiting ``http://192.168.1.50:6543/``. The same holds true if you use +IPv6. ``[::]`` means the same as ``0.0.0.0`` but for IPv6 protocol. You can change the port on which the server runs on by changing the same portion of the ``development.ini`` file. For example, you can change the -``port = 6543`` line in the ``development.ini`` file's ``[server:main]`` -section to ``port = 8080`` to run the server on port 8080 instead of port 6543. +``listen = localhost:6543`` line in the ``development.ini`` file's ``[server:main]`` +section to ``listen = localhost:8080`` to run the server on port 8080 instead of port 6543. You can shut down a server started this way by pressing ``Ctrl-C`` (or ``Ctrl-Break`` on Windows). The default server used to run your Pyramid application when a project is -created from a scaffold is named :term:`Waitress`. This server is what prints -the ``serving on...`` line when you run ``pserve``. It's a good idea to use +created from a cookiecutter is named :term:`Waitress`. This server is what prints +the ``Serving on...`` line when you run ``pserve``. It's a good idea to use this server during development because it's very simple. It can also be used for light production. Setting your application up under a different server is not advised until you've done some development work under the default server, @@ -365,7 +401,8 @@ For example, on UNIX: $ $VENV/bin/pserve development.ini --reload Starting subprocess with file monitor Starting server in PID 16601. - serving on http://127.0.0.1:6543 + Serving on http://localhost:6543 + Serving on http://localhost:6543 Now if you make a change to any of your project's ``.py`` files or ``.ini`` files, you'll see the server restart automatically: @@ -375,12 +412,13 @@ files, you'll see the server restart automatically: development.ini changed; reloading... -------------------- Restarting -------------------- Starting server in PID 16602. - serving on http://127.0.0.1:6543 + Serving on http://localhost:6543 + Serving on http://localhost:6543 Changes to template files (such as ``.pt`` or ``.mak`` files) won't cause the server to restart. Changes to template files don't require a server restart as long as the ``pyramid.reload_templates`` setting in the ``development.ini`` -file is ``true``. Changes made to template files when this setting is true +file is ``true``. Changes made to template files when this setting is ``true`` will take effect immediately without a server restart. .. index:: @@ -395,8 +433,8 @@ browser like what is displayed in the following image: .. image:: project.png -This is the page shown by default when you visit an unmodified ``pcreate`` -generated ``starter`` application in a browser. +This is the page shown by default when you visit an unmodified ``cookiecutter`` +generated ``pyramid-cookiecutter-starter`` application in a browser. .. index:: single: debug toolbar @@ -477,48 +515,57 @@ this: The Project Structure --------------------- -The ``starter`` scaffold generated a :term:`project` (named ``MyProject``), +The ``pyramid-cookiecutter-starter`` cookiecutter generated a :term:`project` (named ``myproject``), which contains a Python :term:`package`. The package is *also* named -``myproject``, but it's lowercased; the scaffold generates a project which -contains a package that shares its name except for case. +``myproject``; the cookiecutter generates a project which +contains a package that shares its name. -All :app:`Pyramid` ``pcreate``-generated projects share a similar structure. -The ``MyProject`` project we've generated has the following directory structure: +All :app:`Pyramid` ``cookiecutter``-generated projects share a similar structure. +The ``myproject`` project we've generated has the following directory structure: .. code-block:: text - MyProject/ - |-- CHANGES.txt - |-- development.ini - |-- MANIFEST.in - |-- myproject - | |-- __init__.py - | |-- static - | | |-- pyramid-16x16.png - | | |-- pyramid.png - | | |-- theme.css - | | `-- theme.min.css - | |-- templates - | | `-- mytemplate.pt - | |-- tests.py - | `-- views.py - |-- production.ini - |-- README.txt - `-- setup.py - -The ``MyProject`` :term:`Project` + myproject/ + ├── .coveragerc + ├── CHANGES.txt + ├── MANIFEST.in + ├── myproject + │  ├── __init__.py + │  ├── static + │  │  ├── pyramid-16x16.png + │  │  ├── pyramid.png + │  │  └── theme.css + │  ├── templates + │  │  ├── layout.jinja2 + │  │  └── mytemplate.jinja2 + │  ├── tests.py + │  └── views.py + ├── README.txt + ├── development.ini + ├── production.ini + ├── pytest.ini + └── setup.py + + +The ``myproject`` :term:`Project` --------------------------------- -The ``MyProject`` :term:`project` directory is the distribution and deployment +The ``myproject`` :term:`project` directory is the distribution and deployment wrapper for your application. It contains both the ``myproject`` :term:`package` representing your application as well as files used to describe, run, and test your application. +#. ``.coveragerc`` configures coverage when running tests. + #. ``CHANGES.txt`` describes the changes you've made to the application. It is - conventionally written in :term:`ReStructuredText` format. + conventionally written in :term:`reStructuredText` format. + +#. ``MANIFEST.in`` is a :term:`distutils` "manifest" file, naming which files + should be included in a source distribution of the package when ``python + setup.py sdist`` is run. #. ``README.txt`` describes the application in general. It is conventionally - written in :term:`ReStructuredText` format. + written in :term:`reStructuredText` format. #. ``development.ini`` is a :term:`PasteDeploy` configuration file that can be used to execute your application during development. @@ -526,9 +573,7 @@ describe, run, and test your application. #. ``production.ini`` is a :term:`PasteDeploy` configuration file that can be used to execute your application in a production configuration. -#. ``MANIFEST.in`` is a :term:`distutils` "manifest" file, naming which files - should be included in a source distribution of the package when ``python - setup.py sdist`` is run. +#. ``pytest.ini`` is a configuration file for running tests. #. ``setup.py`` is the file you'll use to test and distribute your application. It is a standard :term:`setuptools` ``setup.py`` file. @@ -537,7 +582,7 @@ describe, run, and test your application. single: PasteDeploy single: ini file -.. _MyProject_ini: +.. _myproject_ini: ``development.ini`` ~~~~~~~~~~~~~~~~~~~ @@ -548,7 +593,7 @@ as the deployment settings provided to that application. The generated ``development.ini`` file looks like so: -.. literalinclude:: MyProject/development.ini +.. literalinclude:: myproject/development.ini :language: ini :linenos: @@ -557,15 +602,15 @@ This file contains several sections including ``[app:main]``, The ``[app:main]`` section represents configuration for your :app:`Pyramid` application. The ``use`` setting is the only setting required to be present in -the ``[app:main]`` section. Its default value, ``egg:MyProject``, indicates -that our MyProject project contains the application that should be served. +the ``[app:main]`` section. Its default value, ``egg:myproject``, indicates +that our myproject project contains the application that should be served. Other settings added to this section are passed as keyword arguments to the function named ``main`` in our package's ``__init__.py`` module. You can provide startup-time configuration parameters to your application by adding more settings to this section. .. seealso:: See :ref:`pastedeploy_entry_points` for more information about the - meaning of the ``use = egg:MyProject`` value in this section. + meaning of the ``use = egg:myproject`` value in this section. The ``pyramid.reload_templates`` setting in the ``[app:main]`` section is a :app:`Pyramid`-specific setting which is passed into the framework. If it @@ -599,7 +644,7 @@ The ``[server:main]`` section of the configuration file configures a WSGI server which listens on TCP port 6543. It is configured to listen on localhost only (``127.0.0.1``). -.. _MyProject_ini_logging: +.. _myproject_ini_logging: The sections after ``# logging configuration`` represent Python's standard library :mod:`logging` module configuration for your application. These @@ -660,7 +705,7 @@ directory didn't contain a ``MANIFEST.in`` file that told the ``sdist`` machinery to include ``*.pt`` files, the ``myproject/templates/mytemplate.pt`` file would not be included in the generated tarball. -Projects generated by Pyramid scaffolds include a default ``MANIFEST.in`` file. +Projects generated by Pyramid cookiecutters include a default ``MANIFEST.in`` file. The ``MANIFEST.in`` file contains declarations which tell it to include files like ``*.pt``, ``*.css`` and ``*.js`` in the generated tarball. If you include files with extensions other than the files named in the project's @@ -700,7 +745,7 @@ testing, as well as distributing your application. Our generated ``setup.py`` looks like this: -.. literalinclude:: MyProject/setup.py +.. literalinclude:: myproject/setup.py :language: python :linenos: @@ -743,7 +788,7 @@ you can try this command now: $ $VENV/bin/python setup.py sdist This will create a tarball of your application in a ``dist`` subdirectory named -``MyProject-0.0.tar.gz``. You can send this tarball to other people who want +``myproject-0.0.tar.gz``. You can send this tarball to other people who want to install and use your application. .. index:: @@ -752,7 +797,7 @@ to install and use your application. The ``myproject`` :term:`Package` --------------------------------- -The ``myproject`` :term:`package` lives inside the ``MyProject`` +The ``myproject`` :term:`package` lives inside the ``myproject`` :term:`project`. It contains: #. An ``__init__.py`` file signifies that this is a Python :term:`package`. It @@ -760,14 +805,14 @@ The ``myproject`` :term:`package` lives inside the ``MyProject`` ``main`` function which is used as a entry point for commands such as ``pserve``, ``pshell``, ``pviews``, and others. -#. A ``templates`` directory, which contains :term:`Chameleon` (or other types +#. A ``templates`` directory, which contains :term:`Jinja2` (or other types of) templates. #. A ``tests.py`` module, which contains unit test code for the application. #. A ``views.py`` module, which contains view code for the application. -These are purely conventions established by the scaffold. :app:`Pyramid` +These are purely conventions established by the cookiecutter. :app:`Pyramid` doesn't insist that you name things in any particular way. However, it's generally a good idea to follow Pyramid standards for naming, so that other Pyramid developers can get up to speed quickly on your code when you need help. @@ -785,7 +830,7 @@ advertises an entry point for use by our :term:`PasteDeploy` ``.ini`` file. This is the file named ``__init__.py``. The presence of an ``__init__.py`` also informs Python that the directory which contains it is a *package*. -.. literalinclude:: MyProject/myproject/__init__.py +.. literalinclude:: myproject/myproject/__init__.py :language: python :linenos: @@ -800,8 +845,8 @@ also informs Python that the directory which contains it is a *package*. Line 7 creates an instance of a :term:`Configurator`. - Line 8 adds support for Chameleon templating bindings, allowing us to - specify renderers with the ``.pt`` extension. + Line 8 adds support for Jinja2 templating bindings, allowing us to + specify renderers with the ``.jinja2`` extension. Line 9 registers a static view, which will serve up the files from the ``myproject:static`` :term:`asset specification` (the ``static`` directory @@ -827,7 +872,7 @@ callables*. A :term:`view callable` is the main tool of a :app:`Pyramid` web application developer; it is a bit of code which accepts a :term:`request` and which returns a :term:`response`. -.. literalinclude:: MyProject/myproject/views.py +.. literalinclude:: myproject/myproject/views.py :language: python :linenos: @@ -844,8 +889,8 @@ result of the view callable. This particular view declaration points at specifies the ``mytemplate.pt`` file within the ``templates`` directory of the ``myproject`` package. The asset specification could have also been specified as ``myproject:templates/mytemplate.pt``; the leading package name and colon is -optional. The template file pointed to is a :term:`Chameleon` ZPT template -file (``templates/my_template.pt``). +optional. The template file pointed to is a :term:`Jinja2` template +file (``templates/my_template.jinja2``). This view callable function is handed a single piece of information: the :term:`request`. The *request* is an instance of the :term:`WebOb` ``Request`` @@ -858,9 +903,9 @@ the HTML in a :term:`response`. .. note:: Dictionaries provide values to :term:`template`\s. -.. note:: When the application is run with the scaffold's :ref:`default - development.ini <MyProject_ini>` configuration, :ref:`logging is set up - <MyProject_ini_logging>` to aid debugging. If an exception is raised, +.. note:: When the application is run with the cookiecutter's :ref:`default + development.ini <myproject_ini>` configuration, :ref:`logging is set up + <myproject_ini_logging>` to aid debugging. If an exception is raised, uncaught tracebacks are displayed after the startup messages on :ref:`the console running the server <running_the_project_application>`. Also ``print()`` statements may be inserted into the application for debugging to @@ -869,7 +914,7 @@ the HTML in a :term:`response`. .. note:: ``development.ini`` has a setting that controls how templates are reloaded, ``pyramid.reload_templates``. - - When set to ``True`` (as in the scaffold ``development.ini``), changed + - When set to ``True`` (as in the cookiecutter ``development.ini``), changed templates automatically reload without a server restart. This is convenient while developing, but slows template rendering speed. @@ -900,31 +945,46 @@ the HTML in a :term:`response`. ``static`` ~~~~~~~~~~ -This directory contains static assets which support the ``mytemplate.pt`` +This directory contains static assets which support the ``layout.jinja2`` template. It includes CSS and images. -``templates/mytemplate.pt`` + +``templates/layout.jinja2`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This is the single :term:`Chameleon` template that exists in the project. Its -contents are too long to show here, but it displays a default page when -rendered. It is referenced by the call to ``@view_config`` as the ``renderer`` +This is the base layout content. It contains a single marker for content block. Other templates inherit its content, providing layout for the web application. Its contents are too long to show here, but here is an excerpt: + +.. literalinclude:: myproject/myproject/templates/layout.jinja2 + :language: jinja + :lines: 34-38 + :lineno-match: + + +``templates/mytemplate.jinja2`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is the content :term:`Jinja2` template that exists in the project. It is referenced by the call to ``@view_config`` as the ``renderer`` of the ``my_view`` view callable in the ``views.py`` file. See -:ref:`views_which_use_a_renderer` for more information about renderers. +:ref:`views_which_use_a_renderer` for more information about renderers. It inherits ("extends") the HTML provided by ``layout.jinja2``, replacing the content block with its own content. + +.. literalinclude:: myproject/myproject/templates/mytemplate.jinja2 + :language: jinja + :linenos: Templates are accessed and used by view configurations and sometimes by view functions themselves. See :ref:`templates_used_directly` and :ref:`templates_used_as_renderers`. + .. index:: single: tests.py ``tests.py`` ~~~~~~~~~~~~ -The ``tests.py`` module includes unit tests for your application. +The ``tests.py`` module includes tests for your application. -.. literalinclude:: MyProject/myproject/tests.py +.. literalinclude:: myproject/myproject/tests.py :language: python :linenos: @@ -946,16 +1006,16 @@ Modifying Package Structure --------------------------- It is best practice for your application's code layout to not stray too much -from accepted Pyramid scaffold defaults. If you refrain from changing things +from accepted Pyramid cookiecutter defaults. If you refrain from changing things very much, other Pyramid coders will be able to more quickly understand your -application. However, the code layout choices made for you by a scaffold are +application. However, the code layout choices made for you by a cookiecutter are in no way magical or required. Despite the choices made for you by any -scaffold, you can decide to lay your code out any way you see fit. +cookiecutter, you can decide to lay your code out any way you see fit. For example, the configuration method named :meth:`~pyramid.config.Configurator.add_view` requires you to pass a :term:`dotted Python name` or a direct object reference as the class or -function to be used as a view. By default, the ``starter`` scaffold would have +function to be used as a view. By default, the ``starter`` cookiecutter would have you add view functions to the ``views.py`` module in your package. However, you might be more comfortable creating a ``views`` *directory*, and adding a single file for each view. @@ -997,7 +1057,7 @@ Pyramid application via ``pserve``. This can be a useful debugging tool. See What Is This ``pserve`` Thing ----------------------------- -The code generated by a :app:`Pyramid` scaffold assumes that you will be using +The code generated by a :app:`Pyramid` cookiecutter assumes that you will be using the ``pserve`` command to start your application while you do development. ``pserve`` is a command that reads a :term:`PasteDeploy` ``.ini`` file (e.g., ``development.ini``), and configures a server to serve a :app:`Pyramid` @@ -1007,7 +1067,7 @@ application based on the data in the file. application. As we saw in :ref:`firstapp_chapter`, ``pserve`` needn't be invoked at all to run a :app:`Pyramid` application. The use of ``pserve`` to run a :app:`Pyramid` application is purely conventional based on the output of -its scaffolding. But we strongly recommend using ``pserve`` while developing +its cookiecutter. But we strongly recommend using ``pserve`` while developing your application because many other convenience introspection commands (such as ``pviews``, ``prequest``, ``proutes``, and others) are also implemented in terms of configuration availability of this ``.ini`` file format. It also @@ -1019,7 +1079,7 @@ restarting of the server when code changes. Using an Alternate WSGI Server ------------------------------ -Pyramid scaffolds generate projects which use the :term:`Waitress` WSGI server. +Pyramid cookiecutters generate projects which use the :term:`Waitress` WSGI server. Waitress is a server that is suited for development and light production usage. It's not the fastest nor the most featureful WSGI server. Instead, its main feature is that it works on all platforms that Pyramid needs to run on, @@ -1036,12 +1096,54 @@ configuration on a local system that you have complete control over; it will provide the best development experience. One popular production alternative to the default Waitress server is -:term:`mod_wsgi`. You can use mod_wsgi to serve your :app:`Pyramid` application +:term:`mod_wsgi`. You can use ``mod_wsgi`` to serve your :app:`Pyramid` application using the Apache web server rather than any "pure-Python" server like Waitress. It is fast and featureful. See :ref:`modwsgi_tutorial` for details. Another good production alternative is :term:`Green Unicorn` (aka ``gunicorn``). It's faster than Waitress and slightly easier to configure than -mod_wsgi, although it depends, in its default configuration, on having a +``mod_wsgi``, although it depends, in its default configuration, on having a buffering HTTP proxy in front of it. It does not, as of this writing, work on Windows. + +Automatically Reloading Your Code +--------------------------------- + +During development, it can be really useful to automatically have the +webserver restart when you make changes. ``pserve`` has a ``--reload`` switch +to enable this. It uses the +`hupper <http://docs.pylonsproject.org/projects/hupper/en/latest/>`_ package +to enable this behavior. When your code crashes, ``hupper`` will wait for +another change or the ``SIGHUP`` signal before restarting again. + +inotify support +~~~~~~~~~~~~~~~ + +By default ``hupper`` will poll the filesystem for changes to all Python +code. This can be pretty inefficient in larger projects. To be nicer to your +hard drive, you should install the +`watchdog <http://pythonhosted.org/watchdog/>`_ package in development. +``hupper`` will automatically use ``watchdog`` to more efficiently poll the +filesystem. + +Monitoring Custom Files +~~~~~~~~~~~~~~~~~~~~~~~ + +By default, ``pserve --reload`` will monitor all imported Python code +(everything in ``sys.modules``) as well as the config file passed to +``pserve`` (e.g., ``development.ini``). You can instruct ``pserve`` to watch +other files for changes as well by defining a ``[pserve]`` section in your +configuration file. For example, let's say your application loads the +``favicon.ico`` file at startup and stores it in memory to efficiently +serve it many times. When you change it, you want ``pserve`` to restart: + +.. code-block:: ini + + [pserve] + watch_files = + myapp/static/favicon.ico + +Paths may be absolute or relative to the configuration file. They may also +be an :term:`asset specification`. These paths are passed to ``hupper``, which +has some basic support for globbing. Acceptable glob patterns depend on the +version of Python being used. diff --git a/docs/narr/scaffolding.rst b/docs/narr/scaffolding.rst index 164ceb3bf..27239d34e 100644 --- a/docs/narr/scaffolding.rst +++ b/docs/narr/scaffolding.rst @@ -3,6 +3,10 @@ Creating Pyramid Scaffolds ========================== +.. deprecated:: 1.8 + + Scaffolds and the ``pcreate`` script used to generate :app:`Pyramid` projects from scaffolds have been deprecated. Use :ref:`cookiecutters` instead. + You can extend Pyramid by creating a :term:`scaffold` template. A scaffold template is useful if you'd like to distribute a customizable configuration of Pyramid to other users. Once you've created a scaffold, and someone has diff --git a/docs/narr/security.rst b/docs/narr/security.rst index 77e7fd707..3a6bfa5e5 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -146,7 +146,7 @@ For example, the following view declaration protects the view named # config is an instance of pyramid.config.Configurator config.add_view('mypackage.views.blog_entry_add_view', - name='add_entry.html', + name='add_entry.html', context='mypackage.resources.Blog', permission='add') @@ -725,7 +725,7 @@ object that implements the following interface: """ Return ``True`` if any of the ``principals`` is allowed the ``permission`` in the current ``context``, else return ``False`` """ - + def principals_allowed_by_permission(self, context, permission): """ Return a set of principal identifiers allowed by the ``permission`` in ``context``. This behavior is optional; if you @@ -765,3 +765,215 @@ which would allow the attacker to control the content of the payload. Re-using a secret across two different subsystems might drop the security of signing to zero. Keys should not be re-used across different contexts where an attacker has the possibility of providing a chosen plaintext. + +.. index:: + single: preventing cross-site request forgery attacks + single: cross-site request forgery attacks, prevention + +Preventing Cross-Site Request Forgery Attacks +--------------------------------------------- + +`Cross-site request forgery +<https://en.wikipedia.org/wiki/Cross-site_request_forgery>`_ attacks are a +phenomenon whereby a user who is logged in to your website might inadvertantly +load a URL because it is linked from, or embedded in, an attacker's website. +If the URL is one that may modify or delete data, the consequences can be dire. + +You can avoid most of these attacks by issuing a unique token to the browser +and then requiring that it be present in all potentially unsafe requests. +:app:`Pyramid` provides facilities to create and check CSRF tokens. + +By default :app:`Pyramid` comes with a session-based CSRF implementation +:class:`pyramid.csrf.SessionCSRFStoragePolicy`. To use it, you must first enable +a :term:`session factory` as described in +:ref:`using_the_default_session_factory` or +:ref:`using_alternate_session_factories`. Alternatively, you can use +a cookie-based implementation :class:`pyramid.csrf.CookieCSRFStoragePolicy` which gives +some additional flexibility as it does not require a session for each user. +You can also define your own implementation of +:class:`pyramid.interfaces.ICSRFStoragePolicy` and register it with the +:meth:`pyramid.config.Configurator.set_csrf_storage_policy` directive. + +For example: + +.. code-block:: python + + from pyramid.config import Configurator + + config = Configurator() + config.set_csrf_storage_policy(MyCustomCSRFPolicy()) + +.. index:: + single: csrf.get_csrf_token + +Using the ``csrf.get_csrf_token`` Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To get the current CSRF token, use the +:data:`pyramid.csrf.get_csrf_token` method. + +.. code-block:: python + + from pyramid.csrf import get_csrf_token + token = get_csrf_token(request) + +The ``get_csrf_token()`` method accepts a single argument: the request. It +returns a CSRF *token* string. If ``get_csrf_token()`` or ``new_csrf_token()`` +was invoked previously for this user, then the existing token will be returned. +If no CSRF token previously existed for this user, then a new token will be set +into the session and returned. The newly created token will be opaque and +randomized. + +.. _get_csrf_token_in_templates: + +Using the ``get_csrf_token`` global in templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Templates have a ``get_csrf_token()`` method inserted into their globals, which +allows you to get the current token without modifying the view code. This +method takes no arguments and returns a CSRF token string. You can use the +returned token as the value of a hidden field in a form that posts to a method +that requires elevated privileges, or supply it as a request header in AJAX +requests. + +For example, include the CSRF token as a hidden field: + +.. code-block:: html + + <form method="post" action="/myview"> + <input type="hidden" name="csrf_token" value="${get_csrf_token()}"> + <input type="submit" value="Delete Everything"> + </form> + +Or include it as a header in a jQuery AJAX request: + +.. code-block:: javascript + + var csrfToken = "${get_csrf_token()}"; + $.ajax({ + type: "POST", + url: "/myview", + headers: { 'X-CSRF-Token': csrfToken } + }).done(function() { + alert("Deleted"); + }); + +The handler for the URL that receives the request should then require that the +correct CSRF token is supplied. + +.. index:: + single: csrf.new_csrf_token + +Using the ``csrf.new_csrf_token`` Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To explicitly create a new CSRF token, use the ``csrf.new_csrf_token()`` +method. This differs only from ``csrf.get_csrf_token()`` inasmuch as it +clears any existing CSRF token, creates a new CSRF token, sets the token into +the user, and returns the token. + +.. code-block:: python + + from pyramid.csrf import get_csrf_token + token = new_csrf_token() + +.. note:: + + It is not possible to force a new CSRF token from a template. If you + want to regenerate your CSRF token then do it in the view code and return + the new token as part of the context. + +Checking CSRF Tokens Manually +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In request handling code, you can check the presence and validity of a CSRF +token with :func:`pyramid.csrf.check_csrf_token`. If the token is valid, it +will return ``True``, otherwise it will raise ``HTTPBadRequest``. Optionally, +you can specify ``raises=False`` to have the check return ``False`` instead of +raising an exception. + +By default, it checks for a POST parameter named ``csrf_token`` or a header +named ``X-CSRF-Token``. + +.. code-block:: python + + from pyramid.csrf import check_csrf_token + + def myview(request): + # Require CSRF Token + check_csrf_token(request) + + # ... + +.. _auto_csrf_checking: + +Checking CSRF Tokens Automatically +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 1.7 + +:app:`Pyramid` supports automatically checking CSRF tokens on requests with an +unsafe method as defined by RFC2616. Any other request may be checked manually. +This feature can be turned on globally for an application using the +:meth:`pyramid.config.Configurator.set_default_csrf_options` directive. +For example: + +.. code-block:: python + + from pyramid.config import Configurator + + config = Configurator() + config.set_default_csrf_options(require_csrf=True) + +CSRF checking may be explicitly enabled or disabled on a per-view basis using +the ``require_csrf`` view option. A value of ``True`` or ``False`` will +override the default set by ``set_default_csrf_options``. For example: + +.. code-block:: python + + @view_config(route_name='hello', require_csrf=False) + def myview(request): + # ... + +When CSRF checking is active, the token and header used to find the +supplied CSRF token will be ``csrf_token`` and ``X-CSRF-Token``, respectively, +unless otherwise overridden by ``set_default_csrf_options``. The token is +checked against the value in ``request.POST`` which is the submitted form body. +If this value is not present, then the header will be checked. + +In addition to token based CSRF checks, if the request is using HTTPS then the +automatic CSRF checking will also check the referrer of the request to ensure +that it matches one of the trusted origins. By default the only trusted origin +is the current host, however additional origins may be configured by setting +``pyramid.csrf_trusted_origins`` to a list of domain names (and ports if they +are non-standard). If a host in the list of domains starts with a ``.`` then +that will allow all subdomains as well as the domain without the ``.``. + +If CSRF checks fail then a :class:`pyramid.exceptions.BadCSRFToken` or +:class:`pyramid.exceptions.BadCSRFOrigin` exception will be raised. This +exception may be caught and handled by an :term:`exception view` but, by +default, will result in a ``400 Bad Request`` response being sent to the +client. + +Checking CSRF Tokens with a View Predicate +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 1.7 + Use the ``require_csrf`` option or read :ref:`auto_csrf_checking` instead + to have :class:`pyramid.exceptions.BadCSRFToken` exceptions raised. + +A convenient way to require a valid CSRF token for a particular view is to +include ``check_csrf=True`` as a view predicate. See +:meth:`pyramid.config.Configurator.add_view`. + +.. code-block:: python + + @view_config(request_method='POST', check_csrf=True, ...) + def myview(request): + ... + +.. note:: + A mismatch of a CSRF token is treated like any other predicate miss, and the + predicate system, when it doesn't find a view, raises ``HTTPNotFound`` + instead of ``HTTPBadRequest``, so ``check_csrf=True`` behavior is different + from calling :func:`pyramid.csrf.check_csrf_token`. diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index 5b24201a9..7e2469d54 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -12,8 +12,7 @@ application. This chapter describes how to configure sessions, what session implementations :app:`Pyramid` provides out of the box, how to store and retrieve data from -sessions, and two session-specific features: flash messages, and cross-site -request forgery attack prevention. +sessions, and a session-specific feature: flash messages. .. index:: single: session factory (default) @@ -316,183 +315,3 @@ flash storage. ['info message'] >>> request.session.peek_flash() [] - -.. index:: - single: preventing cross-site request forgery attacks - single: cross-site request forgery attacks, prevention - -Preventing Cross-Site Request Forgery Attacks ---------------------------------------------- - -`Cross-site request forgery -<https://en.wikipedia.org/wiki/Cross-site_request_forgery>`_ attacks are a -phenomenon whereby a user who is logged in to your website might inadvertantly -load a URL because it is linked from, or embedded in, an attacker's website. -If the URL is one that may modify or delete data, the consequences can be dire. - -You can avoid most of these attacks by issuing a unique token to the browser -and then requiring that it be present in all potentially unsafe requests. -:app:`Pyramid` sessions provide facilities to create and check CSRF tokens. - -To use CSRF tokens, you must first enable a :term:`session factory` as -described in :ref:`using_the_default_session_factory` or -:ref:`using_alternate_session_factories`. - -.. index:: - single: session.get_csrf_token - -Using the ``session.get_csrf_token`` Method -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To get the current CSRF token from the session, use the -``session.get_csrf_token()`` method. - -.. code-block:: python - - token = request.session.get_csrf_token() - -The ``session.get_csrf_token()`` method accepts no arguments. It returns a -CSRF *token* string. If ``session.get_csrf_token()`` or -``session.new_csrf_token()`` was invoked previously for this session, then the -existing token will be returned. If no CSRF token previously existed for this -session, then a new token will be set into the session and returned. The newly -created token will be opaque and randomized. - -You can use the returned token as the value of a hidden field in a form that -posts to a method that requires elevated privileges, or supply it as a request -header in AJAX requests. - -For example, include the CSRF token as a hidden field: - -.. code-block:: html - - <form method="post" action="/myview"> - <input type="hidden" name="csrf_token" value="${request.session.get_csrf_token()}"> - <input type="submit" value="Delete Everything"> - </form> - -Or include it as a header in a jQuery AJAX request: - -.. code-block:: javascript - - var csrfToken = ${request.session.get_csrf_token()}; - $.ajax({ - type: "POST", - url: "/myview", - headers: { 'X-CSRF-Token': csrfToken } - }).done(function() { - alert("Deleted"); - }); - -The handler for the URL that receives the request should then require that the -correct CSRF token is supplied. - -.. index:: - single: session.new_csrf_token - -Using the ``session.new_csrf_token`` Method -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To explicitly create a new CSRF token, use the ``session.new_csrf_token()`` -method. This differs only from ``session.get_csrf_token()`` inasmuch as it -clears any existing CSRF token, creates a new CSRF token, sets the token into -the session, and returns the token. - -.. code-block:: python - - token = request.session.new_csrf_token() - -Checking CSRF Tokens Manually -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In request handling code, you can check the presence and validity of a CSRF -token with :func:`pyramid.session.check_csrf_token`. If the token is valid, it -will return ``True``, otherwise it will raise ``HTTPBadRequest``. Optionally, -you can specify ``raises=False`` to have the check return ``False`` instead of -raising an exception. - -By default, it checks for a POST parameter named ``csrf_token`` or a header -named ``X-CSRF-Token``. - -.. code-block:: python - - from pyramid.session import check_csrf_token - - def myview(request): - # Require CSRF Token - check_csrf_token(request) - - # ... - -.. _auto_csrf_checking: - -Checking CSRF Tokens Automatically -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 1.7 - -:app:`Pyramid` supports automatically checking CSRF tokens on requests with an -unsafe method as defined by RFC2616. Any other request may be checked manually. -This feature can be turned on globally for an application using the -:meth:`pyramid.config.Configurator.set_default_csrf_options` directive. -For example: - -.. code-block:: python - - from pyramid.config import Configurator - - config = Configurator() - config.set_default_csrf_options(require_csrf=True) - -CSRF checking may be explicitly enabled or disabled on a per-view basis using -the ``require_csrf`` view option. A value of ``True`` or ``False`` will -override the default set by ``set_default_csrf_options``. For example: - -.. code-block:: python - - @view_config(route_name='hello', require_csrf=False) - def myview(request): - # ... - -When CSRF checking is active, the token and header used to find the -supplied CSRF token will be ``csrf_token`` and ``X-CSRF-Token``, respectively, -unless otherwise overridden by ``set_default_csrf_options``. The token is -checked against the value in ``request.POST`` which is the submitted form body. -If this value is not present, then the header will be checked. - -In addition to token based CSRF checks, if the request is using HTTPS then the -automatic CSRF checking will also check the referrer of the request to ensure -that it matches one of the trusted origins. By default the only trusted origin -is the current host, however additional origins may be configured by setting -``pyramid.csrf_trusted_origins`` to a list of domain names (and ports if they -are non standard). If a host in the list of domains starts with a ``.`` then -that will allow all subdomains as well as the domain without the ``.``. - -If CSRF checks fail then a :class:`pyramid.exceptions.BadCSRFToken` or -:class:`pyramid.exceptions.BadCSRFOrigin` exception will be raised. This -exception may be caught and handled by an :term:`exception view` but, by -default, will result in a ``400 Bad Request`` response being sent to the -client. - -Checking CSRF Tokens with a View Predicate -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 1.7 - Use the ``require_csrf`` option or read :ref:`auto_csrf_checking` instead - to have :class:`pyramid.exceptions.BadCSRFToken` exceptions raised. - -A convenient way to require a valid CSRF token for a particular view is to -include ``check_csrf=True`` as a view predicate. See -:meth:`pyramid.config.Configurator.add_view`. - -.. code-block:: python - - @view_config(request_method='POST', check_csrf=True, ...) - def myview(request): - ... - -.. note:: - A mismatch of a CSRF token is treated like any other predicate miss, and the - predicate system, when it doesn't find a view, raises ``HTTPNotFound`` - instead of ``HTTPBadRequest``, so ``check_csrf=True`` behavior is different - from calling :func:`pyramid.session.check_csrf_token`. diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst index 3e168eaea..5e7c7c871 100644 --- a/docs/narr/startup.rst +++ b/docs/narr/startup.rst @@ -10,11 +10,12 @@ you'll see something much like this show up on the console: $ $VENV/bin/pserve development.ini Starting server in PID 16305. - serving on http://127.0.0.1:6543 + Serving on http://localhost:6543 + Serving on http://localhost:6543 This chapter explains what happens between the time you press the "Return" key -on your keyboard after typing ``pserve development.ini`` and the time the line -``serving on http://127.0.0.1:6543`` is output to your console. +on your keyboard after typing ``pserve development.ini`` and the time the lines +``Serving on http://localhost:6543`` are output to your console. .. index:: single: startup process @@ -37,7 +38,14 @@ Here's a high-level time-ordered overview of what happens when you press begin to run and serve an application using the information contained within the ``development.ini`` file. -#. The framework finds a section named either ``[app:main]``, +#. ``pserve`` passes the ``development.ini`` path to :term:`plaster` which + finds an available configuration loader that recognizes the ``ini`` format. + +#. :term:`plaster` finds the ``plaster_pastedeploy`` library which binds + the :term:`PasteDeploy` library and returns a parser that can understand + the format. + +#. The :term:`PasteDeploy` finds a section named either ``[app:main]``, ``[pipeline:main]``, or ``[composite:main]`` in the ``.ini`` file. This section represents the configuration of a :term:`WSGI` application that will be served. If you're using a simple application (e.g., ``[app:main]``), the @@ -49,7 +57,7 @@ Here's a high-level time-ordered overview of what happens when you press application or a pipeline, you're using a "composite" (e.g., ``[composite:main]``), refer to the documentation for that particular composite to understand how to make it refer to your :app:`Pyramid` - application. In most cases, a Pyramid application built from a scaffold + application. In most cases, a Pyramid application built from a cookiecutter will have a single ``[app:main]`` section in it, and this will be the application served. @@ -69,7 +77,7 @@ Here's a high-level time-ordered overview of what happens when you press :app:`Pyramid` :term:`router` instance. Here's the contents of an example ``__init__.py`` module: - .. literalinclude:: MyProject/myproject/__init__.py + .. literalinclude:: myproject/myproject/__init__.py :language: python :linenos: @@ -85,12 +93,12 @@ Here's a high-level time-ordered overview of what happens when you press Our generated ``development.ini`` file looks like so: - .. literalinclude:: MyProject/development.ini + .. literalinclude:: myproject/development.ini :language: ini :linenos: In this case, the ``myproject.__init__:main`` function referred to by the - entry point URI ``egg:MyProject`` (see :ref:`MyProject_ini` for more + entry point URI ``egg:myproject`` (see :ref:`myproject_ini` for more information about entry point URIs, and how they relate to callables) will receive the key/value pairs ``{pyramid.reload_templates = true, pyramid.debug_authorization = false, pyramid.debug_notfound = false, @@ -130,10 +138,10 @@ Here's a high-level time-ordered overview of what happens when you press #. ``pserve`` starts the WSGI *server* defined within the ``[server:main]`` section. In our case, this is the Waitress server (``use = - egg:waitress#main``), and it will listen on all interfaces (``host = - 127.0.0.1``), on port number 6543 (``port = 6543``). The server code itself - is what prints ``serving on http://127.0.0.1:6543``. The server serves the - application, and the application is running, waiting to receive requests. + egg:waitress#main``), and it will listen on all interfaces on port 6543 + for both IPv4 and IPv6 (``listen = localhost:6543``). The server + code itself is what prints ``Serving on http://localhost:6543``. The server + serves the application, and the application is running, waiting to receive requests. .. seealso:: Logging configuration is described in the :ref:`logging_chapter` chapter. diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst index 6b3b5fcce..4eadbd2f0 100644 --- a/docs/narr/templates.rst +++ b/docs/narr/templates.rst @@ -228,6 +228,10 @@ These values are provided to the template: provided if the template is rendered as the result of a ``renderer=`` argument to the view configuration being used. +``get_csrf_token()`` + A convenience function to access the current CSRF token. See + :ref:`get_csrf_token_in_templates` for more information. + ``renderer_name`` The renderer name used to perform the rendering, e.g., ``mypackage:templates/foo.pt``. diff --git a/docs/narr/testing.rst b/docs/narr/testing.rst index 354a462d4..406383bbd 100644 --- a/docs/narr/testing.rst +++ b/docs/narr/testing.rst @@ -370,11 +370,11 @@ coverage reports. Regardless of which testing :term:`package` you use, be sure to add a ``tests_require`` dependency on that package to your application's ``setup.py`` -file. Using the project ``MyProject`` generated by the starter scaffold as +file. Using the project ``myproject`` generated by the starter cookiecutter as described in :doc:`project`, we would insert the following code immediately -following the ``requires`` block in the file ``MyProject/setup.py``. +following the ``requires`` block in the file ``myproject/setup.py``. -.. literalinclude:: MyProject/setup.py +.. literalinclude:: myproject/setup.py :language: python :linenos: :lines: 11-22 @@ -383,7 +383,7 @@ following the ``requires`` block in the file ``MyProject/setup.py``. Remember to change the dependency. -.. literalinclude:: MyProject/setup.py +.. literalinclude:: myproject/setup.py :language: python :linenos: :lines: 40-44 @@ -401,14 +401,14 @@ In your ``MyPackage`` project, your :term:`package` is named ``myproject`` which contains a ``views`` module, which in turn contains a :term:`view` function ``my_view`` that returns an HTML body when the root URL is invoked: - .. literalinclude:: MyProject/myproject/views.py + .. literalinclude:: myproject/myproject/views.py :linenos: :language: python The following example functional test demonstrates invoking the above :term:`view`: - .. literalinclude:: MyProject/myproject/tests.py + .. literalinclude:: myproject/myproject/tests.py :linenos: :pyobject: FunctionalTests :language: python diff --git a/docs/narr/upgrading.rst b/docs/narr/upgrading.rst index 4e434c3c6..e0482d5a2 100644 --- a/docs/narr/upgrading.rst +++ b/docs/narr/upgrading.rst @@ -208,7 +208,7 @@ On Windows, you need to issue two commands: .. code-block:: doscon c:\> set PYTHONWARNINGS=default - c:\> Scripts/pserve.exe development.ini + c:\> Scripts\pserve development.ini At this point, it's ensured that deprecation warnings will be printed to the console whenever a codepath is hit that generates one. You can then click diff --git a/docs/narr/vhosting.rst b/docs/narr/vhosting.rst index 0edf03353..e4cee9882 100644 --- a/docs/narr/vhosting.rst +++ b/docs/narr/vhosting.rst @@ -26,20 +26,20 @@ Hosting an Application Under a URL Prefix ``http://example.com/``). If you use a "pure Python" environment, this functionality can be provided by -Paste's `urlmap <http://pythonpaste.org/modules/urlmap.html>`_ "composite" WSGI -application. Alternatively, you can use :term:`mod_wsgi` to serve your +`rutter <http://rutter.readthedocs.io/en/latest/>`_, forming a "composite" +WSGI application. Alternatively, you can use :term:`mod_wsgi` to serve your application, which handles this virtual hosting translation for you "under the hood". -If you use the ``urlmap`` composite application "in front" of a :app:`Pyramid` +If you use the ``rutter`` composite application "in front" of a :app:`Pyramid` application or if you use :term:`mod_wsgi` to serve up a :app:`Pyramid` application, nothing special needs to be done within the application for URLs -to be generated that contain a prefix. :mod:`paste.urlmap` and :term:`mod_wsgi` +to be generated that contain a prefix. Rutter and :term:`mod_wsgi` manipulate the :term:`WSGI` environment in such a way that the ``PATH_INFO`` and ``SCRIPT_NAME`` variables are correct for some given prefix. Here's an example of a PasteDeploy configuration snippet that includes a -``urlmap`` composite. +``rutter`` composite. .. code-block:: ini :linenos: @@ -48,13 +48,13 @@ Here's an example of a PasteDeploy configuration snippet that includes a use = egg:mypyramidapp [composite:main] - use = egg:Paste#urlmap + use = egg:rutter#urlmap /pyramidapp = mypyramidapp This "roots" the :app:`Pyramid` application at the prefix ``/pyramidapp`` and serves up the composite as the "main" application in the file. -.. note:: If you're using an Apache server to proxy to a Paste ``urlmap`` +.. note:: If you're using an Apache server to proxy to a ``urlmap`` composite, you may have to use the `ProxyPreserveHost <http://httpd.apache.org/docs/2.2/mod/mod_proxy.html#proxypreservehost>`_ directive to pass the original ``HTTP_HOST`` header along to the diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index 7cb8e0306..3b683ff79 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -252,7 +252,7 @@ Non-Predicate Arguments def myview(request): ... - Is similar to doing:: + Is similar to decorating the view callable directly:: @view_config(...) @decorator2 @@ -260,8 +260,10 @@ Non-Predicate Arguments def myview(request): ... - All view callables in the decorator chain must return a response object - implementing :class:`pyramid.interfaces.IResponse` or raise an exception: + An important distinction is that each decorator will receive a response + object implementing :class:`pyramid.interfaces.IResponse` instead of the + raw value returned from the view callable. All decorators in the chain must + return a response object or raise an exception: .. code-block:: python diff --git a/docs/narr/views.rst b/docs/narr/views.rst index ab139ea19..e8a07202e 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -52,7 +52,7 @@ of exceptions from within the body of a view callable. Defining a View Callable as a Function -------------------------------------- -One of the easiest way to define a view callable is to create a function that +One of the easiest ways to define a view callable is to create a function that accepts a single argument named ``request``, and which returns a :term:`Response` object. For example, this is a "hello world" view callable implemented as a function: @@ -246,7 +246,7 @@ within view code, the result of the :term:`Not Found View` will be returned to the user agent which performed the request. If :exc:`~pyramid.httpexceptions.HTTPForbidden` is raised by Pyramid itself -within view code, the result of the :term:`Forbidden View` will be returned to +or within view code, the result of the :term:`Forbidden View` will be returned to the user agent which performed the request. .. index:: @@ -523,8 +523,7 @@ Alternate View Callable Argument/Calling Conventions ---------------------------------------------------- Usually view callables are defined to accept only a single argument: -``request``. However, view callables may alternately be defined as classes, -functions, or any callable that accept *two* positional arguments: a +``request``. However, a view callable may alternately be defined as any class, function, or callable that accepts *two* positional arguments: a :term:`context` resource as the first argument and a :term:`request` as the second argument. diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst index ce1586834..578fdeb51 100644 --- a/docs/narr/webob.rst +++ b/docs/narr/webob.rst @@ -345,8 +345,8 @@ against your ``mypackage`` package during application initialization. .. note:: This is only an example. In particular, it is not necessary to cause - ``DBSession.remove`` to be called in an application generated from any - :app:`Pyramid` scaffold, because these all use the ``pyramid_tm`` package. + ``DBSession.remove`` to be called in an application generated from a + :app:`Pyramid` cookiecutter, because these all use the ``pyramid_tm`` package. The cleanup done by ``DBSession.remove`` is unnecessary when ``pyramid_tm`` :term:`middleware` is configured into the application. diff --git a/docs/pscripts/pcreate.rst b/docs/pscripts/pcreate.rst index b5ec3f4e2..c48a70acd 100644 --- a/docs/pscripts/pcreate.rst +++ b/docs/pscripts/pcreate.rst @@ -1,13 +1,9 @@ .. index:: - single: pcreate; --help + single: pcreate; --help .. _pcreate_script: -``pcreate`` ------------ - -.. program-output:: pcreate --help - :prompt: - :shell: +.. autoprogram:: pyramid.scripts.pcreate:PCreateCommand.parser + :prog: pcreate .. seealso:: :ref:`creating_a_project` diff --git a/docs/pscripts/pdistreport.rst b/docs/pscripts/pdistreport.rst index 1c53fb6e9..b3c9dffd8 100644 --- a/docs/pscripts/pdistreport.rst +++ b/docs/pscripts/pdistreport.rst @@ -1,13 +1,9 @@ .. index:: - single: pdistreport; --help + single: pdistreport; --help .. _pdistreport_script: -``pdistreport`` ---------------- - -.. program-output:: pdistreport --help - :prompt: - :shell: +.. autoprogram:: pyramid.scripts.pdistreport:get_parser() + :prog: pdistreport .. seealso:: :ref:`showing_distributions` diff --git a/docs/pscripts/prequest.rst b/docs/pscripts/prequest.rst index a15827767..64ed01739 100644 --- a/docs/pscripts/prequest.rst +++ b/docs/pscripts/prequest.rst @@ -1,13 +1,9 @@ .. index:: - single: prequest; --help + single: prequest; --help .. _prequest_script: -``prequest`` ------------- - -.. program-output:: prequest --help - :prompt: - :shell: +.. autoprogram:: pyramid.scripts.prequest:PRequestCommand.parser + :prog: prequest .. seealso:: :ref:`invoking_a_request` diff --git a/docs/pscripts/proutes.rst b/docs/pscripts/proutes.rst index 09ed013e1..ee7c209e3 100644 --- a/docs/pscripts/proutes.rst +++ b/docs/pscripts/proutes.rst @@ -3,11 +3,7 @@ .. _proutes_script: -``proutes`` ------------ - -.. program-output:: proutes --help - :prompt: - :shell: +.. autoprogram:: pyramid.scripts.proutes:PRoutesCommand.parser + :prog: proutes .. seealso:: :ref:`displaying_application_routes` diff --git a/docs/pscripts/pserve.rst b/docs/pscripts/pserve.rst index d33d4a484..991976aea 100644 --- a/docs/pscripts/pserve.rst +++ b/docs/pscripts/pserve.rst @@ -3,11 +3,7 @@ .. _pserve_script: -``pserve`` ----------- - -.. program-output:: pserve --help - :prompt: - :shell: +.. autoprogram:: pyramid.scripts.pserve:PServeCommand.parser + :prog: pserve .. seealso:: :ref:`running_the_project_application` diff --git a/docs/pscripts/pshell.rst b/docs/pscripts/pshell.rst index cfd84d4f8..4e2ce74cb 100644 --- a/docs/pscripts/pshell.rst +++ b/docs/pscripts/pshell.rst @@ -3,11 +3,7 @@ .. _pshell_script: -``pshell`` ----------- - -.. program-output:: pshell --help - :prompt: - :shell: +.. autoprogram:: pyramid.scripts.pshell:PShellCommand.parser + :prog: pshell .. seealso:: :ref:`interactive_shell` diff --git a/docs/pscripts/ptweens.rst b/docs/pscripts/ptweens.rst index 02e23e49a..f586e1467 100644 --- a/docs/pscripts/ptweens.rst +++ b/docs/pscripts/ptweens.rst @@ -3,11 +3,7 @@ .. _ptweens_script: -``ptweens`` ------------ - -.. program-output:: ptweens --help - :prompt: - :shell: +.. autoprogram:: pyramid.scripts.ptweens:PTweensCommand.parser + :prog: ptweens .. seealso:: :ref:`displaying_tweens` diff --git a/docs/pscripts/pviews.rst b/docs/pscripts/pviews.rst index b4de5c054..da4660779 100644 --- a/docs/pscripts/pviews.rst +++ b/docs/pscripts/pviews.rst @@ -3,11 +3,7 @@ .. _pviews_script: -``pviews`` ----------- - -.. program-output:: pviews --help - :prompt: - :shell: +.. autoprogram:: pyramid.scripts.pviews:PViewsCommand.parser + :prog: pviews .. seealso:: :ref:`displaying_matching_views` diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index 39b4cafb3..1265012ab 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -26,7 +26,7 @@ To save a little bit of typing and to be certain that we use the modules, scripts, and packages installed in our virtual environment, we'll set an environment variable, too. -As an example, for Python 3.5+ on Linux: +As an example, for Python 3.6+ on Linux: .. parsed-literal:: @@ -46,7 +46,7 @@ For Windows: # set an environment variable to where you want your virtual environment c:\\> set VENV=c:\\env # create the virtual environment - c:\\> %VENV%\\Scripts\\python -m venv %VENV% + c:\\> python -m venv %VENV% # install pyramid c:\\> %VENV%\\Scripts\\pip install pyramid # or for a specific released version @@ -116,7 +116,7 @@ part of a web application, web developers need a robust, mature set of software for web requests. Pyramid has always fit nicely into the existing world of Python web development -(virtual environments, packaging, scaffolding, one of the first to embrace +(virtual environments, packaging, cookiecutters, one of the first to embrace Python 3, etc.). Pyramid turned to the well-regarded :term:`WebOb` Python library for request and response handling. In our example above, Pyramid hands ``hello_world`` a ``request`` that is :ref:`based on WebOb <webob_chapter>`. @@ -491,40 +491,55 @@ more to offer: :ref:`class_as_view`. -Quick project startup with scaffolds -==================================== +Quick project startup with cookiecutters +======================================== So far we have done all of our *Quick Tour* as a single Python file. No Python packages, no structure. Most Pyramid projects, though, aren't developed this way. -To ease the process of getting started, Pyramid provides *scaffolds* that -generate sample projects from templates in Pyramid and Pyramid add-ons. -Pyramid's ``pcreate`` command can list the available scaffolds: +To ease the process of getting started, the Pylons Project provides :term:`cookiecutter`\ s that generate sample Pyramid projects from project templates. These cookiecutters will install Pyramid and its dependencies as well. + +First you'll need to install cookiecutter. .. code-block:: bash - $ pcreate --list - Available scaffolds: - alchemy: Pyramid project using SQLAlchemy, SQLite, URL dispatch, and Jinja2 - pyramid_jinja2_starter: Pyramid Jinja2 starter project - starter: Pyramid starter project using URL dispatch and Chameleon - zodb: Pyramid project using ZODB, traversal, and Chameleon + $ $VENV/bin/pip install cookiecutter -The ``pyramid_jinja2`` add-on gave us a scaffold that we can use. From the -parent directory of where we want our Python package to be generated, let's use -that scaffold to make our project: +Let's use the cookiecutter ``pyramid-cookiecutter-starter`` to create a starter Pyramid project in the current directory, entering values at the prompts as shown below for the following command. .. code-block:: bash - $ pcreate --scaffold pyramid_jinja2_starter hello_world + $ $VENV/bin/cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter -We next use the normal Python command to set up our package for development: +If prompted for the first item, accept the default ``yes`` by hitting return. + +.. code-block:: text + + You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. + Is it okay to delete and re-clone it? [yes]: yes + project_name [Pyramid Scaffold]: hello_world + repo_name [hello_world]: hello_world + Select template_language: + 1 - jinja2 + 2 - chameleon + 3 - mako + Choose from 1, 2, 3 [1]: 1 + +We then run through the following commands. .. code-block:: bash + # Change directory into your newly created project. $ cd hello_world - $ $VENV/bin/pip install -e . + # Create a new virtual environment... + $ python3 -m venv env + # ...where we upgrade packaging tools... + $ env/bin/pip install --upgrade pip setuptools + # ...and into which we install our project and its testing requirements. + $ env/bin/pip install -e ".[testing]" + # Reset our environment variable for a new virtual environment. + $ export VENV=~/hello_world/env We are moving in the direction of a full-featured Pyramid project, with a proper setup for Python standards (packaging) and Pyramid configuration. This @@ -537,14 +552,14 @@ includes a new way of running your application: Let's look at ``pserve`` and configuration in more depth. .. seealso:: See also: - :ref:`Quick Tutorial Scaffolds <qtut_scaffolds>`, + :ref:`Quick Tutorial Cookiecutters <qtut_cookiecutters>`, :ref:`project_narr`, and - :doc:`../narr/scaffolding` + :doc:`../narr/cookiecutters` Application running with ``pserve`` =================================== -Prior to scaffolds, our project mixed a number of operational details into our +Prior to the cookiecutter, our project mixed a number of operational details into our code. Why should my main code care which HTTP server I want and what port number to run on? @@ -574,7 +589,7 @@ Configuration with ``.ini`` files Earlier in *Quick Tour* we first met Pyramid's configuration system. At that point we did all configuration in Python code. For example, the port number -chosen for our HTTP server was right there in Python code. Our scaffold has +chosen for our HTTP server was right there in Python code. Our cookiecutter has moved this decision and more into the ``development.ini`` file: .. literalinclude:: quick_tour/package/development.ini @@ -591,11 +606,6 @@ sections: We have a few decisions made for us in this configuration: -#. *Choice of web server:* ``use = egg:hello_world`` tells ``pserve`` to - use the ``waitress`` server. - -#. *Port number:* ``port = 6543`` tells ``waitress`` to listen on port 6543. - #. *WSGI app:* What package has our WSGI application in it? ``use = egg:hello_world`` in the app section tells the configuration what application to load. @@ -605,7 +615,12 @@ We have a few decisions made for us in this configuration: ``pyramid.reload_templates = true`` sets this policy, which might be different in production. -Additionally the ``development.ini`` generated by this scaffold wired up +#. *Choice of web server:* ``use = egg:waitress#main`` tells ``pserve`` to + use the ``waitress`` server. + +#. *Interfaces:* ``listen = localhost:6543`` tells ``waitress`` to listen on all interfaces on port 6543 for both IPv4 and IPv6. + +Additionally the ``development.ini`` generated by this cookiecutter wired up Python's standard logging. We'll now see in the console, for example, a log on every request that comes in, as well as traceback information. @@ -626,38 +641,35 @@ and earlier we showed ``--reload`` for application reloading. available in your browser. Adding it to your project illustrates several points about configuration. -The scaffold ``pyramid_jinja2_starter`` is already configured to include the +The cookiecutter ``pyramid-cookiecutter-starter`` already configured our package to include the add-on ``pyramid_debugtoolbar`` in its ``setup.py``: .. literalinclude:: quick_tour/package/setup.py :language: python - :linenos: - :lineno-start: 11 + :lineno-match: :lines: 11-16 + :emphasize-lines: 4 It was installed when you previously ran: .. code-block:: bash - $ $VENV/bin/pip install -e . + $ $VENV/bin/pip install -e ".[testing]" The ``pyramid_debugtoolbar`` package is a Pyramid add-on, which means we need -to include its configuration into our web application. The ``pyramid_jinja2`` -add-on already took care of this for us in its ``__init__.py``: +to include its configuration into our web application. The cookiecutter already took care of this for us in its ``__init__.py``: .. literalinclude:: quick_tour/package/hello_world/__init__.py :language: python - :linenos: - :lineno-start: 16 - :lines: 19 + :lineno-match: + :lines: 8 And it uses the ``pyramid.includes`` facility in our ``development.ini``: .. literalinclude:: quick_tour/package/development.ini :language: ini - :linenos: - :lineno-start: 15 - :lines: 15-16 + :lineno-match: + :lines: 14-15 You'll now see a Pyramid logo on the right side of your browser window, which when clicked opens a new window that provides introspective access to debugging @@ -677,42 +689,23 @@ Yikes! We got this far and we haven't yet discussed tests. This is particularly egregious, as Pyramid has had a deep commitment to full test coverage since before its release. -Our ``pyramid_jinja2_starter`` scaffold generated a ``tests.py`` module with -one unit test in it. To run it, let's install the handy ``pytest`` test runner -by editing ``setup.py``. While we're at it, we'll throw in the ``pytest-cov`` -tool which yells at us for code that isn't tested. Insert and edit the -following lines as shown: +Our ``pyramid-cookiecutter-starter`` cookiecutter generated a ``tests.py`` module with +one unit test and one functional test in it. It also configured ``setup.py`` with test requirements: +``py.test`` as the test runner, ``WebTest`` for running view tests, and the +``pytest-cov`` tool which yells at us for code that isn't tested. The +highlighted lines show this: -.. code-block:: python - :linenos: - :lineno-start: 11 - :emphasize-lines: 8-12 - - requires = [ - 'pyramid', - 'pyramid_jinja2', - 'pyramid_debugtoolbar', - 'waitress', - ] - - tests_require = [ - 'WebTest >= 1.3.1', # py3 compat - 'pytest', # includes virtualenv - 'pytest-cov', - ] - -.. code-block:: python - :linenos: - :lineno-start: 34 - :emphasize-lines: 2-4 +.. literalinclude:: quick_tour/package/setup.py + :language: python + :lineno-match: + :lines: 18-22 - zip_safe=False, - extras_require={ - 'testing': tests_require, - }, +.. literalinclude:: quick_tour/package/setup.py + :language: python + :lineno-match: + :lines: 42-44 -We changed ``setup.py`` which means we need to rerun ``$VENV/bin/pip install -e -".[testing]"``. We can now run all our tests: +We already installed the test requirements when we ran the command ``$VENV/bin/pip install -e ".[testing]"``. We can now run all our tests: .. code-block:: bash @@ -723,34 +716,33 @@ This yields the following output. .. code-block:: text =========================== test session starts =========================== - platform darwin -- Python 3.5.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 - rootdir: /Users/stevepiercy/projects/hack-on-pyramid/hello_world, inifile: - plugins: cov-2.2.1 - collected 1 items - - hello_world/tests.py . - ------------- coverage: platform darwin, python 3.5.0-final-0 ------------- - Name Stmts Miss Cover Missing - -------------------------------------------------------- - hello_world/__init__.py 11 8 27% 11-23 - hello_world/resources.py 5 1 80% 8 - hello_world/tests.py 14 0 100% - hello_world/views.py 4 0 100% - -------------------------------------------------------- - TOTAL 34 9 74% - - ========================= 1 passed in 0.22 seconds ========================= - -Our unit test passed, although its coverage is incomplete. What did our test -look like? + platform darwin -- Python 3.6.0, pytest-3.0.5, py-1.4.32, pluggy-0.4.0 + rootdir: /Users/stevepiercy/hello_world, inifile: pytest.ini + plugins: cov-2.4.0 + collected 2 items + + hello_world/tests.py .. + + ------------- coverage: platform darwin, python 3.6.0-final-0 ------------- + Name Stmts Miss Cover Missing + ----------------------------------------------------------------------- + hello_world/__init__.py 8 0 100% + hello_world/views.py 3 0 100% + ----------------------------------------------------------------------- + TOTAL 11 0 100% + + + ========================= 2 passed in 1.37 seconds ========================= + +Our tests passed, and its coverage is complete. What did our test look like? .. literalinclude:: quick_tour/package/hello_world/tests.py :language: python :linenos: Pyramid supplies helpers for test writing, which we use in the test setup and -teardown. Our one test imports the view, makes a dummy request, and sees if the -view returns what we expected. +teardown. Our first test imports the view, makes a dummy request, and sees if the +view returns what we expected. Our second test verifies that the response body from a request to the web root contains what we expected. .. seealso:: See also: :ref:`Quick Tutorial Unit Testing <qtut_unit_testing>`, :ref:`Quick @@ -764,38 +756,34 @@ It's important to know what is going on inside our web application. In development we might need to collect some output. In production we might need to detect situations when other people use the site. We need *logging*. -Fortunately Pyramid uses the normal Python approach to logging. The scaffold -generated in your ``development.ini`` has a number of lines that configure the +Fortunately Pyramid uses the normal Python approach to logging. The ``development.ini`` file for your project has a number of lines that configure the logging for you to some reasonable defaults. You then see messages sent by Pyramid (for example, when a new request comes in). Maybe you would like to log messages in your code? In your Python module, -import and set up the logging: +import and set up the logging in your ``views.py``: -.. literalinclude:: quick_tour/package/hello_world/views.py +.. literalinclude:: quick_tour/logging/hello_world/views.py :language: python - :linenos: - :lineno-start: 3 + :lineno-match: :lines: 3-4 You can now, in your code, log messages: -.. literalinclude:: quick_tour/package/hello_world/views.py +.. literalinclude:: quick_tour/logging/hello_world/views.py :language: python - :linenos: - :lineno-start: 9 - :lines: 9-10 + :lineno-match: + :lines: 7-8 :emphasize-lines: 2 -This will log ``Some Message`` at a ``debug`` log level to the +This will log ``Some Message`` at a ``DEBUG`` log level to the application-configured logger in your ``development.ini``. What controls that? These emphasized sections in the configuration file: -.. literalinclude:: quick_tour/package/development.ini +.. literalinclude:: quick_tour/logging/development.ini :language: ini - :linenos: - :lineno-start: 36 - :lines: 36-52 + :lineno-match: + :lines: 34-50 :emphasize-lines: 1-2,14-17 Our application, a package named ``hello_world``, is set up as a logger and @@ -804,7 +792,7 @@ http://localhost:6543, your console will now show: .. code-block:: text - 2016-01-18 13:55:55,040 DEBUG [hello_world.views:10][waitress] Some Message + 2016-12-25 03:03:57,059 DEBUG [hello_world.views:8][waitress] Some Message .. seealso:: See also: :ref:`Quick Tutorial Logging <qtut_logging>` and :ref:`logging_chapter`. @@ -822,11 +810,10 @@ your own custom sessioning engine. Let's take a look at the :doc:`built-in sessioning support <../narr/sessions>`. In our ``__init__.py`` we first import the kind of sessioning we want: -.. literalinclude:: quick_tour/package/hello_world/__init__.py +.. literalinclude:: quick_tour/sessions/hello_world/__init__.py :language: python - :linenos: - :lineno-start: 2 - :lines: 2-3 + :lineno-match: + :lines: 1-2 :emphasize-lines: 2 .. warning:: @@ -837,31 +824,28 @@ the kind of sessioning we want: Now make a "factory" and pass it to the :term:`configurator`'s ``session_factory`` argument: -.. literalinclude:: quick_tour/package/hello_world/__init__.py +.. literalinclude:: quick_tour/sessions/hello_world/__init__.py :language: python - :linenos: - :lineno-start: 13 - :lines: 13-17 - :emphasize-lines: 3-5 + :lineno-match: + :lines: 10-13 + :emphasize-lines: 2-3 Pyramid's :term:`request` object now has a ``session`` attribute that we can use in our view code in ``views.py``: -.. literalinclude:: quick_tour/package/hello_world/views.py +.. literalinclude:: quick_tour/sessions/hello_world/views.py :language: python - :linenos: - :lineno-start: 9 - :lines: 9-15 + :lineno-match: + :lines: 7- :emphasize-lines: 3-7 -We need to update our Jinja2 template to show counter increment in the session: +We need to update our Jinja2 template ``templates/mytemplate.jinja2`` to show counter increment in the session: -.. literalinclude:: quick_tour/package/hello_world/templates/mytemplate.jinja2 +.. literalinclude:: quick_tour/sessions/hello_world/templates/mytemplate.jinja2 :language: jinja - :linenos: - :lineno-start: 40 - :lines: 40-42 - :emphasize-lines: 3 + :lineno-match: + :lines: 4-8 + :emphasize-lines: 4 .. seealso:: See also: :ref:`Quick Tutorial Sessions <qtut_sessions>`, :ref:`sessions_chapter`, @@ -877,13 +861,36 @@ databases frequently mean an "ORM" (object-relational mapper.) In Python, ORM usually leads to the mega-quality *SQLAlchemy*, a Python package that greatly eases working with databases. -Pyramid and SQLAlchemy are great friends. That friendship includes a scaffold! +Pyramid and SQLAlchemy are great friends. That friendship includes a cookiecutter! + +.. code-block:: bash + + $ cd ~ + $ env/bin/cookiecutter https://github.com/Pylons/pyramid-cookiecutter-alchemy + +If prompted for the first item, accept the default ``yes`` by hitting return. + +.. code-block:: text + + You've cloned ~/.cookiecutters/pyramid-cookiecutter-alchemy before. + Is it okay to delete and re-clone it? [yes]: yes + project_name [Pyramid Scaffold]: sqla_demo + repo_name [sqla_demo]: sqla_demo + +We then run through the following commands as before. .. code-block:: bash - $ $VENV/bin/pcreate --scaffold alchemy sqla_demo - $ cd sqla_demo - $ $VENV/bin/pip install -e . + # Change directory into your newly created project. + $ cd sqla_demo + # Create a new virtual environment... + $ python3 -m venv env + # ...where we upgrade packaging tools... + $ env/bin/pip install --upgrade pip setuptools + # ...and into which we install our project and its testing requirements. + $ env/bin/pip install -e ".[testing]" + # Reset our environment variable for a new virtual environment. + $ export VENV=~/sqla_demo/env We now have a working sample SQLAlchemy application with all dependencies installed. The sample project provides a console script to initialize a SQLite @@ -895,21 +902,21 @@ database with tables. Let's run it, then start the application: $ $VENV/bin/pserve development.ini The ORM eases the mapping of database structures into a programming language. -SQLAlchemy uses "models" for this mapping. The scaffold generated a sample +SQLAlchemy uses "models" for this mapping. The cookiecutter generated a sample model: .. literalinclude:: quick_tour/sqla_demo/sqla_demo/models/mymodel.py :language: python - :start-after: Start Sphinx Include - :end-before: End Sphinx Include + :lineno-match: + :pyobject: MyModel View code, which mediates the logic between web requests and the rest of the system, can then easily get at the data thanks to SQLAlchemy: .. literalinclude:: quick_tour/sqla_demo/sqla_demo/views/default.py :language: python - :start-after: Start Sphinx Include - :end-before: End Sphinx Include + :lineno-match: + :lines: 13 .. seealso:: See also: :ref:`Quick Tutorial Databases <qtut_databases>`, `SQLAlchemy @@ -968,14 +975,10 @@ Deform and Colander provide a very flexible combination for forms, widgets, schemas, and validation. Recent versions of Deform also include a :ref:`retail mode <deform:retail>` for gaining Deform features on custom forms. -Also the ``deform_bootstrap`` Pyramid add-on restyles the stock Deform widgets -using attractive CSS from Twitter Bootstrap and more powerful widgets from -Chosen. +Deform uses attractive CSS from Twitter Bootstrap and more powerful select, checkbox, and date and time widgets. .. seealso:: See also: - :ref:`Quick Tutorial Forms <qtut_forms>`, :ref:`Deform <deform:overview>`, - :ref:`Colander <colander:overview>`, and `deform_bootstrap - <https://pypi.python.org/pypi/deform_bootstrap>`_. + :ref:`Quick Tutorial Forms <qtut_forms>`, :ref:`Deform <deform:overview>`, and :ref:`Colander <colander:overview>`. Conclusion ========== diff --git a/docs/quick_tour/logging/.coveragerc b/docs/quick_tour/logging/.coveragerc new file mode 100644 index 000000000..128e26410 --- /dev/null +++ b/docs/quick_tour/logging/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = hello_world +omit = hello_world/test* diff --git a/docs/quick_tour/logging/CHANGES.txt b/docs/quick_tour/logging/CHANGES.txt new file mode 100644 index 000000000..14b902fd1 --- /dev/null +++ b/docs/quick_tour/logging/CHANGES.txt @@ -0,0 +1,4 @@ +0.0 +--- + +- Initial version. diff --git a/docs/quick_tour/logging/MANIFEST.in b/docs/quick_tour/logging/MANIFEST.in new file mode 100644 index 000000000..a75da6dad --- /dev/null +++ b/docs/quick_tour/logging/MANIFEST.in @@ -0,0 +1,2 @@ +include *.txt *.ini *.cfg *.rst +recursive-include hello_world *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/quick_tour/logging/README.txt b/docs/quick_tour/logging/README.txt new file mode 100644 index 000000000..ff70a1354 --- /dev/null +++ b/docs/quick_tour/logging/README.txt @@ -0,0 +1,29 @@ +hello_world +=========== + +Getting Started +--------------- + +- Change directory into your newly created project. + + cd hello_world + +- Create a Python virtual environment. + + python3 -m venv env + +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/quick_tour/logging/development.ini b/docs/quick_tour/logging/development.ini new file mode 100644 index 000000000..b0210cbad --- /dev/null +++ b/docs/quick_tour/logging/development.ini @@ -0,0 +1,59 @@ +### +# app configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +### + +[app:main] +use = egg:hello_world + +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.default_locale_name = en +pyramid.includes = + pyramid_debugtoolbar + +# By default, the toolbar only appears for clients from IP addresses +# '127.0.0.1' and '::1'. +# debugtoolbar.hosts = 127.0.0.1 ::1 + +### +# wsgi server configuration +### + +[server:main] +use = egg:waitress#main +listen = localhost:6543 + +### +# logging configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +### + +[loggers] +keys = root, hello_world + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[logger_hello_world] +level = DEBUG +handlers = +qualname = hello_world + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s diff --git a/docs/quick_tutorial/scaffolds/scaffolds/__init__.py b/docs/quick_tour/logging/hello_world/__init__.py index ad5ecbc6f..49dde36d4 100644 --- a/docs/quick_tutorial/scaffolds/scaffolds/__init__.py +++ b/docs/quick_tour/logging/hello_world/__init__.py @@ -5,7 +5,7 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(settings=settings) - config.include('pyramid_chameleon') + config.include('pyramid_jinja2') config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('home', '/') config.scan() diff --git a/docs/quick_tour/logging/hello_world/static/pyramid-16x16.png b/docs/quick_tour/logging/hello_world/static/pyramid-16x16.png Binary files differnew file mode 100644 index 000000000..979203112 --- /dev/null +++ b/docs/quick_tour/logging/hello_world/static/pyramid-16x16.png diff --git a/docs/quick_tour/logging/hello_world/static/pyramid.png b/docs/quick_tour/logging/hello_world/static/pyramid.png Binary files differnew file mode 100644 index 000000000..4ab837be9 --- /dev/null +++ b/docs/quick_tour/logging/hello_world/static/pyramid.png diff --git a/docs/quick_tour/logging/hello_world/static/theme.css b/docs/quick_tour/logging/hello_world/static/theme.css new file mode 100644 index 000000000..0f4b1a4d4 --- /dev/null +++ b/docs/quick_tour/logging/hello_world/static/theme.css @@ -0,0 +1,154 @@ +@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); +body { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 300; + color: #ffffff; + background: #bc2131; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 300; +} +p { + font-weight: 300; +} +.font-normal { + font-weight: 400; +} +.font-semi-bold { + font-weight: 600; +} +.font-bold { + font-weight: 700; +} +.starter-template { + margin-top: 250px; +} +.starter-template .content { + margin-left: 10px; +} +.starter-template .content h1 { + margin-top: 10px; + font-size: 60px; +} +.starter-template .content h1 .smaller { + font-size: 40px; + color: #f2b7bd; +} +.starter-template .content .lead { + font-size: 25px; + color: #f2b7bd; +} +.starter-template .content .lead .font-normal { + color: #ffffff; +} +.starter-template .links { + float: right; + right: 0; + margin-top: 125px; +} +.starter-template .links ul { + display: block; + padding: 0; + margin: 0; +} +.starter-template .links ul li { + list-style: none; + display: inline; + margin: 0 10px; +} +.starter-template .links ul li:first-child { + margin-left: 0; +} +.starter-template .links ul li:last-child { + margin-right: 0; +} +.starter-template .links ul li.current-version { + color: #f2b7bd; + font-weight: 400; +} +.starter-template .links ul li a, a { + color: #f2b7bd; + text-decoration: underline; +} +.starter-template .links ul li a:hover, a:hover { + color: #ffffff; + text-decoration: underline; +} +.starter-template .links ul li .icon-muted { + color: #eb8b95; + margin-right: 5px; +} +.starter-template .links ul li:hover .icon-muted { + color: #ffffff; +} +.starter-template .copyright { + margin-top: 10px; + font-size: 0.9em; + color: #f2b7bd; + text-transform: lowercase; + float: right; + right: 0; +} +@media (max-width: 1199px) { + .starter-template .content h1 { + font-size: 45px; + } + .starter-template .content h1 .smaller { + font-size: 30px; + } + .starter-template .content .lead { + font-size: 20px; + } +} +@media (max-width: 991px) { + .starter-template { + margin-top: 0; + } + .starter-template .logo { + margin: 40px auto; + } + .starter-template .content { + margin-left: 0; + text-align: center; + } + .starter-template .content h1 { + margin-bottom: 20px; + } + .starter-template .links { + float: none; + text-align: center; + margin-top: 60px; + } + .starter-template .copyright { + float: none; + text-align: center; + } +} +@media (max-width: 767px) { + .starter-template .content h1 .smaller { + font-size: 25px; + display: block; + } + .starter-template .content .lead { + font-size: 16px; + } + .starter-template .links { + margin-top: 40px; + } + .starter-template .links ul li { + display: block; + margin: 0; + } + .starter-template .links ul li .icon-muted { + display: none; + } + .starter-template .copyright { + margin-top: 20px; + } +} diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt b/docs/quick_tour/logging/hello_world/templates/layout.jinja2 index f8cbe2e2c..916127267 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt +++ b/docs/quick_tour/logging/hello_world/templates/layout.jinja2 @@ -1,20 +1,20 @@ <!DOCTYPE html> -<html lang="${request.locale_name}"> +<html lang="{{request.locale_name}}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="description" content="pyramid web application"> <meta name="author" content="Pylons Project"> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> + <link rel="shortcut icon" href="{{request.static_url('hello_world:static/pyramid-16x16.png')}}"> - <title>ZODB Scaffold for The Pyramid Web Framework</title> + <title>Cookiecutter Starter project for the Pyramid Web Framework</title> <!-- Bootstrap core CSS --> <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> <!-- Custom styles for this scaffold --> - <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> + <link href="{{request.static_url('hello_world:static/theme.css')}}" rel="stylesheet"> <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> <!--[if lt IE 9]> @@ -29,22 +29,19 @@ <div class="container"> <div class="row"> <div class="col-md-2"> - <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> + <img class="logo img-responsive" src="{{request.static_url('hello_world:static/pyramid.png') }}" alt="pyramid web framework"> </div> <div class="col-md-10"> - <div class="content"> - <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1> - <p class="lead">Welcome to <span class="font-normal">${project}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p> - </div> + {% block content %} + <p>No content</p> + {% endblock content %} </div> </div> <div class="row"> <div class="links"> <ul> - <li class="current-version">Generated by v1.7</li> - <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li> <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> - <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> </ul> </div> diff --git a/docs/quick_tour/logging/hello_world/templates/mytemplate.jinja2 b/docs/quick_tour/logging/hello_world/templates/mytemplate.jinja2 new file mode 100644 index 000000000..cf2d7f996 --- /dev/null +++ b/docs/quick_tour/logging/hello_world/templates/mytemplate.jinja2 @@ -0,0 +1,8 @@ +{% extends "layout.jinja2" %} + +{% block content %} +<div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1> + <p class="lead">Welcome to <span class="font-normal">hello_world</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p> +</div> +{% endblock content %} diff --git a/docs/quick_tour/logging/hello_world/tests.py b/docs/quick_tour/logging/hello_world/tests.py new file mode 100644 index 000000000..ee9745685 --- /dev/null +++ b/docs/quick_tour/logging/hello_world/tests.py @@ -0,0 +1,29 @@ +import unittest + +from pyramid import testing + + +class ViewTests(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + + def tearDown(self): + testing.tearDown() + + def test_my_view(self): + from .views import my_view + request = testing.DummyRequest() + info = my_view(request) + self.assertEqual(info['project'], 'hello_world') + + +class FunctionalTests(unittest.TestCase): + def setUp(self): + from hello_world import main + app = main({}) + from webtest import TestApp + self.testapp = TestApp(app) + + def test_root(self): + res = self.testapp.get('/', status=200) + self.assertTrue(b'Pyramid' in res.body) diff --git a/docs/quick_tour/logging/hello_world/views.py b/docs/quick_tour/logging/hello_world/views.py new file mode 100644 index 000000000..a648d6ba3 --- /dev/null +++ b/docs/quick_tour/logging/hello_world/views.py @@ -0,0 +1,9 @@ +from pyramid.view import view_config + +import logging +log = logging.getLogger(__name__) + +@view_config(route_name='home', renderer='templates/mytemplate.jinja2') +def my_view(request): + log.debug('Some Message') + return {'project': 'hello_world'} diff --git a/docs/narr/MyProject/production.ini b/docs/quick_tour/logging/production.ini index 1174b1cc7..9c12bc4ec 100644 --- a/docs/narr/MyProject/production.ini +++ b/docs/quick_tour/logging/production.ini @@ -1,10 +1,10 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] -use = egg:MyProject +use = egg:hello_world pyramid.reload_templates = false pyramid.debug_authorization = false @@ -18,16 +18,15 @@ pyramid.default_locale_name = en [server:main] use = egg:waitress#main -host = 0.0.0.0 -port = 6543 +listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] -keys = root, myproject +keys = root, hello_world [handlers] keys = console @@ -39,10 +38,10 @@ keys = generic level = WARN handlers = console -[logger_myproject] +[logger_hello_world] level = WARN handlers = -qualname = myproject +qualname = hello_world [handler_console] class = StreamHandler diff --git a/docs/quick_tour/logging/pytest.ini b/docs/quick_tour/logging/pytest.ini new file mode 100644 index 000000000..f707d54e4 --- /dev/null +++ b/docs/quick_tour/logging/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = hello_world +python_files = *.py diff --git a/docs/quick_tour/logging/setup.py b/docs/quick_tour/logging/setup.py new file mode 100644 index 000000000..e32aecacd --- /dev/null +++ b/docs/quick_tour/logging/setup.py @@ -0,0 +1,51 @@ +import os + +from setuptools import setup, find_packages + +here = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(here, 'README.txt')) as f: + README = f.read() +with open(os.path.join(here, 'CHANGES.txt')) as f: + CHANGES = f.read() + +requires = [ + 'pyramid', + 'pyramid_jinja2', + 'pyramid_debugtoolbar', + 'waitress', +] + +tests_require = [ + 'WebTest >= 1.3.1', # py3 compat + 'pytest', + 'pytest-cov', +] + +setup( + name='hello_world', + version='0.0', + description='hello_world', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = hello_world:main', + ], + }, +) diff --git a/docs/quick_tour/package/.coveragerc b/docs/quick_tour/package/.coveragerc new file mode 100644 index 000000000..128e26410 --- /dev/null +++ b/docs/quick_tour/package/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = hello_world +omit = hello_world/test* diff --git a/docs/quick_tour/package/CHANGES.txt b/docs/quick_tour/package/CHANGES.txt index ffa255da8..14b902fd1 100644 --- a/docs/quick_tour/package/CHANGES.txt +++ b/docs/quick_tour/package/CHANGES.txt @@ -1,4 +1,4 @@ 0.0 --- -- Initial version +- Initial version. diff --git a/docs/quick_tour/package/MANIFEST.in b/docs/quick_tour/package/MANIFEST.in index 1d0352f7d..a75da6dad 100644 --- a/docs/quick_tour/package/MANIFEST.in +++ b/docs/quick_tour/package/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include hello_world *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.jinja2 *.js *.html *.xml +recursive-include hello_world *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/quick_tour/package/README.txt b/docs/quick_tour/package/README.txt index 63aaf6fbd..ff70a1354 100644 --- a/docs/quick_tour/package/README.txt +++ b/docs/quick_tour/package/README.txt @@ -1,4 +1,29 @@ -hello_world README +hello_world +=========== +Getting Started +--------------- +- Change directory into your newly created project. + cd hello_world + +- Create a Python virtual environment. + + python3 -m venv env + +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/quick_tour/package/development.ini b/docs/quick_tour/package/development.ini index 20f9817a9..b0210cbad 100644 --- a/docs/quick_tour/package/development.ini +++ b/docs/quick_tour/package/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.6-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -10,7 +10,6 @@ pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false -pyramid.debug_templates = true pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar @@ -25,12 +24,11 @@ pyramid.includes = [server:main] use = egg:waitress#main -host = 127.0.0.1 -port = 6543 +listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.6-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/quick_tour/package/hello_world/__init__.py b/docs/quick_tour/package/hello_world/__init__.py index 97f93d5a8..49dde36d4 100644 --- a/docs/quick_tour/package/hello_world/__init__.py +++ b/docs/quick_tour/package/hello_world/__init__.py @@ -1,26 +1,12 @@ from pyramid.config import Configurator -from hello_world.resources import get_root -from pyramid.session import SignedCookieSessionFactory def main(global_config, **settings): - """ This function returns a WSGI application. - - It is usually called by the PasteDeploy framework during - ``paster serve``. + """ This function returns a Pyramid WSGI application. """ - settings = dict(settings) - settings.setdefault('jinja2.i18n.domain', 'hello_world') - - my_session_factory = SignedCookieSessionFactory('itsaseekreet') - config = Configurator(root_factory=get_root, settings=settings, - session_factory=my_session_factory) - config.add_translation_dirs('locale/') + config = Configurator(settings=settings) config.include('pyramid_jinja2') - - config.add_static_view('static', 'static') - config.add_view('hello_world.views.my_view', - context='hello_world.resources.MyResource', - renderer="templates/mytemplate.jinja2") - + config.add_static_view('static', 'static', cache_max_age=3600) + config.add_route('home', '/') + config.scan() return config.make_wsgi_app() diff --git a/docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.mo b/docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.mo Binary files differdeleted file mode 100644 index 40bf0c271..000000000 --- a/docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.mo +++ /dev/null diff --git a/docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.po b/docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.po deleted file mode 100644 index 0df243dba..000000000 --- a/docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.po +++ /dev/null @@ -1,21 +0,0 @@ -# Translations template for PROJECT. -# Copyright (C) 2011 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR <EMAIL@ADDRESS>, 2011. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2011-05-12 09:14-0330\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 0.9.6\n" - -msgid "Hello!" -msgstr "Hallo!" diff --git a/docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.mo b/docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.mo Binary files differdeleted file mode 100644 index 4fc438bfe..000000000 --- a/docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.mo +++ /dev/null diff --git a/docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.po b/docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.po deleted file mode 100644 index dc0aae5d7..000000000 --- a/docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.po +++ /dev/null @@ -1,21 +0,0 @@ -# Translations template for PROJECT. -# Copyright (C) 2011 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR <EMAIL@ADDRESS>, 2011. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2011-05-12 09:14-0330\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 0.9.6\n" - -msgid "Hello!" -msgstr "Bonjour!" diff --git a/docs/quick_tour/package/hello_world/locale/hello_world.pot b/docs/quick_tour/package/hello_world/locale/hello_world.pot deleted file mode 100644 index 9c9460cb2..000000000 --- a/docs/quick_tour/package/hello_world/locale/hello_world.pot +++ /dev/null @@ -1,21 +0,0 @@ -# Translations template for PROJECT. -# Copyright (C) 2011 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR <EMAIL@ADDRESS>, 2011. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2011-05-12 09:14-0330\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 0.9.6\n" - -msgid "Hello!" -msgstr "" diff --git a/docs/quick_tour/package/hello_world/resources.py b/docs/quick_tour/package/hello_world/resources.py deleted file mode 100644 index e89c2f363..000000000 --- a/docs/quick_tour/package/hello_world/resources.py +++ /dev/null @@ -1,8 +0,0 @@ -class MyResource(object): - pass - -root = MyResource() - - -def get_root(request): - return root diff --git a/docs/quick_tour/package/hello_world/static/favicon.ico b/docs/quick_tour/package/hello_world/static/favicon.ico Binary files differdeleted file mode 100644 index 71f837c9e..000000000 --- a/docs/quick_tour/package/hello_world/static/favicon.ico +++ /dev/null diff --git a/docs/quick_tour/package/hello_world/static/theme.css b/docs/quick_tour/package/hello_world/static/theme.css index e3cf3f290..0f4b1a4d4 100644 --- a/docs/quick_tour/package/hello_world/static/theme.css +++ b/docs/quick_tour/package/hello_world/static/theme.css @@ -72,10 +72,12 @@ p { color: #f2b7bd; font-weight: 400; } -.starter-template .links ul li a { - color: #ffffff; +.starter-template .links ul li a, a { + color: #f2b7bd; + text-decoration: underline; } -.starter-template .links ul li a:hover { +.starter-template .links ul li a:hover, a:hover { + color: #ffffff; text-decoration: underline; } .starter-template .links ul li .icon-muted { @@ -150,4 +152,3 @@ p { margin-top: 20px; } } - diff --git a/docs/narr/MyProject/myproject/templates/mytemplate.pt b/docs/quick_tour/package/hello_world/templates/layout.jinja2 index 543663fe8..916127267 100644 --- a/docs/narr/MyProject/myproject/templates/mytemplate.pt +++ b/docs/quick_tour/package/hello_world/templates/layout.jinja2 @@ -1,20 +1,20 @@ <!DOCTYPE html> -<html lang="${request.locale_name}"> +<html lang="{{request.locale_name}}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="description" content="pyramid web application"> <meta name="author" content="Pylons Project"> - <link rel="shortcut icon" href="${request.static_url('myproject:static/pyramid-16x16.png')}"> + <link rel="shortcut icon" href="{{request.static_url('hello_world:static/pyramid-16x16.png')}}"> - <title>Starter Scaffold for The Pyramid Web Framework</title> + <title>Cookiecutter Starter project for the Pyramid Web Framework</title> <!-- Bootstrap core CSS --> <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> <!-- Custom styles for this scaffold --> - <link href="${request.static_url('myproject:static/theme.css')}" rel="stylesheet"> + <link href="{{request.static_url('hello_world:static/theme.css')}}" rel="stylesheet"> <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> <!--[if lt IE 9]> @@ -29,22 +29,19 @@ <div class="container"> <div class="row"> <div class="col-md-2"> - <img class="logo img-responsive" src="${request.static_url('myproject:static/pyramid.png')}" alt="pyramid web framework"> + <img class="logo img-responsive" src="{{request.static_url('hello_world:static/pyramid.png') }}" alt="pyramid web framework"> </div> <div class="col-md-10"> - <div class="content"> - <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter scaffold</span></h1> - <p class="lead">Welcome to <span class="font-normal">${project}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p> - </div> + {% block content %} + <p>No content</p> + {% endblock content %} </div> </div> <div class="row"> <div class="links"> <ul> - <li class="current-version">Generated by v1.7</li> - <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li> <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> - <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> </ul> </div> diff --git a/docs/quick_tour/package/hello_world/templates/mytemplate.jinja2 b/docs/quick_tour/package/hello_world/templates/mytemplate.jinja2 index a6089aebc..cf2d7f996 100644 --- a/docs/quick_tour/package/hello_world/templates/mytemplate.jinja2 +++ b/docs/quick_tour/package/hello_world/templates/mytemplate.jinja2 @@ -1,72 +1,8 @@ -<!DOCTYPE html> -<html lang="{{request.locale_name}}"> - <head> - <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta name="description" content="pyramid web application"> - <meta name="author" content="Pylons Project"> - <link rel="shortcut icon" href="{{request.static_url('hello_world:static/pyramid-16x16.png')}}"> - - <title>Starter Scaffold for Pyramid Jinja2</title> - - <!-- Bootstrap core CSS --> - <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> - - <!-- Custom styles for this scaffold --> - <link href="{{request.static_url('hello_world:static/theme.css')}}" rel="stylesheet"> - - <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> - <!--[if lt IE 9]> - <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script> - <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> - <![endif]--> - </head> - - <body> - - <div class="starter-template"> - <div class="container"> - <div class="row"> - <div class="col-md-2"> - <img class="logo img-responsive" src="{{request.static_url('hello_world:static/pyramid.png')}}" alt="pyramid web framework"> - </div> - <div class="col-md-10"> - <div class="content"> - <h1> - <span class="font-semi-bold">Pyramid</span> - <span class="smaller">Jinja2 scaffold</span> - </h1> - <p class="lead"> - {% trans %}Hello{% endtrans %} to <span class="font-normal">{{project}}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework 1.6</span>.</p> - <p>Counter: {{ request.session.counter }}</p> - </div> - </div> - </div> - <div class="row"> - <div class="links"> - <ul> - <li class="current-version">Generated by v1.6</li> - <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.6-branch/">Docs</a></li> - <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> - <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li> - <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> - </ul> - </div> - </div> - <div class="row"> - <div class="copyright"> - Copyright © Pylons Project - </div> - </div> - </div> - </div> - - - <!-- Bootstrap core JavaScript - ================================================== --> - <!-- Placed at the end of the document so the pages load faster --> - <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script> - <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script> - </body> -</html> +{% extends "layout.jinja2" %} + +{% block content %} +<div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1> + <p class="lead">Welcome to <span class="font-normal">hello_world</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p> +</div> +{% endblock content %} diff --git a/docs/quick_tour/package/hello_world/tests.py b/docs/quick_tour/package/hello_world/tests.py index ccec14f70..ee9745685 100644 --- a/docs/quick_tour/package/hello_world/tests.py +++ b/docs/quick_tour/package/hello_world/tests.py @@ -1,20 +1,29 @@ import unittest -from pyramid import testing -from pyramid.i18n import TranslationStringFactory -_ = TranslationStringFactory('hello_world') +from pyramid import testing class ViewTests(unittest.TestCase): - def setUp(self): - testing.setUp() - + self.config = testing.setUp() + def tearDown(self): testing.tearDown() def test_my_view(self): - from hello_world.views import my_view + from .views import my_view request = testing.DummyRequest() - response = my_view(request) - self.assertEqual(response['project'], 'hello_world') + info = my_view(request) + self.assertEqual(info['project'], 'hello_world') + + +class FunctionalTests(unittest.TestCase): + def setUp(self): + from hello_world import main + app = main({}) + from webtest import TestApp + self.testapp = TestApp(app) + + def test_root(self): + res = self.testapp.get('/', status=200) + self.assertTrue(b'Pyramid' in res.body) diff --git a/docs/quick_tour/package/hello_world/views.py b/docs/quick_tour/package/hello_world/views.py index 9f7953c8e..67f78dad7 100644 --- a/docs/quick_tour/package/hello_world/views.py +++ b/docs/quick_tour/package/hello_world/views.py @@ -1,16 +1,6 @@ -from pyramid.i18n import TranslationStringFactory - -import logging -log = logging.getLogger(__name__) - -_ = TranslationStringFactory('hello_world') +from pyramid.view import view_config +@view_config(route_name='home', renderer='templates/mytemplate.jinja2') def my_view(request): - log.debug('Some Message') - session = request.session - if 'counter' in session: - session['counter'] += 1 - else: - session['counter'] = 0 return {'project': 'hello_world'} diff --git a/docs/quick_tour/package/message-extraction.ini b/docs/quick_tour/package/message-extraction.ini deleted file mode 100644 index 0c3d54bc1..000000000 --- a/docs/quick_tour/package/message-extraction.ini +++ /dev/null @@ -1,3 +0,0 @@ -[python: **.py] -[jinja2: **.jinja2] -encoding = utf-8 diff --git a/docs/quick_tour/package/production.ini b/docs/quick_tour/package/production.ini new file mode 100644 index 000000000..9c12bc4ec --- /dev/null +++ b/docs/quick_tour/package/production.ini @@ -0,0 +1,53 @@ +### +# app configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +### + +[app:main] +use = egg:hello_world + +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.default_locale_name = en + +### +# wsgi server configuration +### + +[server:main] +use = egg:waitress#main +listen = *:6543 + +### +# logging configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +### + +[loggers] +keys = root, hello_world + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console + +[logger_hello_world] +level = WARN +handlers = +qualname = hello_world + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s diff --git a/docs/quick_tour/package/pytest.ini b/docs/quick_tour/package/pytest.ini new file mode 100644 index 000000000..f707d54e4 --- /dev/null +++ b/docs/quick_tour/package/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = hello_world +python_files = *.py diff --git a/docs/quick_tour/package/setup.cfg b/docs/quick_tour/package/setup.cfg deleted file mode 100644 index 186e796fc..000000000 --- a/docs/quick_tour/package/setup.cfg +++ /dev/null @@ -1,28 +0,0 @@ -[nosetests] -match = ^test -nocapture = 1 -cover-package = hello_world -with-coverage = 1 -cover-erase = 1 - -[compile_catalog] -directory = hello_world/locale -domain = hello_world -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = hello_world/locale/hello_world.pot -width = 80 -mapping_file = message-extraction.ini - -[init_catalog] -domain = hello_world -input_file = hello_world/locale/hello_world.pot -output_dir = hello_world/locale - -[update_catalog] -domain = hello_world -input_file = hello_world/locale/hello_world.pot -output_dir = hello_world/locale -previous = true diff --git a/docs/quick_tour/package/setup.py b/docs/quick_tour/package/setup.py index 61ed3c406..e32aecacd 100644 --- a/docs/quick_tour/package/setup.py +++ b/docs/quick_tour/package/setup.py @@ -15,30 +15,37 @@ requires = [ 'waitress', ] -setup(name='hello_world', - version='0.0', - description='hello_world', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web pyramid pylons', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - install_requires=requires, - tests_require={ - 'testing': ['nose', 'coverage'], - }, - test_suite="hello_world", - entry_points="""\ - [paste.app_factory] - main = hello_world:main - """, - ) +tests_require = [ + 'WebTest >= 1.3.1', # py3 compat + 'pytest', + 'pytest-cov', +] + +setup( + name='hello_world', + version='0.0', + description='hello_world', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = hello_world:main', + ], + }, +) diff --git a/docs/quick_tour/sessions/.coveragerc b/docs/quick_tour/sessions/.coveragerc new file mode 100644 index 000000000..128e26410 --- /dev/null +++ b/docs/quick_tour/sessions/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = hello_world +omit = hello_world/test* diff --git a/docs/quick_tour/sessions/CHANGES.txt b/docs/quick_tour/sessions/CHANGES.txt new file mode 100644 index 000000000..14b902fd1 --- /dev/null +++ b/docs/quick_tour/sessions/CHANGES.txt @@ -0,0 +1,4 @@ +0.0 +--- + +- Initial version. diff --git a/docs/quick_tour/sessions/MANIFEST.in b/docs/quick_tour/sessions/MANIFEST.in new file mode 100644 index 000000000..a75da6dad --- /dev/null +++ b/docs/quick_tour/sessions/MANIFEST.in @@ -0,0 +1,2 @@ +include *.txt *.ini *.cfg *.rst +recursive-include hello_world *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/quick_tour/sessions/README.txt b/docs/quick_tour/sessions/README.txt new file mode 100644 index 000000000..ff70a1354 --- /dev/null +++ b/docs/quick_tour/sessions/README.txt @@ -0,0 +1,29 @@ +hello_world +=========== + +Getting Started +--------------- + +- Change directory into your newly created project. + + cd hello_world + +- Create a Python virtual environment. + + python3 -m venv env + +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/quick_tour/sessions/development.ini b/docs/quick_tour/sessions/development.ini new file mode 100644 index 000000000..b0210cbad --- /dev/null +++ b/docs/quick_tour/sessions/development.ini @@ -0,0 +1,59 @@ +### +# app configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +### + +[app:main] +use = egg:hello_world + +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.default_locale_name = en +pyramid.includes = + pyramid_debugtoolbar + +# By default, the toolbar only appears for clients from IP addresses +# '127.0.0.1' and '::1'. +# debugtoolbar.hosts = 127.0.0.1 ::1 + +### +# wsgi server configuration +### + +[server:main] +use = egg:waitress#main +listen = localhost:6543 + +### +# logging configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +### + +[loggers] +keys = root, hello_world + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[logger_hello_world] +level = DEBUG +handlers = +qualname = hello_world + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s diff --git a/docs/quick_tour/sessions/hello_world/__init__.py b/docs/quick_tour/sessions/hello_world/__init__.py new file mode 100644 index 000000000..7cdc55ebe --- /dev/null +++ b/docs/quick_tour/sessions/hello_world/__init__.py @@ -0,0 +1,14 @@ +from pyramid.config import Configurator +from pyramid.session import SignedCookieSessionFactory + +def main(global_config, **settings): + """ This function returns a Pyramid WSGI application. + """ + config = Configurator(settings=settings) + config.include('pyramid_jinja2') + config.add_static_view('static', 'static', cache_max_age=3600) + config.add_route('home', '/') + my_session_factory = SignedCookieSessionFactory('itsaseekreet') + config.set_session_factory(my_session_factory) + config.scan() + return config.make_wsgi_app() diff --git a/docs/quick_tour/sessions/hello_world/static/pyramid-16x16.png b/docs/quick_tour/sessions/hello_world/static/pyramid-16x16.png Binary files differnew file mode 100644 index 000000000..979203112 --- /dev/null +++ b/docs/quick_tour/sessions/hello_world/static/pyramid-16x16.png diff --git a/docs/quick_tour/sessions/hello_world/static/pyramid.png b/docs/quick_tour/sessions/hello_world/static/pyramid.png Binary files differnew file mode 100644 index 000000000..4ab837be9 --- /dev/null +++ b/docs/quick_tour/sessions/hello_world/static/pyramid.png diff --git a/docs/quick_tour/sessions/hello_world/static/theme.css b/docs/quick_tour/sessions/hello_world/static/theme.css new file mode 100644 index 000000000..0f4b1a4d4 --- /dev/null +++ b/docs/quick_tour/sessions/hello_world/static/theme.css @@ -0,0 +1,154 @@ +@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); +body { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 300; + color: #ffffff; + background: #bc2131; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 300; +} +p { + font-weight: 300; +} +.font-normal { + font-weight: 400; +} +.font-semi-bold { + font-weight: 600; +} +.font-bold { + font-weight: 700; +} +.starter-template { + margin-top: 250px; +} +.starter-template .content { + margin-left: 10px; +} +.starter-template .content h1 { + margin-top: 10px; + font-size: 60px; +} +.starter-template .content h1 .smaller { + font-size: 40px; + color: #f2b7bd; +} +.starter-template .content .lead { + font-size: 25px; + color: #f2b7bd; +} +.starter-template .content .lead .font-normal { + color: #ffffff; +} +.starter-template .links { + float: right; + right: 0; + margin-top: 125px; +} +.starter-template .links ul { + display: block; + padding: 0; + margin: 0; +} +.starter-template .links ul li { + list-style: none; + display: inline; + margin: 0 10px; +} +.starter-template .links ul li:first-child { + margin-left: 0; +} +.starter-template .links ul li:last-child { + margin-right: 0; +} +.starter-template .links ul li.current-version { + color: #f2b7bd; + font-weight: 400; +} +.starter-template .links ul li a, a { + color: #f2b7bd; + text-decoration: underline; +} +.starter-template .links ul li a:hover, a:hover { + color: #ffffff; + text-decoration: underline; +} +.starter-template .links ul li .icon-muted { + color: #eb8b95; + margin-right: 5px; +} +.starter-template .links ul li:hover .icon-muted { + color: #ffffff; +} +.starter-template .copyright { + margin-top: 10px; + font-size: 0.9em; + color: #f2b7bd; + text-transform: lowercase; + float: right; + right: 0; +} +@media (max-width: 1199px) { + .starter-template .content h1 { + font-size: 45px; + } + .starter-template .content h1 .smaller { + font-size: 30px; + } + .starter-template .content .lead { + font-size: 20px; + } +} +@media (max-width: 991px) { + .starter-template { + margin-top: 0; + } + .starter-template .logo { + margin: 40px auto; + } + .starter-template .content { + margin-left: 0; + text-align: center; + } + .starter-template .content h1 { + margin-bottom: 20px; + } + .starter-template .links { + float: none; + text-align: center; + margin-top: 60px; + } + .starter-template .copyright { + float: none; + text-align: center; + } +} +@media (max-width: 767px) { + .starter-template .content h1 .smaller { + font-size: 25px; + display: block; + } + .starter-template .content .lead { + font-size: 16px; + } + .starter-template .links { + margin-top: 40px; + } + .starter-template .links ul li { + display: block; + margin: 0; + } + .starter-template .links ul li .icon-muted { + display: none; + } + .starter-template .copyright { + margin-top: 20px; + } +} diff --git a/docs/quick_tour/sessions/hello_world/templates/layout.jinja2 b/docs/quick_tour/sessions/hello_world/templates/layout.jinja2 new file mode 100644 index 000000000..916127267 --- /dev/null +++ b/docs/quick_tour/sessions/hello_world/templates/layout.jinja2 @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<html lang="{{request.locale_name}}"> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="pyramid web application"> + <meta name="author" content="Pylons Project"> + <link rel="shortcut icon" href="{{request.static_url('hello_world:static/pyramid-16x16.png')}}"> + + <title>Cookiecutter Starter project for the Pyramid Web Framework</title> + + <!-- Bootstrap core CSS --> + <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> + + <!-- Custom styles for this scaffold --> + <link href="{{request.static_url('hello_world:static/theme.css')}}" rel="stylesheet"> + + <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> + <!--[if lt IE 9]> + <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script> + <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> + <![endif]--> + </head> + + <body> + + <div class="starter-template"> + <div class="container"> + <div class="row"> + <div class="col-md-2"> + <img class="logo img-responsive" src="{{request.static_url('hello_world:static/pyramid.png') }}" alt="pyramid web framework"> + </div> + <div class="col-md-10"> + {% block content %} + <p>No content</p> + {% endblock content %} + </div> + </div> + <div class="row"> + <div class="links"> + <ul> + <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> + </ul> + </div> + </div> + <div class="row"> + <div class="copyright"> + Copyright © Pylons Project + </div> + </div> + </div> + </div> + + + <!-- Bootstrap core JavaScript + ================================================== --> + <!-- Placed at the end of the document so the pages load faster --> + <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script> + <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script> + </body> +</html> diff --git a/docs/quick_tour/sessions/hello_world/templates/mytemplate.jinja2 b/docs/quick_tour/sessions/hello_world/templates/mytemplate.jinja2 new file mode 100644 index 000000000..c7776144c --- /dev/null +++ b/docs/quick_tour/sessions/hello_world/templates/mytemplate.jinja2 @@ -0,0 +1,9 @@ +{% extends "layout.jinja2" %} + +{% block content %} +<div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1> + <p class="lead">Welcome to <span class="font-normal">hello_world</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p> + <p>Counter: {{ request.session.counter }}</p> +</div> +{% endblock content %} diff --git a/docs/quick_tour/sessions/hello_world/tests.py b/docs/quick_tour/sessions/hello_world/tests.py new file mode 100644 index 000000000..ee9745685 --- /dev/null +++ b/docs/quick_tour/sessions/hello_world/tests.py @@ -0,0 +1,29 @@ +import unittest + +from pyramid import testing + + +class ViewTests(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + + def tearDown(self): + testing.tearDown() + + def test_my_view(self): + from .views import my_view + request = testing.DummyRequest() + info = my_view(request) + self.assertEqual(info['project'], 'hello_world') + + +class FunctionalTests(unittest.TestCase): + def setUp(self): + from hello_world import main + app = main({}) + from webtest import TestApp + self.testapp = TestApp(app) + + def test_root(self): + res = self.testapp.get('/', status=200) + self.assertTrue(b'Pyramid' in res.body) diff --git a/docs/quick_tour/sessions/hello_world/views.py b/docs/quick_tour/sessions/hello_world/views.py new file mode 100644 index 000000000..9716f854e --- /dev/null +++ b/docs/quick_tour/sessions/hello_world/views.py @@ -0,0 +1,14 @@ +from pyramid.view import view_config + +import logging +log = logging.getLogger(__name__) + +@view_config(route_name='home', renderer='templates/mytemplate.jinja2') +def my_view(request): + log.debug('Some Message') + session = request.session + if 'counter' in session: + session['counter'] += 1 + else: + session['counter'] = 0 + return {'project': 'hello_world'} diff --git a/docs/quick_tour/sessions/production.ini b/docs/quick_tour/sessions/production.ini new file mode 100644 index 000000000..9c12bc4ec --- /dev/null +++ b/docs/quick_tour/sessions/production.ini @@ -0,0 +1,53 @@ +### +# app configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +### + +[app:main] +use = egg:hello_world + +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.default_locale_name = en + +### +# wsgi server configuration +### + +[server:main] +use = egg:waitress#main +listen = *:6543 + +### +# logging configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +### + +[loggers] +keys = root, hello_world + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console + +[logger_hello_world] +level = WARN +handlers = +qualname = hello_world + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s diff --git a/docs/quick_tour/sessions/pytest.ini b/docs/quick_tour/sessions/pytest.ini new file mode 100644 index 000000000..f707d54e4 --- /dev/null +++ b/docs/quick_tour/sessions/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = hello_world +python_files = *.py diff --git a/docs/quick_tour/sessions/setup.py b/docs/quick_tour/sessions/setup.py new file mode 100644 index 000000000..e32aecacd --- /dev/null +++ b/docs/quick_tour/sessions/setup.py @@ -0,0 +1,51 @@ +import os + +from setuptools import setup, find_packages + +here = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(here, 'README.txt')) as f: + README = f.read() +with open(os.path.join(here, 'CHANGES.txt')) as f: + CHANGES = f.read() + +requires = [ + 'pyramid', + 'pyramid_jinja2', + 'pyramid_debugtoolbar', + 'waitress', +] + +tests_require = [ + 'WebTest >= 1.3.1', # py3 compat + 'pytest', + 'pytest-cov', +] + +setup( + name='hello_world', + version='0.0', + description='hello_world', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = hello_world:main', + ], + }, +) diff --git a/docs/quick_tour/sqla_demo/.coveragerc b/docs/quick_tour/sqla_demo/.coveragerc new file mode 100644 index 000000000..918f8dea0 --- /dev/null +++ b/docs/quick_tour/sqla_demo/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = sqla_demo +omit = sqla_demo/test* diff --git a/docs/quick_tour/sqla_demo/CHANGES.txt b/docs/quick_tour/sqla_demo/CHANGES.txt index 35a34f332..14b902fd1 100644 --- a/docs/quick_tour/sqla_demo/CHANGES.txt +++ b/docs/quick_tour/sqla_demo/CHANGES.txt @@ -1,4 +1,4 @@ 0.0 --- -- Initial version +- Initial version. diff --git a/docs/quick_tour/sqla_demo/MANIFEST.in b/docs/quick_tour/sqla_demo/MANIFEST.in index a432577e9..e079655f9 100644 --- a/docs/quick_tour/sqla_demo/MANIFEST.in +++ b/docs/quick_tour/sqla_demo/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include sqla_demo *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include sqla_demo *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/quick_tour/sqla_demo/README.txt b/docs/quick_tour/sqla_demo/README.txt index b6d4c7798..27bbff5a7 100644 --- a/docs/quick_tour/sqla_demo/README.txt +++ b/docs/quick_tour/sqla_demo/README.txt @@ -1,14 +1,33 @@ -sqla_demo README -================== +sqla_demo +========= Getting Started --------------- -- cd <directory containing this file> +- Change directory into your newly created project. -- $VENV/bin/pip install -e . + cd sqla_demo -- $VENV/bin/initialize_sqla_demo_db development.ini +- Create a Python virtual environment. -- $VENV/bin/pserve development.ini + python3 -m venv env +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Configure the database. + + env/bin/initialize_sqla_demo_db development.ini + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/quick_tour/sqla_demo/development.ini b/docs/quick_tour/sqla_demo/development.ini index 0db0950a0..8d45a0975 100644 --- a/docs/quick_tour/sqla_demo/development.ini +++ b/docs/quick_tour/sqla_demo/development.ini @@ -13,7 +13,6 @@ pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar - pyramid_tm sqlalchemy.url = sqlite:///%(here)s/sqla_demo.sqlite @@ -27,8 +26,7 @@ sqlalchemy.url = sqlite:///%(here)s/sqla_demo.sqlite [server:main] use = egg:waitress#main -host = 127.0.0.1 -port = 6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/quick_tour/sqla_demo/production.ini b/docs/quick_tour/sqla_demo/production.ini index 38f3b6318..a85c354d3 100644 --- a/docs/quick_tour/sqla_demo/production.ini +++ b/docs/quick_tour/sqla_demo/production.ini @@ -11,15 +11,16 @@ pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en -pyramid.includes = - pyramid_tm sqlalchemy.url = sqlite:///%(here)s/sqla_demo.sqlite +### +# wsgi server configuration +### + [server:main] use = egg:waitress#main -host = 0.0.0.0 -port = 6543 +listen = *:6543 ### # logging configuration diff --git a/docs/quick_tour/sqla_demo/pytest.ini b/docs/quick_tour/sqla_demo/pytest.ini new file mode 100644 index 000000000..2d7535841 --- /dev/null +++ b/docs/quick_tour/sqla_demo/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = sqla_demo +python_files = *.py diff --git a/docs/quick_tour/sqla_demo/setup.cfg b/docs/quick_tour/sqla_demo/setup.cfg deleted file mode 100644 index 9f91cd122..000000000 --- a/docs/quick_tour/sqla_demo/setup.cfg +++ /dev/null @@ -1,27 +0,0 @@ -[nosetests] -match=^test -nocapture=1 -cover-package=sqla_demo -with-coverage=1 -cover-erase=1 - -[compile_catalog] -directory = sqla_demo/locale -domain = sqla_demo -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = sqla_demo/locale/sqla_demo.pot -width = 80 - -[init_catalog] -domain = sqla_demo -input_file = sqla_demo/locale/sqla_demo.pot -output_dir = sqla_demo/locale - -[update_catalog] -domain = sqla_demo -input_file = sqla_demo/locale/sqla_demo.pot -output_dir = sqla_demo/locale -previous = true diff --git a/docs/quick_tour/sqla_demo/setup.py b/docs/quick_tour/sqla_demo/setup.py index 312a97c06..75c1403fb 100644 --- a/docs/quick_tour/sqla_demo/setup.py +++ b/docs/quick_tour/sqla_demo/setup.py @@ -17,31 +17,42 @@ requires = [ 'transaction', 'zope.sqlalchemy', 'waitress', - ] +] -setup(name='sqla_demo', - version='0.0', - description='sqla_demo', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", +tests_require = [ + 'WebTest >= 1.3.1', # py3 compat + 'pytest', + 'pytest-cov', +] + +setup( + name='sqla_demo', + version='0.0', + description='sqla_demo', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = sqla_demo:main', + ], + 'console_scripts': [ + 'initialize_sqla_demo_db = sqla_demo.scripts.initializedb:main', ], - author='', - author_email='', - url='', - keywords='web wsgi bfg pylons pyramid', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - test_suite='sqla_demo', - install_requires=requires, - entry_points="""\ - [paste.app_factory] - main = sqla_demo:main - [console_scripts] - initialize_sqla_demo_db = sqla_demo.scripts.initializedb:main - """, - ) + }, +) diff --git a/docs/quick_tour/sqla_demo/sqla_demo/__init__.py b/docs/quick_tour/sqla_demo/sqla_demo/__init__.py index 7994bbfa8..4dab44823 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/__init__.py +++ b/docs/quick_tour/sqla_demo/sqla_demo/__init__.py @@ -6,8 +6,7 @@ def main(global_config, **settings): """ config = Configurator(settings=settings) config.include('pyramid_jinja2') - config.include('.models.meta') - config.add_static_view('static', 'static', cache_max_age=3600) - config.add_route('home', '/') + config.include('.models') + config.include('.routes') config.scan() return config.make_wsgi_app() diff --git a/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py b/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py index 76e0fd26b..339326758 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py +++ b/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py @@ -1,7 +1,73 @@ +from sqlalchemy import engine_from_config +from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import configure_mappers -# import all models classes here for sqlalchemy mappers -# to pick up -from .mymodel import MyModel # noqa +import zope.sqlalchemy -# run configure mappers to ensure we avoid any race conditions +# import or define all models here to ensure they are attached to the +# Base.metadata prior to any initialization routines +from .mymodel import MyModel # flake8: noqa + +# run configure_mappers after defining all of the models to ensure +# all relationships can be setup configure_mappers() + + +def get_engine(settings, prefix='sqlalchemy.'): + return engine_from_config(settings, prefix) + + +def get_session_factory(engine): + factory = sessionmaker() + factory.configure(bind=engine) + return factory + + +def get_tm_session(session_factory, transaction_manager): + """ + Get a ``sqlalchemy.orm.Session`` instance backed by a transaction. + + This function will hook the session to the transaction manager which + will take care of committing any changes. + + - When using pyramid_tm it will automatically be committed or aborted + depending on whether an exception is raised. + + - When using scripts you should wrap the session in a manager yourself. + For example:: + + import transaction + + engine = get_engine(settings) + session_factory = get_session_factory(engine) + with transaction.manager: + dbsession = get_tm_session(session_factory, transaction.manager) + + """ + dbsession = session_factory() + zope.sqlalchemy.register( + dbsession, transaction_manager=transaction_manager) + return dbsession + + +def includeme(config): + """ + Initialize the model for a Pyramid app. + + Activate this setup using ``config.include('sqla_demo.models')``. + + """ + settings = config.get_settings() + + # use pyramid_tm to hook the transaction lifecycle to the request + config.include('pyramid_tm') + + session_factory = get_session_factory(get_engine(settings)) + config.registry['dbsession_factory'] = session_factory + + # make request.dbsession available for use in Pyramid + config.add_request_method( + # r.tm is the transaction manager used by pyramid_tm + lambda r: get_tm_session(session_factory, r.tm), + 'dbsession', + reify=True + ) diff --git a/docs/quick_tour/sqla_demo/sqla_demo/models/meta.py b/docs/quick_tour/sqla_demo/sqla_demo/models/meta.py index 03c50ae93..0682247b5 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/models/meta.py +++ b/docs/quick_tour/sqla_demo/sqla_demo/models/meta.py @@ -1,8 +1,5 @@ -from sqlalchemy import engine_from_config from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker from sqlalchemy.schema import MetaData -import zope.sqlalchemy # Recommended naming convention used by Alembic, as various different database # providers will autogenerate vastly different names making migrations more @@ -17,33 +14,3 @@ NAMING_CONVENTION = { metadata = MetaData(naming_convention=NAMING_CONVENTION) Base = declarative_base(metadata=metadata) - - -def includeme(config): - settings = config.get_settings() - dbmaker = get_dbmaker(get_engine(settings)) - - config.add_request_method( - lambda r: get_session(r.tm, dbmaker), - 'dbsession', - reify=True - ) - - config.include('pyramid_tm') - - -def get_session(transaction_manager, dbmaker): - dbsession = dbmaker() - zope.sqlalchemy.register(dbsession, - transaction_manager=transaction_manager) - return dbsession - - -def get_engine(settings, prefix='sqlalchemy.'): - return engine_from_config(settings, prefix) - - -def get_dbmaker(engine): - dbmaker = sessionmaker() - dbmaker.configure(bind=engine) - return dbmaker diff --git a/docs/quick_tour/sqla_demo/sqla_demo/models/mymodel.py b/docs/quick_tour/sqla_demo/sqla_demo/models/mymodel.py index eb645bfe6..d65a01a42 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/models/mymodel.py +++ b/docs/quick_tour/sqla_demo/sqla_demo/models/mymodel.py @@ -1,4 +1,3 @@ -from .meta import Base from sqlalchemy import ( Column, Index, @@ -6,14 +5,14 @@ from sqlalchemy import ( Text, ) +from .meta import Base + -# Start Sphinx Include class MyModel(Base): __tablename__ = 'models' id = Column(Integer, primary_key=True) name = Column(Text) value = Column(Integer) - # End Sphinx Include Index('my_index', MyModel.name, unique=True, mysql_length=255) diff --git a/docs/quick_tour/sqla_demo/sqla_demo/routes.py b/docs/quick_tour/sqla_demo/sqla_demo/routes.py new file mode 100644 index 000000000..25504ad4d --- /dev/null +++ b/docs/quick_tour/sqla_demo/sqla_demo/routes.py @@ -0,0 +1,3 @@ +def includeme(config): + config.add_static_view('static', 'static', cache_max_age=3600) + config.add_route('home', '/') diff --git a/docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py b/docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py index f0d09729e..7307ecc5c 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py +++ b/docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py @@ -9,13 +9,13 @@ from pyramid.paster import ( from pyramid.scripts.common import parse_vars -from ..models.meta import ( - Base, - get_session, +from ..models.meta import Base +from ..models import ( get_engine, - get_dbmaker, + get_session_factory, + get_tm_session, ) -from ..models.mymodel import MyModel +from ..models import MyModel def usage(argv): @@ -34,12 +34,12 @@ def main(argv=sys.argv): settings = get_appsettings(config_uri, options=options) engine = get_engine(settings) - dbmaker = get_dbmaker(engine) - - dbsession = get_session(transaction.manager, dbmaker) - Base.metadata.create_all(engine) + session_factory = get_session_factory(engine) + with transaction.manager: + dbsession = get_tm_session(session_factory, transaction.manager) + model = MyModel(name='one', value=1) dbsession.add(model) diff --git a/docs/quick_tour/sqla_demo/sqla_demo/templates/404.jinja2 b/docs/quick_tour/sqla_demo/sqla_demo/templates/404.jinja2 new file mode 100644 index 000000000..1917f83c7 --- /dev/null +++ b/docs/quick_tour/sqla_demo/sqla_demo/templates/404.jinja2 @@ -0,0 +1,8 @@ +{% extends "layout.jinja2" %} + +{% block content %} +<div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1> + <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p> +</div> +{% endblock content %} diff --git a/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2 b/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2 index 76a098122..4607eb11f 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2 +++ b/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2 @@ -8,7 +8,7 @@ <meta name="author" content="Pylons Project"> <link rel="shortcut icon" href="{{request.static_url('sqla_demo:static/pyramid-16x16.png')}}"> - <title>Alchemy Scaffold for The Pyramid Web Framework</title> + <title>Cookiecutter Alchemy project for the Pyramid Web Framework</title> <!-- Bootstrap core CSS --> <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> @@ -29,7 +29,7 @@ <div class="container"> <div class="row"> <div class="col-md-2"> - <img class="logo img-responsive" src="{{request.static_url('sqla_demo:static/pyramid.png')}}" alt="pyramid web framework"> + <img class="logo img-responsive" src="{{request.static_url('sqla_demo:static/pyramid.png') }}" alt="pyramid web framework"> </div> <div class="col-md-10"> {% block content %} @@ -40,10 +40,8 @@ <div class="row"> <div class="links"> <ul> - <li class="current-version">Generated by v1.7.dev0</li> - <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li> <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> - <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> </ul> </div> diff --git a/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.jinja2 b/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.jinja2 index bb622bf5a..bd00845d5 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.jinja2 +++ b/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.jinja2 @@ -2,7 +2,7 @@ {% block content %} <div class="content"> - <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1> - <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework 1.7.dev0</span>.</p> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy project</span></h1> + <p class="lead">Welcome to <span class="font-normal">sqla_demo</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p> </div> {% endblock content %} diff --git a/docs/quick_tour/sqla_demo/sqla_demo/tests.py b/docs/quick_tour/sqla_demo/sqla_demo/tests.py index b6b6fdf4d..6db538ffd 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/tests.py +++ b/docs/quick_tour/sqla_demo/sqla_demo/tests.py @@ -13,19 +13,19 @@ class BaseTest(unittest.TestCase): self.config = testing.setUp(settings={ 'sqlalchemy.url': 'sqlite:///:memory:' }) - self.config.include('.models.meta') + self.config.include('.models') settings = self.config.get_settings() - from .models.meta import ( - get_session, + from .models import ( get_engine, - get_dbmaker, + get_session_factory, + get_tm_session, ) self.engine = get_engine(settings) - dbmaker = get_dbmaker(self.engine) + session_factory = get_session_factory(self.engine) - self.session = get_session(transaction.manager, dbmaker) + self.session = get_tm_session(session_factory, transaction.manager) def init_database(self): from .models.meta import Base @@ -36,7 +36,7 @@ class BaseTest(unittest.TestCase): testing.tearDown() transaction.abort() - Base.metadata.create_all(self.engine) + Base.metadata.drop_all(self.engine) class TestMyViewSuccessCondition(BaseTest): @@ -45,7 +45,7 @@ class TestMyViewSuccessCondition(BaseTest): super(TestMyViewSuccessCondition, self).setUp() self.init_database() - from .models.mymodel import MyModel + from .models import MyModel model = MyModel(name='one', value=55) self.session.add(model) diff --git a/docs/quick_tour/sqla_demo/sqla_demo/views/default.py b/docs/quick_tour/sqla_demo/sqla_demo/views/default.py index e5e70cf9d..d4afb1b0b 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/views/default.py +++ b/docs/quick_tour/sqla_demo/sqla_demo/views/default.py @@ -3,18 +3,16 @@ from pyramid.view import view_config from sqlalchemy.exc import DBAPIError -from ..models.mymodel import MyModel +from ..models import MyModel @view_config(route_name='home', renderer='../templates/mytemplate.jinja2') def my_view(request): try: query = request.dbsession.query(MyModel) - # Start Sphinx Include one = query.filter(MyModel.name == 'one').first() - # End Sphinx Include except DBAPIError: - return Response(db_err_msg, content_type='text/plain', status_int=500) + return Response(db_err_msg, content_type='text/plain', status=500) return {'one': one, 'project': 'sqla_demo'} diff --git a/docs/quick_tour/sqla_demo/sqla_demo/views/notfound.py b/docs/quick_tour/sqla_demo/sqla_demo/views/notfound.py new file mode 100644 index 000000000..69d6e2804 --- /dev/null +++ b/docs/quick_tour/sqla_demo/sqla_demo/views/notfound.py @@ -0,0 +1,7 @@ +from pyramid.view import notfound_view_config + + +@notfound_view_config(renderer='../templates/404.jinja2') +def notfound_view(request): + request.response.status = 404 + return {} diff --git a/docs/quick_tutorial/authentication/development.ini b/docs/quick_tutorial/authentication/development.ini index 8a39b2fe7..a4586d45f 100644 --- a/docs/quick_tutorial/authentication/development.ini +++ b/docs/quick_tutorial/authentication/development.ini @@ -7,5 +7,4 @@ tutorial.secret = 98zd [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/quick_tutorial/authorization/development.ini b/docs/quick_tutorial/authorization/development.ini index 8a39b2fe7..a4586d45f 100644 --- a/docs/quick_tutorial/authorization/development.ini +++ b/docs/quick_tutorial/authorization/development.ini @@ -7,5 +7,4 @@ tutorial.secret = 98zd [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/quick_tutorial/cookiecutters.rst b/docs/quick_tutorial/cookiecutters.rst new file mode 100644 index 000000000..337a5c535 --- /dev/null +++ b/docs/quick_tutorial/cookiecutters.rst @@ -0,0 +1,87 @@ +.. _qtut_cookiecutters: + +================================================= +Prelude: Quick Project Startup with Cookiecutters +================================================= + +To ease the process of getting started on a project, the Pylons Project provides :term:`cookiecutter`\ s that generate sample :app:`Pyramid` projects from project templates. These cookiecutters will install :app:`Pyramid` and its dependencies as well. We will still cover many topics of web application development using :app:`Pyramid`, but it's good to know of this facility. This prelude will demonstrate how to get a working :app:`Pyramid` web application running via ``cookiecutter``. + + +Objectives +========== + +- Use a cookiecutter to make a new project. + +- Start up a :app:`Pyramid` application and visit it in a web browser. + + +Steps +===== + +#. Install cookiecutter into your virtual environment. + + .. code-block:: bash + + $VENV/bin/pip install cookiecutter + +#. Let's use the cookiecutter ``pyramid-cookiecutter-starter`` to create a starter :app:`Pyramid` project in the current directory, entering values at the prompts as shown below for the following command. + + .. code-block:: bash + + $ $VENV/bin/cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter + + If prompted for the first item, accept the default ``yes`` by hitting return. + + .. code-block:: text + + You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. + Is it okay to delete and re-clone it? [yes]: yes + project_name [Pyramid Scaffold]: cc_starter + repo_name [cc_starter]: cc_starter + Select template_language: + 1 - jinja2 + 2 - chameleon + 3 - mako + Choose from 1, 2, 3 [1]: 1 + +#. We then run through the following commands. + + .. code-block:: bash + + # Change directory into your newly created project. + $ cd cc_starter + # Create a new virtual environment... + $ python3 -m venv env + # ...where we upgrade packaging tools... + $ env/bin/pip install --upgrade pip setuptools + # ...and into which we install our project. + $ env/bin/pip install -e . + +#. Start up the application by pointing :app:`Pyramid`'s ``pserve`` command at the + project's (generated) configuration file: + + .. code-block:: bash + + $ env/bin/pserve development.ini --reload + + On start up, ``pserve`` logs some output: + + .. code-block:: text + + Starting subprocess with file monitor + Starting server in PID 73732. + Serving on http://localhost:6543 + Serving on http://localhost:6543 + +#. Open http://localhost:6543/ in your browser. + +Analysis +======== + +Rather than starting from scratch, a cookiecutter can make it easy to get a Python +project containing a working :app:`Pyramid` application. The Pylons Project provides `several cookiecutters <https://github.com/Pylons?q=pyramid-cookiecutter>`_. + +``pserve`` is :app:`Pyramid`'s application runner, separating operational details from +your code. When you install :app:`Pyramid`, a small command program called ``pserve`` +is written to your ``bin`` directory. This program is an executable Python +module. It is passed a configuration file (in this case, ``development.ini``). diff --git a/docs/quick_tutorial/cookiecutters/.coveragerc b/docs/quick_tutorial/cookiecutters/.coveragerc new file mode 100644 index 000000000..1bcbb8c3e --- /dev/null +++ b/docs/quick_tutorial/cookiecutters/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = cc_starter +omit = cc_starter/test* diff --git a/docs/quick_tutorial/cookiecutters/CHANGES.txt b/docs/quick_tutorial/cookiecutters/CHANGES.txt new file mode 100644 index 000000000..14b902fd1 --- /dev/null +++ b/docs/quick_tutorial/cookiecutters/CHANGES.txt @@ -0,0 +1,4 @@ +0.0 +--- + +- Initial version. diff --git a/docs/quick_tutorial/cookiecutters/MANIFEST.in b/docs/quick_tutorial/cookiecutters/MANIFEST.in new file mode 100644 index 000000000..79c7ec16c --- /dev/null +++ b/docs/quick_tutorial/cookiecutters/MANIFEST.in @@ -0,0 +1,2 @@ +include *.txt *.ini *.cfg *.rst +recursive-include cc_starter *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/quick_tutorial/cookiecutters/README.txt b/docs/quick_tutorial/cookiecutters/README.txt new file mode 100644 index 000000000..55c5dcec6 --- /dev/null +++ b/docs/quick_tutorial/cookiecutters/README.txt @@ -0,0 +1,29 @@ +cc_starter +========== + +Getting Started +--------------- + +- Change directory into your newly created project. + + cd cc_starter + +- Create a Python virtual environment. + + python3 -m venv env + +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/__init__.py b/docs/quick_tutorial/cookiecutters/cc_starter/__init__.py new file mode 100644 index 000000000..49dde36d4 --- /dev/null +++ b/docs/quick_tutorial/cookiecutters/cc_starter/__init__.py @@ -0,0 +1,12 @@ +from pyramid.config import Configurator + + +def main(global_config, **settings): + """ This function returns a Pyramid WSGI application. + """ + config = Configurator(settings=settings) + config.include('pyramid_jinja2') + config.add_static_view('static', 'static', cache_max_age=3600) + config.add_route('home', '/') + config.scan() + return config.make_wsgi_app() diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/static/pyramid-16x16.png b/docs/quick_tutorial/cookiecutters/cc_starter/static/pyramid-16x16.png Binary files differnew file mode 100644 index 000000000..979203112 --- /dev/null +++ b/docs/quick_tutorial/cookiecutters/cc_starter/static/pyramid-16x16.png diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/static/pyramid.png b/docs/quick_tutorial/cookiecutters/cc_starter/static/pyramid.png Binary files differnew file mode 100644 index 000000000..4ab837be9 --- /dev/null +++ b/docs/quick_tutorial/cookiecutters/cc_starter/static/pyramid.png diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/static/theme.css b/docs/quick_tutorial/cookiecutters/cc_starter/static/theme.css new file mode 100644 index 000000000..0f4b1a4d4 --- /dev/null +++ b/docs/quick_tutorial/cookiecutters/cc_starter/static/theme.css @@ -0,0 +1,154 @@ +@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); +body { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 300; + color: #ffffff; + background: #bc2131; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 300; +} +p { + font-weight: 300; +} +.font-normal { + font-weight: 400; +} +.font-semi-bold { + font-weight: 600; +} +.font-bold { + font-weight: 700; +} +.starter-template { + margin-top: 250px; +} +.starter-template .content { + margin-left: 10px; +} +.starter-template .content h1 { + margin-top: 10px; + font-size: 60px; +} +.starter-template .content h1 .smaller { + font-size: 40px; + color: #f2b7bd; +} +.starter-template .content .lead { + font-size: 25px; + color: #f2b7bd; +} +.starter-template .content .lead .font-normal { + color: #ffffff; +} +.starter-template .links { + float: right; + right: 0; + margin-top: 125px; +} +.starter-template .links ul { + display: block; + padding: 0; + margin: 0; +} +.starter-template .links ul li { + list-style: none; + display: inline; + margin: 0 10px; +} +.starter-template .links ul li:first-child { + margin-left: 0; +} +.starter-template .links ul li:last-child { + margin-right: 0; +} +.starter-template .links ul li.current-version { + color: #f2b7bd; + font-weight: 400; +} +.starter-template .links ul li a, a { + color: #f2b7bd; + text-decoration: underline; +} +.starter-template .links ul li a:hover, a:hover { + color: #ffffff; + text-decoration: underline; +} +.starter-template .links ul li .icon-muted { + color: #eb8b95; + margin-right: 5px; +} +.starter-template .links ul li:hover .icon-muted { + color: #ffffff; +} +.starter-template .copyright { + margin-top: 10px; + font-size: 0.9em; + color: #f2b7bd; + text-transform: lowercase; + float: right; + right: 0; +} +@media (max-width: 1199px) { + .starter-template .content h1 { + font-size: 45px; + } + .starter-template .content h1 .smaller { + font-size: 30px; + } + .starter-template .content .lead { + font-size: 20px; + } +} +@media (max-width: 991px) { + .starter-template { + margin-top: 0; + } + .starter-template .logo { + margin: 40px auto; + } + .starter-template .content { + margin-left: 0; + text-align: center; + } + .starter-template .content h1 { + margin-bottom: 20px; + } + .starter-template .links { + float: none; + text-align: center; + margin-top: 60px; + } + .starter-template .copyright { + float: none; + text-align: center; + } +} +@media (max-width: 767px) { + .starter-template .content h1 .smaller { + font-size: 25px; + display: block; + } + .starter-template .content .lead { + font-size: 16px; + } + .starter-template .links { + margin-top: 40px; + } + .starter-template .links ul li { + display: block; + margin: 0; + } + .starter-template .links ul li .icon-muted { + display: none; + } + .starter-template .copyright { + margin-top: 20px; + } +} diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2 b/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2 new file mode 100644 index 000000000..20da74879 --- /dev/null +++ b/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2 @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<html lang="{{request.locale_name}}"> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="pyramid web application"> + <meta name="author" content="Pylons Project"> + <link rel="shortcut icon" href="{{request.static_url('cc_starter:static/pyramid-16x16.png')}}"> + + <title>Cookiecutter Starter project for the Pyramid Web Framework</title> + + <!-- Bootstrap core CSS --> + <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> + + <!-- Custom styles for this scaffold --> + <link href="{{request.static_url('cc_starter:static/theme.css')}}" rel="stylesheet"> + + <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> + <!--[if lt IE 9]> + <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script> + <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> + <![endif]--> + </head> + + <body> + + <div class="starter-template"> + <div class="container"> + <div class="row"> + <div class="col-md-2"> + <img class="logo img-responsive" src="{{request.static_url('cc_starter:static/pyramid.png') }}" alt="pyramid web framework"> + </div> + <div class="col-md-10"> + {% block content %} + <p>No content</p> + {% endblock content %} + </div> + </div> + <div class="row"> + <div class="links"> + <ul> + <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> + </ul> + </div> + </div> + <div class="row"> + <div class="copyright"> + Copyright © Pylons Project + </div> + </div> + </div> + </div> + + + <!-- Bootstrap core JavaScript + ================================================== --> + <!-- Placed at the end of the document so the pages load faster --> + <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script> + <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script> + </body> +</html> diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/templates/mytemplate.jinja2 b/docs/quick_tutorial/cookiecutters/cc_starter/templates/mytemplate.jinja2 new file mode 100644 index 000000000..979ee5071 --- /dev/null +++ b/docs/quick_tutorial/cookiecutters/cc_starter/templates/mytemplate.jinja2 @@ -0,0 +1,8 @@ +{% extends "layout.jinja2" %} + +{% block content %} +<div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1> + <p class="lead">Welcome to <span class="font-normal">cc_starter</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p> +</div> +{% endblock content %} diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/tests.py b/docs/quick_tutorial/cookiecutters/cc_starter/tests.py new file mode 100644 index 000000000..2f553bbb4 --- /dev/null +++ b/docs/quick_tutorial/cookiecutters/cc_starter/tests.py @@ -0,0 +1,29 @@ +import unittest + +from pyramid import testing + + +class ViewTests(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + + def tearDown(self): + testing.tearDown() + + def test_my_view(self): + from .views import my_view + request = testing.DummyRequest() + info = my_view(request) + self.assertEqual(info['project'], 'cc_starter') + + +class FunctionalTests(unittest.TestCase): + def setUp(self): + from cc_starter import main + app = main({}) + from webtest import TestApp + self.testapp = TestApp(app) + + def test_root(self): + res = self.testapp.get('/', status=200) + self.assertTrue(b'Pyramid' in res.body) diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/views.py b/docs/quick_tutorial/cookiecutters/cc_starter/views.py new file mode 100644 index 000000000..deedd53b8 --- /dev/null +++ b/docs/quick_tutorial/cookiecutters/cc_starter/views.py @@ -0,0 +1,6 @@ +from pyramid.view import view_config + + +@view_config(route_name='home', renderer='templates/mytemplate.jinja2') +def my_view(request): + return {'project': 'cc_starter'} diff --git a/docs/quick_tutorial/scaffolds/development.ini b/docs/quick_tutorial/cookiecutters/development.ini index b31d06194..a5093fb52 100644 --- a/docs/quick_tutorial/scaffolds/development.ini +++ b/docs/quick_tutorial/cookiecutters/development.ini @@ -4,14 +4,14 @@ ### [app:main] -use = egg:scaffolds +use = egg:cc_starter pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en -pyramid.includes = +pyramid.includes = pyramid_debugtoolbar # By default, the toolbar only appears for clients from IP addresses @@ -24,8 +24,7 @@ pyramid.includes = [server:main] use = egg:waitress#main -host = 0.0.0.0 -port = 6543 +listen = localhost:6543 ### # logging configuration @@ -33,7 +32,7 @@ port = 6543 ### [loggers] -keys = root, scaffolds +keys = root, cc_starter [handlers] keys = console @@ -45,10 +44,10 @@ keys = generic level = INFO handlers = console -[logger_scaffolds] +[logger_cc_starter] level = DEBUG handlers = -qualname = scaffolds +qualname = cc_starter [handler_console] class = StreamHandler @@ -57,4 +56,4 @@ level = NOTSET formatter = generic [formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s +format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s diff --git a/docs/quick_tutorial/cookiecutters/production.ini b/docs/quick_tutorial/cookiecutters/production.ini new file mode 100644 index 000000000..e24a065b1 --- /dev/null +++ b/docs/quick_tutorial/cookiecutters/production.ini @@ -0,0 +1,53 @@ +### +# app configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +### + +[app:main] +use = egg:cc_starter + +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.default_locale_name = en + +### +# wsgi server configuration +### + +[server:main] +use = egg:waitress#main +listen = *:6543 + +### +# logging configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +### + +[loggers] +keys = root, cc_starter + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console + +[logger_cc_starter] +level = WARN +handlers = +qualname = cc_starter + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s diff --git a/docs/quick_tutorial/cookiecutters/pytest.ini b/docs/quick_tutorial/cookiecutters/pytest.ini new file mode 100644 index 000000000..a7bd797f0 --- /dev/null +++ b/docs/quick_tutorial/cookiecutters/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = cc_starter +python_files = *.py diff --git a/docs/quick_tutorial/cookiecutters/setup.py b/docs/quick_tutorial/cookiecutters/setup.py new file mode 100644 index 000000000..e47cdf8ea --- /dev/null +++ b/docs/quick_tutorial/cookiecutters/setup.py @@ -0,0 +1,51 @@ +import os + +from setuptools import setup, find_packages + +here = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(here, 'README.txt')) as f: + README = f.read() +with open(os.path.join(here, 'CHANGES.txt')) as f: + CHANGES = f.read() + +requires = [ + 'pyramid', + 'pyramid_jinja2', + 'pyramid_debugtoolbar', + 'waitress', +] + +tests_require = [ + 'WebTest >= 1.3.1', # py3 compat + 'pytest', + 'pytest-cov', +] + +setup( + name='cc_starter', + version='0.0', + description='cc_starter', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = cc_starter:main', + ], + }, +) diff --git a/docs/quick_tutorial/databases.rst b/docs/quick_tutorial/databases.rst index c8d87c180..87f2703c7 100644 --- a/docs/quick_tutorial/databases.rst +++ b/docs/quick_tutorial/databases.rst @@ -21,7 +21,7 @@ storage and retrieval for the wiki pages in the previous step. .. note:: - The ``alchemy`` scaffold is really helpful for getting an SQLAlchemy + The ``pyramid-cookiecutter-alchemy`` cookiecutter is really helpful for getting an SQLAlchemy project going, including generation of the console script. Since we want to see all the decisions, we will forgo convenience in this tutorial, and wire it up ourselves. diff --git a/docs/quick_tutorial/databases/development.ini b/docs/quick_tutorial/databases/development.ini index 5da87d602..270643071 100644 --- a/docs/quick_tutorial/databases/development.ini +++ b/docs/quick_tutorial/databases/development.ini @@ -9,7 +9,6 @@ sqlalchemy.url = sqlite:///%(here)s/sqltutorial.sqlite [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 # Begin logging configuration diff --git a/docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt b/docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt index d1fea0d7f..25bab04d0 100644 --- a/docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt +++ b/docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt @@ -2,12 +2,23 @@ <html lang="en"> <head> <title>WikiPage: Add/Edit</title> + <link rel="stylesheet" + href="${request.static_url('deform:static/css/bootstrap.min.css')}" + type="text/css" media="screen" charset="utf-8"/> + <link rel="stylesheet" + href="${request.static_url('deform:static/css/form.css')}" + type="text/css"/> <tal:block tal:repeat="reqt view.reqts['css']"> <link rel="stylesheet" type="text/css" - href="${request.static_url('deform:static/' + reqt)}"/> + href="${request.static_url(reqt)}"/> </tal:block> + <script src="${request.static_url('deform:static/scripts/jquery-2.0.3.min.js')}" + type="text/javascript"></script> + <script src="${request.static_url('deform:static/scripts/bootstrap.min.js')}" + type="text/javascript"></script> + <tal:block tal:repeat="reqt view.reqts['js']"> - <script src="${request.static_url('deform:static/' + reqt)}" + <script src="${request.static_url(reqt)}" type="text/javascript"></script> </tal:block> </head> diff --git a/docs/quick_tutorial/debugtoolbar/development.ini b/docs/quick_tutorial/debugtoolbar/development.ini index 52b2a3a41..17b479011 100644 --- a/docs/quick_tutorial/debugtoolbar/development.ini +++ b/docs/quick_tutorial/debugtoolbar/development.ini @@ -5,5 +5,4 @@ pyramid.includes = [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/quick_tutorial/forms/development.ini b/docs/quick_tutorial/forms/development.ini index 4d47e54a5..7066668bf 100644 --- a/docs/quick_tutorial/forms/development.ini +++ b/docs/quick_tutorial/forms/development.ini @@ -6,5 +6,4 @@ pyramid.includes = [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt b/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt index d1fea0d7f..25bab04d0 100644 --- a/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt +++ b/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt @@ -2,12 +2,23 @@ <html lang="en"> <head> <title>WikiPage: Add/Edit</title> + <link rel="stylesheet" + href="${request.static_url('deform:static/css/bootstrap.min.css')}" + type="text/css" media="screen" charset="utf-8"/> + <link rel="stylesheet" + href="${request.static_url('deform:static/css/form.css')}" + type="text/css"/> <tal:block tal:repeat="reqt view.reqts['css']"> <link rel="stylesheet" type="text/css" - href="${request.static_url('deform:static/' + reqt)}"/> + href="${request.static_url(reqt)}"/> </tal:block> + <script src="${request.static_url('deform:static/scripts/jquery-2.0.3.min.js')}" + type="text/javascript"></script> + <script src="${request.static_url('deform:static/scripts/bootstrap.min.js')}" + type="text/javascript"></script> + <tal:block tal:repeat="reqt view.reqts['js']"> - <script src="${request.static_url('deform:static/' + reqt)}" + <script src="${request.static_url(reqt)}" type="text/javascript"></script> </tal:block> </head> diff --git a/docs/quick_tutorial/functional_testing/development.ini b/docs/quick_tutorial/functional_testing/development.ini index 52b2a3a41..17b479011 100644 --- a/docs/quick_tutorial/functional_testing/development.ini +++ b/docs/quick_tutorial/functional_testing/development.ini @@ -5,5 +5,4 @@ pyramid.includes = [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/quick_tutorial/hello_world.rst b/docs/quick_tutorial/hello_world.rst index 56dccde58..2f2515fcd 100644 --- a/docs/quick_tutorial/hello_world.rst +++ b/docs/quick_tutorial/hello_world.rst @@ -11,7 +11,7 @@ Python packages, no ``pip install -e .``, no other machinery. Background ========== -Microframeworks are all the rage these days. "Microframework" is a marketing +Microframeworks were all the rage, until the next shiny thing came along. "Microframework" is a marketing term, not a technical one. They have a low mental overhead: they do so little, the only things you have to worry about are *your things*. @@ -49,7 +49,7 @@ Steps .. code-block:: bash - $ mkdir hello_world; cd hello_world + $ cd ~/projects/quick_tutorial; mkdir hello_world; cd hello_world #. Copy the following into ``hello_world/app.py``: diff --git a/docs/quick_tutorial/index.rst b/docs/quick_tutorial/index.rst index 29b4d8fb7..b5b7b3313 100644 --- a/docs/quick_tutorial/index.rst +++ b/docs/quick_tutorial/index.rst @@ -19,7 +19,7 @@ Contents requirements tutorial_approach - scaffolds + cookiecutters hello_world package ini diff --git a/docs/quick_tutorial/ini.rst b/docs/quick_tutorial/ini.rst index 9a65d66d1..96dfc5b5f 100644 --- a/docs/quick_tutorial/ini.rst +++ b/docs/quick_tutorial/ini.rst @@ -136,7 +136,7 @@ Extra credit .. seealso:: :ref:`project_narr`, - :ref:`scaffolding_chapter`, + :ref:`cookiecutters`, :ref:`what_is_this_pserve_thing`, :ref:`environment_chapter`, :ref:`paste_chapter` diff --git a/docs/quick_tutorial/ini/development.ini b/docs/quick_tutorial/ini/development.ini index 8853e2c2b..cffbd66c9 100644 --- a/docs/quick_tutorial/ini/development.ini +++ b/docs/quick_tutorial/ini/development.ini @@ -3,5 +3,4 @@ use = egg:tutorial [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/quick_tutorial/jinja2/development.ini b/docs/quick_tutorial/jinja2/development.ini index 4d47e54a5..7066668bf 100644 --- a/docs/quick_tutorial/jinja2/development.ini +++ b/docs/quick_tutorial/jinja2/development.ini @@ -6,5 +6,4 @@ pyramid.includes = [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/quick_tutorial/json/development.ini b/docs/quick_tutorial/json/development.ini index 4d47e54a5..7066668bf 100644 --- a/docs/quick_tutorial/json/development.ini +++ b/docs/quick_tutorial/json/development.ini @@ -6,5 +6,4 @@ pyramid.includes = [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/quick_tutorial/logging.rst b/docs/quick_tutorial/logging.rst index cbbf7860e..0a530e91f 100644 --- a/docs/quick_tutorial/logging.rst +++ b/docs/quick_tutorial/logging.rst @@ -15,7 +15,7 @@ It's important to know what is going on inside our web application. In development we might need to collect some output. In production, we might need to detect problems when other people use the site. We need *logging*. -Fortunately Pyramid uses the normal Python approach to logging. The scaffold +Fortunately Pyramid uses the normal Python approach to logging. The project generated in your ``development.ini`` has a number of lines that configure the logging for you to some reasonable defaults. You then see messages sent by Pyramid, for example, when a new request comes in. diff --git a/docs/quick_tutorial/logging/development.ini b/docs/quick_tutorial/logging/development.ini index 62e0c5123..b869ca5b6 100644 --- a/docs/quick_tutorial/logging/development.ini +++ b/docs/quick_tutorial/logging/development.ini @@ -6,7 +6,6 @@ pyramid.includes = [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 # Begin logging configuration diff --git a/docs/quick_tutorial/more_view_classes/development.ini b/docs/quick_tutorial/more_view_classes/development.ini index 4d47e54a5..7066668bf 100644 --- a/docs/quick_tutorial/more_view_classes/development.ini +++ b/docs/quick_tutorial/more_view_classes/development.ini @@ -6,5 +6,4 @@ pyramid.includes = [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/quick_tutorial/request_response.rst b/docs/quick_tutorial/request_response.rst index 0ac9b4f6d..ece8cdd6f 100644 --- a/docs/quick_tutorial/request_response.rst +++ b/docs/quick_tutorial/request_response.rst @@ -26,7 +26,7 @@ part of a web application, web developers need a robust, mature set of software for web requests and returning web responses. Pyramid has always fit nicely into the existing world of Python web development -(virtual environments, packaging, scaffolding, first to embrace Python 3, and +(virtual environments, packaging, cookiecutters, first to embrace Python 3, and so on). Pyramid turned to the well-regarded :term:`WebOb` Python library for request and response handling. In our example above, Pyramid hands ``hello_world`` a ``request`` that is :ref:`based on WebOb <webob_chapter>`. diff --git a/docs/quick_tutorial/request_response/development.ini b/docs/quick_tutorial/request_response/development.ini index 4d47e54a5..7066668bf 100644 --- a/docs/quick_tutorial/request_response/development.ini +++ b/docs/quick_tutorial/request_response/development.ini @@ -6,5 +6,4 @@ pyramid.includes = [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst index afa8ed104..70e68514b 100644 --- a/docs/quick_tutorial/requirements.rst +++ b/docs/quick_tutorial/requirements.rst @@ -19,11 +19,11 @@ virtual environment.) This *Quick Tutorial* is based on: -* **Python 3.5**. Pyramid fully supports Python 3.4+ and Python 2.7+. This - tutorial uses **Python 3.5** but runs fine under Python 2.7. +* **Python 3.6**. Pyramid fully supports Python 3.4+ and Python 2.7+. This + tutorial uses **Python 3.6** but runs fine under Python 2.7. * **venv**. We believe in virtual environments. For this tutorial, we use - Python 3.5's built-in solution :term:`venv`. For Python 2.7, you can install + Python 3.6's built-in solution :term:`venv`. For Python 2.7, you can install :term:`virtualenv`. * **pip**. We use :term:`pip` for package management. @@ -156,7 +156,7 @@ environment variable. .. code-block:: doscon # Windows - c:\> c:\Python35\python -m venv %VENV% + c:\> python -m venv %VENV% .. seealso:: See also Python 3's :mod:`venv module <python:venv>` and Python 2's `virtualenv <https://virtualenv.pypa.io/en/latest/>`_ package. @@ -172,7 +172,7 @@ time of its release. .. code-block:: bash # Mac and Linux - $VENV/bin/pip install --upgrade pip setuptools + $ $VENV/bin/pip install --upgrade pip setuptools .. code-block:: doscon @@ -214,4 +214,4 @@ tutorial. .. code-block:: doscon # Windows - c:\> %VENV%\Scripts\pip install webtest deform sqlalchemy pyramid_chameleon pyramid_debugtoolbar pyramid_jinja2 waitress pyramid_tm zope.sqlalchemy + c:\> %VENV%\Scripts\pip install webtest pytest pytest-cov deform sqlalchemy pyramid_chameleon pyramid_debugtoolbar pyramid_jinja2 waitress pyramid_tm zope.sqlalchemy diff --git a/docs/quick_tutorial/retail_forms/development.ini b/docs/quick_tutorial/retail_forms/development.ini index 4d47e54a5..7066668bf 100644 --- a/docs/quick_tutorial/retail_forms/development.ini +++ b/docs/quick_tutorial/retail_forms/development.ini @@ -6,5 +6,4 @@ pyramid.includes = [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/quick_tutorial/routing/development.ini b/docs/quick_tutorial/routing/development.ini index 4d47e54a5..7066668bf 100644 --- a/docs/quick_tutorial/routing/development.ini +++ b/docs/quick_tutorial/routing/development.ini @@ -6,5 +6,4 @@ pyramid.includes = [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/quick_tutorial/scaffolds.rst b/docs/quick_tutorial/scaffolds.rst deleted file mode 100644 index ad002f4fd..000000000 --- a/docs/quick_tutorial/scaffolds.rst +++ /dev/null @@ -1,87 +0,0 @@ -.. _qtut_scaffolds: - -============================================= -Prelude: Quick Project Startup with Scaffolds -============================================= - -To ease the process of getting started, Pyramid provides *scaffolds* that -generate sample projects from templates in Pyramid and Pyramid add-ons. - - -Background -========== - -We're going to cover a lot in this tutorial, focusing on one topic at a time -and writing everything from scratch. As a warm up, though, it sure would be -nice to see some pixels on a screen. - -Like other web development frameworks, Pyramid provides a number of "scaffolds" -that generate working Python, template, and CSS code for sample applications. -In this step we'll use a built-in scaffold to let us preview a Pyramid -application, before starting from scratch on Step 1. - - -Objectives -========== - -- Use Pyramid's ``pcreate`` command to list scaffolds and make a new project. - -- Start up a Pyramid application and visit it in a web browser. - - -Steps -===== - -#. Pyramid's ``pcreate`` command can list the available scaffolds: - - .. code-block:: bash - - $ $VENV/bin/pcreate --list - Available scaffolds: - alchemy: Pyramid project using SQLAlchemy, SQLite, URL dispatch, and Jinja2 - starter: Pyramid starter project using URL dispatch and Chameleon - zodb: Pyramid project using ZODB, traversal, and Chameleon - -#. Tell ``pcreate`` to use the ``starter`` scaffold to make our project: - - .. code-block:: bash - - $ $VENV/bin/pcreate --scaffold starter scaffolds - -#. Install our project in editable mode for development in the current - directory: - - .. code-block:: bash - - $ cd scaffolds - $ $VENV/bin/pip install -e . - -#. Start up the application by pointing Pyramid's ``pserve`` command at the - project's (generated) configuration file: - - .. code-block:: bash - - $ $VENV/bin/pserve development.ini --reload - - On start up, ``pserve`` logs some output: - - .. code-block:: bash - - Starting subprocess with file monitor - Starting server in PID 72213. - Starting HTTP server on http://0.0.0.0:6543 - -#. Open http://localhost:6543/ in your browser. - -Analysis -======== - -Rather than starting from scratch, ``pcreate`` can make getting a Python -project containing a Pyramid application a quick matter. Pyramid ships with a -few scaffolds. But installing a Pyramid add-on can give you new scaffolds from -that add-on. - -``pserve`` is Pyramid's application runner, separating operational details from -your code. When you install Pyramid, a small command program called ``pserve`` -is written to your ``bin`` directory. This program is an executable Python -module. It is passed a configuration file (in this case, ``development.ini``). diff --git a/docs/quick_tutorial/scaffolds/CHANGES.txt b/docs/quick_tutorial/scaffolds/CHANGES.txt deleted file mode 100644 index 35a34f332..000000000 --- a/docs/quick_tutorial/scaffolds/CHANGES.txt +++ /dev/null @@ -1,4 +0,0 @@ -0.0 ---- - -- Initial version diff --git a/docs/quick_tutorial/scaffolds/MANIFEST.in b/docs/quick_tutorial/scaffolds/MANIFEST.in deleted file mode 100644 index 91d3f763b..000000000 --- a/docs/quick_tutorial/scaffolds/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -include *.txt *.ini *.cfg *.rst -recursive-include scaffolds *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml diff --git a/docs/quick_tutorial/scaffolds/README.txt b/docs/quick_tutorial/scaffolds/README.txt deleted file mode 100644 index 7776dd2d5..000000000 --- a/docs/quick_tutorial/scaffolds/README.txt +++ /dev/null @@ -1 +0,0 @@ -scaffolds README diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/favicon.ico b/docs/quick_tutorial/scaffolds/scaffolds/static/favicon.ico Binary files differdeleted file mode 100644 index 71f837c9e..000000000 --- a/docs/quick_tutorial/scaffolds/scaffolds/static/favicon.ico +++ /dev/null diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/footerbg.png b/docs/quick_tutorial/scaffolds/scaffolds/static/footerbg.png Binary files differdeleted file mode 100644 index 1fbc873da..000000000 --- a/docs/quick_tutorial/scaffolds/scaffolds/static/footerbg.png +++ /dev/null diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/headerbg.png b/docs/quick_tutorial/scaffolds/scaffolds/static/headerbg.png Binary files differdeleted file mode 100644 index 0596f2020..000000000 --- a/docs/quick_tutorial/scaffolds/scaffolds/static/headerbg.png +++ /dev/null diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/ie6.css b/docs/quick_tutorial/scaffolds/scaffolds/static/ie6.css deleted file mode 100644 index b7c8493d8..000000000 --- a/docs/quick_tutorial/scaffolds/scaffolds/static/ie6.css +++ /dev/null @@ -1,8 +0,0 @@ -* html img, -* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", -this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", -this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), -this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", -this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) -);} -#wrap{display:table;height:100%} diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/middlebg.png b/docs/quick_tutorial/scaffolds/scaffolds/static/middlebg.png Binary files differdeleted file mode 100644 index 2369cfb7d..000000000 --- a/docs/quick_tutorial/scaffolds/scaffolds/static/middlebg.png +++ /dev/null diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/pylons.css b/docs/quick_tutorial/scaffolds/scaffolds/static/pylons.css deleted file mode 100644 index 4b1c017cd..000000000 --- a/docs/quick_tutorial/scaffolds/scaffolds/static/pylons.css +++ /dev/null @@ -1,372 +0,0 @@ -html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td -{ - margin: 0; - padding: 0; - border: 0; - outline: 0; - font-size: 100%; /* 16px */ - vertical-align: baseline; - background: transparent; -} - -body -{ - line-height: 1; -} - -ol, ul -{ - list-style: none; -} - -blockquote, q -{ - quotes: none; -} - -blockquote:before, blockquote:after, q:before, q:after -{ - content: ''; - content: none; -} - -:focus -{ - outline: 0; -} - -ins -{ - text-decoration: none; -} - -del -{ - text-decoration: line-through; -} - -table -{ - border-collapse: collapse; - border-spacing: 0; -} - -sub -{ - vertical-align: sub; - font-size: smaller; - line-height: normal; -} - -sup -{ - vertical-align: super; - font-size: smaller; - line-height: normal; -} - -ul, menu, dir -{ - display: block; - list-style-type: disc; - margin: 1em 0; - padding-left: 40px; -} - -ol -{ - display: block; - list-style-type: decimal-leading-zero; - margin: 1em 0; - padding-left: 40px; -} - -li -{ - display: list-item; -} - -ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl -{ - margin-top: 0; - margin-bottom: 0; -} - -ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir -{ - list-style-type: circle; -} - -ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir -{ - list-style-type: square; -} - -.hidden -{ - display: none; -} - -p -{ - line-height: 1.5em; -} - -h1 -{ - font-size: 1.75em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h2 -{ - font-size: 1.5em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h3 -{ - font-size: 1.25em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h4 -{ - font-size: 1em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -html, body -{ - width: 100%; - height: 100%; -} - -body -{ - margin: 0; - padding: 0; - background-color: #fff; - position: relative; - font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif; -} - -a -{ - color: #1b61d6; - text-decoration: none; -} - -a:hover -{ - color: #e88f00; - text-decoration: underline; -} - -body h1, body h2, body h3, body h4, body h5, body h6 -{ - font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif; - font-weight: 400; - color: #373839; - font-style: normal; -} - -#wrap -{ - min-height: 100%; -} - -#header, #footer -{ - width: 100%; - color: #fff; - height: 40px; - position: absolute; - text-align: center; - line-height: 40px; - overflow: hidden; - font-size: 12px; - vertical-align: middle; -} - -#header -{ - background: #000; - top: 0; - font-size: 14px; -} - -#footer -{ - bottom: 0; - background: #000 url(footerbg.png) repeat-x 0 top; - position: relative; - margin-top: -40px; - clear: both; -} - -.header, .footer -{ - width: 750px; - margin-right: auto; - margin-left: auto; -} - -.wrapper -{ - width: 100%; -} - -#top, #top-small, #bottom -{ - width: 100%; -} - -#top -{ - color: #000; - height: 230px; - background: #fff url(headerbg.png) repeat-x 0 top; - position: relative; -} - -#top-small -{ - color: #000; - height: 60px; - background: #fff url(headerbg.png) repeat-x 0 top; - position: relative; -} - -#bottom -{ - color: #222; - background-color: #fff; -} - -.top, .top-small, .middle, .bottom -{ - width: 750px; - margin-right: auto; - margin-left: auto; -} - -.top -{ - padding-top: 40px; -} - -.top-small -{ - padding-top: 10px; -} - -#middle -{ - width: 100%; - height: 100px; - background: url(middlebg.png) repeat-x; - border-top: 2px solid #fff; - border-bottom: 2px solid #b2b2b2; -} - -.app-welcome -{ - margin-top: 25px; -} - -.app-name -{ - color: #000; - font-weight: 700; -} - -.bottom -{ - padding-top: 50px; -} - -#left -{ - width: 350px; - float: left; - padding-right: 25px; -} - -#right -{ - width: 350px; - float: right; - padding-left: 25px; -} - -.align-left -{ - text-align: left; -} - -.align-right -{ - text-align: right; -} - -.align-center -{ - text-align: center; -} - -ul.links -{ - margin: 0; - padding: 0; -} - -ul.links li -{ - list-style-type: none; - font-size: 14px; -} - -form -{ - border-style: none; -} - -fieldset -{ - border-style: none; -} - -input -{ - color: #222; - border: 1px solid #ccc; - font-family: sans-serif; - font-size: 12px; - line-height: 16px; -} - -input[type=text], input[type=password] -{ - width: 205px; -} - -input[type=submit] -{ - background-color: #ddd; - font-weight: 700; -} - -/*Opera Fix*/ -body:before -{ - content: ""; - height: 100%; - float: left; - width: 0; - margin-top: -32767px; -} diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid-small.png b/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid-small.png Binary files differdeleted file mode 100644 index a5bc0ade7..000000000 --- a/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid-small.png +++ /dev/null diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid.png b/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid.png Binary files differdeleted file mode 100644 index 347e05549..000000000 --- a/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid.png +++ /dev/null diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/transparent.gif b/docs/quick_tutorial/scaffolds/scaffolds/static/transparent.gif Binary files differdeleted file mode 100644 index 0341802e5..000000000 --- a/docs/quick_tutorial/scaffolds/scaffolds/static/transparent.gif +++ /dev/null diff --git a/docs/quick_tutorial/scaffolds/scaffolds/templates/mytemplate.pt b/docs/quick_tutorial/scaffolds/scaffolds/templates/mytemplate.pt deleted file mode 100644 index b43a174e3..000000000 --- a/docs/quick_tutorial/scaffolds/scaffolds/templates/mytemplate.pt +++ /dev/null @@ -1,73 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"> -<head> - <title>The Pyramid Web Framework</title> - <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> - <meta name="keywords" content="python web application" /> - <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.static_url('scaffolds:static/favicon.ico')}" /> - <link rel="stylesheet" href="${request.static_url('scaffolds:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" /> - <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" /> - <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" /> - <!--[if lte IE 6]> - <link rel="stylesheet" href="${request.static_url('scaffolds:static/ie6.css')}" type="text/css" media="screen" charset="utf-8" /> - <![endif]--> -</head> -<body> - <div id="wrap"> - <div id="top"> - <div class="top align-center"> - <div><img src="${request.static_url('scaffolds:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> - </div> - </div> - <div id="middle"> - <div class="middle align-center"> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid Web Framework. - </p> - </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <h2>Search documentation</h2> - <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/search.html"> - <input type="text" id="q" name="q" value="" /> - <input type="submit" id="x" value="Go" /> - </form> - </div> - <div id="right" class="align-left"> - <h2>Pyramid links</h2> - <ul class="links"> - <li> - <a href="http://pylonsproject.org">Pylons Website</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#narrative-documentation">Narrative Documentation</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#reference-material">API Documentation</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#tutorials">Tutorials</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#detailed-change-history">Change History</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#sample-applications">Sample Applications</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#support-and-development">Support and Development</a> - </li> - <li> - <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> - </li> - </ul> - </div> - </div> - </div> - </div> -</body> -</html> diff --git a/docs/quick_tutorial/scaffolds/scaffolds/tests.py b/docs/quick_tutorial/scaffolds/scaffolds/tests.py deleted file mode 100644 index 4f906ffa9..000000000 --- a/docs/quick_tutorial/scaffolds/scaffolds/tests.py +++ /dev/null @@ -1,17 +0,0 @@ -import unittest - -from pyramid import testing - - -class ViewTests(unittest.TestCase): - def setUp(self): - self.config = testing.setUp() - - def tearDown(self): - testing.tearDown() - - def test_my_view(self): - from .views import my_view - request = testing.DummyRequest() - info = my_view(request) - self.assertEqual(info['project'], 'scaffolds') diff --git a/docs/quick_tutorial/scaffolds/scaffolds/views.py b/docs/quick_tutorial/scaffolds/scaffolds/views.py deleted file mode 100644 index db90d8364..000000000 --- a/docs/quick_tutorial/scaffolds/scaffolds/views.py +++ /dev/null @@ -1,6 +0,0 @@ -from pyramid.view import view_config - - -@view_config(route_name='home', renderer='templates/mytemplate.pt') -def my_view(request): - return {'project': 'scaffolds'} diff --git a/docs/quick_tutorial/scaffolds/setup.py b/docs/quick_tutorial/scaffolds/setup.py deleted file mode 100644 index ec95946a5..000000000 --- a/docs/quick_tutorial/scaffolds/setup.py +++ /dev/null @@ -1,42 +0,0 @@ -import os - -from setuptools import setup, find_packages - -here = os.path.abspath(os.path.dirname(__file__)) -with open(os.path.join(here, 'README.txt')) as f: - README = f.read() -with open(os.path.join(here, 'CHANGES.txt')) as f: - CHANGES = f.read() - -requires = [ - 'pyramid', - 'pyramid_chameleon', - 'pyramid_debugtoolbar', - 'waitress', - ] - -setup(name='scaffolds', - version='0.0', - description='scaffolds', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web pyramid pylons', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - install_requires=requires, - tests_require=requires, - test_suite="scaffolds", - entry_points="""\ - [paste.app_factory] - main = scaffolds:main - """, - ) diff --git a/docs/quick_tutorial/sessions/development.ini b/docs/quick_tutorial/sessions/development.ini index 4d47e54a5..7066668bf 100644 --- a/docs/quick_tutorial/sessions/development.ini +++ b/docs/quick_tutorial/sessions/development.ini @@ -6,5 +6,4 @@ pyramid.includes = [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/quick_tutorial/static_assets/development.ini b/docs/quick_tutorial/static_assets/development.ini index 4d47e54a5..7066668bf 100644 --- a/docs/quick_tutorial/static_assets/development.ini +++ b/docs/quick_tutorial/static_assets/development.ini @@ -6,5 +6,4 @@ pyramid.includes = [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/quick_tutorial/templating/development.ini b/docs/quick_tutorial/templating/development.ini index 4d47e54a5..7066668bf 100644 --- a/docs/quick_tutorial/templating/development.ini +++ b/docs/quick_tutorial/templating/development.ini @@ -6,5 +6,4 @@ pyramid.includes = [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/quick_tutorial/tutorial_approach.rst b/docs/quick_tutorial/tutorial_approach.rst index 49a6bfd85..8da9f71b3 100644 --- a/docs/quick_tutorial/tutorial_approach.rst +++ b/docs/quick_tutorial/tutorial_approach.rst @@ -7,25 +7,16 @@ Details, references, and deeper discussions are mentioned in "See also" notes. .. seealso:: This is an example "See also" note. -This "Getting Started" tutorial is broken into independent steps, starting with -the smallest possible "single file WSGI app" example. Each of these steps -introduce a topic and a very small set of concepts via working code. The steps -each correspond to a directory in this repo, where each step/topic/directory is -a Python package. - -To successfully run each step: - -.. code-block:: bash - - $ cd request_response - $ $VENV/bin/pip install -e . - -...and repeat for each step you would like to work on. In most cases we will -start with the results of an earlier step. Directory tree ============== +This "Getting Started" tutorial is broken into independent steps, starting with +the smallest possible "single file WSGI app" example. Each of these steps +introduces a topic and a very small set of concepts via working code. The steps +each correspond to a directory in our workspace, where each step's directory is +a Python package. + As we develop our tutorial, our directory tree will resemble the structure below: @@ -41,7 +32,15 @@ below: │── development.ini `── setup.py -Each of the first-level directories (e.g., ``request_response``) is a *Python +Each of the directories in our ``quick_tutorial`` workspace (e.g., ``request_response``) is a *Python project* (except as noted for the ``hello_world`` step). The ``tutorial`` -directory is a *Python package*. At the end of each step, we copy a previous -directory into a new directory to use as a starting point. +directory is a *Python package*. + +For most steps you will copy the previous step's directory to a new directory, and change your working directory to the new directory, then install your project: + +.. code-block:: bash + + $ cd ..; cp -r package ini; cd ini + $ $VENV/bin/pip install -e . + +For a few steps, you won't copy the previous step's directory, but you will still need to install your project with ``$VENV/bin/pip install -e .``. diff --git a/docs/quick_tutorial/unit_testing.rst b/docs/quick_tutorial/unit_testing.rst index 7c85d5289..002c62fde 100644 --- a/docs/quick_tutorial/unit_testing.rst +++ b/docs/quick_tutorial/unit_testing.rst @@ -29,7 +29,7 @@ broken the code. As you're writing your code, you might find this more convenient than changing to your browser constantly and clicking reload. We'll also leave discussion of `pytest-cov -<http://pytest-cov.readthedocs.org/en/latest/>`_ for another section. +<http://pytest-cov.readthedocs.io/en/latest/>`_ for another section. Objectives diff --git a/docs/quick_tutorial/unit_testing/development.ini b/docs/quick_tutorial/unit_testing/development.ini index 52b2a3a41..17b479011 100644 --- a/docs/quick_tutorial/unit_testing/development.ini +++ b/docs/quick_tutorial/unit_testing/development.ini @@ -5,5 +5,4 @@ pyramid.includes = [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/quick_tutorial/view_classes/development.ini b/docs/quick_tutorial/view_classes/development.ini index 4d47e54a5..7066668bf 100644 --- a/docs/quick_tutorial/view_classes/development.ini +++ b/docs/quick_tutorial/view_classes/development.ini @@ -6,5 +6,4 @@ pyramid.includes = [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/quick_tutorial/views/development.ini b/docs/quick_tutorial/views/development.ini index 52b2a3a41..17b479011 100644 --- a/docs/quick_tutorial/views/development.ini +++ b/docs/quick_tutorial/views/development.ini @@ -5,5 +5,4 @@ pyramid.includes = [server:main] use = egg:pyramid#wsgiref -host = 0.0.0.0 port = 6543 diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst index c66786b11..170f2ebc8 100644 --- a/docs/tutorials/modwsgi/index.rst +++ b/docs/tutorials/modwsgi/index.rst @@ -32,60 +32,71 @@ specific path information for commands and files. <https://code.google.com/archive/p/modwsgi/wikis/InstallationInstructions.wiki>`_ for your platform into your system's Apache installation. -#. Create a :term:`virtual environment` which we'll use to install our - application. +#. Create a :app:`Pyramid` application. For this tutorial we'll use the + ``starter`` :term:`cookiecutter`. See :ref:`project_narr` for more + in-depth information about creating a new project. - .. code-block:: text + .. code-block:: bash $ cd ~ - $ mkdir modwsgi - $ cd modwsgi - $ python3 -m venv env + $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter -#. Install :app:`Pyramid` into the newly created virtual environment: + If prompted for the first item, accept the default ``yes`` by hitting return. - .. parsed-literal:: + .. code-block:: text - $ cd ~/modwsgi/env - $ $VENV/bin/pip install "pyramid==\ |release|\ " + You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. + Is it okay to delete and re-clone it? [yes]: yes + project_name [Pyramid Scaffold]: myproject + repo_name [myproject]: myproject + Select template_language: + 1 - jinja2 + 2 - chameleon + 3 - mako + Choose from 1, 2, 3 [1]: 1 -#. Create and install your :app:`Pyramid` application. For the purposes of - this tutorial, we'll just be using the ``pyramid_starter`` application as - a baseline application. Substitute your existing :app:`Pyramid` - application as necessary if you already have one. +#. Create a :term:`virtual environment` which we'll use to install our + application. It is important to use the same base Python interpreter + that was used to build ``mod_wsgi``. For example, if ``mod_wsgi`` was + built against the system Python 3.x, then your project should use a + virtual environment created from that same system Python 3.x. - .. code-block:: text + .. code-block:: bash - $ cd ~/modwsgi/env - $ $VENV/bin/pcreate -s starter myapp - $ cd myapp - $ $VENV/bin/pip install -e . + $ cd myproject + $ python3 -m venv env + +#. Install your :app:`Pyramid` application and its dependencies. + + .. code-block:: bash -#. Within the virtual environment directory (``~/modwsgi/env``), create a - script named ``pyramid.wsgi``. Give it these contents: + $ env/bin/pip install -e . + +#. Within the project directory (``~/myproject``), create a script + named ``pyramid.wsgi``. Give it these contents: .. code-block:: python from pyramid.paster import get_app, setup_logging - ini_path = '/Users/chrism/modwsgi/env/myapp/production.ini' + ini_path = '/Users/chrism/myproject/production.ini' setup_logging(ini_path) application = get_app(ini_path, 'main') - The first argument to ``get_app`` is the project configuration file - name. It's best to use the ``production.ini`` file provided by your - scaffold, as it contains settings appropriate for - production. The second is the name of the section within the .ini file - that should be loaded by ``mod_wsgi``. The assignment to the name + The first argument to :func:`pyramid.paster.get_app` is the project + configuration file name. It's best to use the ``production.ini`` file + provided by your cookiecutter, as it contains settings appropriate for + production. The second is the name of the section within the ``.ini`` + file that should be loaded by ``mod_wsgi``. The assignment to the name ``application`` is important: mod_wsgi requires finding such an assignment when it opens the file. - The call to ``setup_logging`` initializes the standard library's - `logging` module to allow logging within your application. + The call to :func:`pyramid.paster.setup_logging` initializes the standard + library's `logging` module to allow logging within your application. See :ref:`logging_config`. There is no need to make the ``pyramid.wsgi`` script executable. However, you'll need to make sure that *two* users have access to change - into the ``~/modwsgi/env`` directory: your current user (mine is + into the ``~/myproject`` directory: your current user (mine is ``chrism`` and the user that Apache will run as often named ``apache`` or ``httpd``). Make sure both of these users can "cd" into that directory. @@ -101,18 +112,17 @@ specific path information for commands and files. WSGIApplicationGroup %{GLOBAL} WSGIPassAuthorization On WSGIDaemonProcess pyramid user=chrism group=staff threads=4 \ - python-path=/Users/chrism/modwsgi/env/lib/python2.7/site-packages - WSGIScriptAlias /myapp /Users/chrism/modwsgi/env/pyramid.wsgi + python-path=/Users/chrism/myproject/env/lib/python3.5/site-packages + WSGIScriptAlias /myapp /Users/chrism/myproject/pyramid.wsgi - <Directory /Users/chrism/modwsgi/env> + <Directory /Users/chrism/myproject> WSGIProcessGroup pyramid - Order allow,deny - Allow from all + Require all granted </Directory> #. Restart Apache - .. code-block:: text + .. code-block:: bash $ sudo /usr/sbin/apachectl restart @@ -122,5 +132,5 @@ specific path information for commands and files. :term:`mod_wsgi` has many knobs and a great variety of deployment modes. This is just one representation of how you might use it to serve up a :app:`Pyramid` application. See the `mod_wsgi configuration documentation -<https://code.google.com/archive/p/modwsgi/wikis/ConfigurationGuidelines.wiki>`_ +<https://modwsgi.readthedocs.io/en/develop/configuration.html>`_ for more in-depth configuration information. diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index 44097b35b..0ba734f83 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -18,6 +18,7 @@ require permission, instead of a default "403 Forbidden" page. We will implement the access control with the following steps: +* Add password hashing dependencies. * Add users and groups (``security.py``, a new module). * Add an :term:`ACL` (``models.py``). * Add an :term:`authentication policy` and an :term:`authorization policy` @@ -25,7 +26,7 @@ We will implement the access control with the following steps: * Add :term:`permission` declarations to the ``edit_page`` and ``add_page`` views (``views.py``). -Then we will add the login and logout feature: +Then we will add the login and logout features: * Add ``login`` and ``logout`` views (``views.py``). * Add a login template (``login.pt``). @@ -38,11 +39,32 @@ Then we will add the login and logout feature: Access control -------------- + +Add dependencies +~~~~~~~~~~~~~~~~ + +Just like in :ref:`wiki_defining_views`, we need a new dependency. We need to add the `bcrypt <https://pypi.python.org/pypi/bcrypt>`_ package, to our tutorial package's ``setup.py`` file by assigning this dependency to the ``requires`` parameter in the ``setup()`` function. + +Open ``setup.py`` and edit it to look like the following: + +.. literalinclude:: src/authorization/setup.py + :linenos: + :emphasize-lines: 21 + :language: python + +Only the highlighted line needs to be added. + +Do not forget to run ``pip install -e .`` just like in :ref:`wiki-running-pip-install`. + +.. note:: + + We are using the ``bcrypt`` package from PyPI to hash our passwords securely. There are other one-way hash algorithms for passwords if bcrypt is an issue on your system. Just make sure that it's an algorithm approved for storing passwords versus a generic one-way hash. + + Add users and groups ~~~~~~~~~~~~~~~~~~~~ -Create a new ``tutorial/security.py`` module with the -following content: +Create a new ``tutorial/security.py`` module with the following content: .. literalinclude:: src/authorization/tutorial/security.py :linenos: @@ -51,7 +73,7 @@ following content: The ``groupfinder`` function accepts a userid and a request and returns one of these values: -- If the userid exists in the system, it will return a sequence of group +- If ``userid`` exists in the system, it will return a sequence of group identifiers (or an empty sequence if the user isn't a member of any groups). - If the userid *does not* exist in the system, it will return ``None``. @@ -61,26 +83,38 @@ request)`` returns ``None``. We will use ``groupfinder()`` as an :term:`authentication policy` "callback" that will provide the :term:`principal` or principals for a user. -In a production system, user and group data will most often come from a +There are two helper methods that will help us later to authenticate users. +The first is ``hash_password`` which takes a raw password and transforms it using +bcrypt into an irreversible representation, a process known as "hashing". The +second method, ``check_password``, will allow us to compare the hashed value of the +submitted password against the hashed value of the password stored in the user's +record. If the two hashed values match, then the submitted +password is valid, and we can authenticate the user. + +We hash passwords so that it is impossible to decrypt and use them to +authenticate in the application. If we stored passwords foolishly in clear text, +then anyone with access to the database could retrieve any password to authenticate +as any user. + +In a production system, user and group data will most often be saved and come from a database, but here we use "dummy" data to represent user and groups sources. Add an ACL ~~~~~~~~~~ Open ``tutorial/models.py`` and add the following import -statement at the head: +statement near the top: .. literalinclude:: src/authorization/tutorial/models.py - :lines: 4-7 - :linenos: + :lines: 4-8 + :lineno-match: :language: python Add the following lines to the ``Wiki`` class: .. literalinclude:: src/authorization/tutorial/models.py :lines: 9-13 - :linenos: - :lineno-start: 9 + :lineno-match: :emphasize-lines: 4-5 :language: python @@ -89,10 +123,10 @@ permission is allowed, and :data:`~pyramid.security.Everyone`, a special :term:`principal` that is associated to all requests. Both are used in the :term:`ACE` entries that make up the ACL. -The ACL is a list that needs to be named `__acl__` and be an attribute of a +The ACL is a list that needs to be named ``__acl__`` and be an attribute of a class. We define an :term:`ACL` with two :term:`ACE` entries: the first entry -allows any user the `view` permission. The second entry allows the -``group:editors`` principal the `edit` permission. +allows any user the ``view`` permission. The second entry allows the +``group:editors`` principal the ``edit`` permission. The ``Wiki`` class that contains the ACL is the :term:`resource` constructor for the :term:`root` resource, which is a ``Wiki`` instance. The ACL is @@ -115,15 +149,14 @@ statements: .. literalinclude:: src/authorization/tutorial/__init__.py :lines: 1-8 :linenos: - :emphasize-lines: 4-5,8 + :emphasize-lines: 3-6,8 :language: python Now add those policies to the configuration: .. literalinclude:: src/authorization/tutorial/__init__.py :lines: 18-23 - :linenos: - :lineno-start: 18 + :lineno-match: :emphasize-lines: 1-3,5-6 :language: python @@ -146,12 +179,12 @@ Open ``tutorial/views.py`` and add a ``permission='edit'`` parameter to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``: .. literalinclude:: src/authorization/tutorial/views.py - :lines: 50-52 + :lines: 49-51 :emphasize-lines: 2-3 :language: python .. literalinclude:: src/authorization/tutorial/views.py - :lines: 70-72 + :lines: 68-70 :emphasize-lines: 2-3 :language: python @@ -213,9 +246,8 @@ cookie. Now add the ``login`` and ``logout`` views at the end of the file: .. literalinclude:: src/authorization/tutorial/views.py - :lines: 82-116 - :linenos: - :lineno-start: 82 + :lines: 80- + :lineno-match: :language: python ``login()`` has two decorators: @@ -252,17 +284,17 @@ the return value of ``view_page()``, ``add_page()``, and ``edit_page()`` as follows: .. literalinclude:: src/authorization/tutorial/views.py - :lines: 47-48 + :lines: 46-47 :emphasize-lines: 1-2 :language: python .. literalinclude:: src/authorization/tutorial/views.py - :lines: 67-68 + :lines: 65-66 :emphasize-lines: 1-2 :language: python .. literalinclude:: src/authorization/tutorial/views.py - :lines: 78-80 + :lines: 76-78 :emphasize-lines: 2-3 :language: python @@ -279,7 +311,7 @@ Open ``tutorial/templates/edit.pt`` and indicated by the highlighted lines. .. literalinclude:: src/authorization/tutorial/templates/edit.pt - :lines: 34-38 + :lines: 35-39 :emphasize-lines: 3-5 :language: html @@ -313,7 +345,7 @@ Our ``tutorial/views.py`` will look like this when we're done: .. literalinclude:: src/authorization/tutorial/views.py :linenos: - :emphasize-lines: 8,11-15,17,24,29,48,52,68,72,80,82-120 + :emphasize-lines: 8,11-15,17,24,29,47,51,66,70,78,80- :language: python Only the highlighted lines need to be added or edited. @@ -323,7 +355,7 @@ we're done: .. literalinclude:: src/authorization/tutorial/templates/edit.pt :linenos: - :emphasize-lines: 36-38 + :emphasize-lines: 37-39 :language: html Only the highlighted lines need to be added or edited. @@ -333,7 +365,7 @@ we're done: .. literalinclude:: src/authorization/tutorial/templates/view.pt :linenos: - :emphasize-lines: 36-38 + :emphasize-lines: 37-39 :language: html Only the highlighted lines need to be added or edited. diff --git a/docs/tutorials/wiki/background.rst b/docs/tutorials/wiki/background.rst index 31dcd6b53..c583b375c 100644 --- a/docs/tutorials/wiki/background.rst +++ b/docs/tutorials/wiki/background.rst @@ -13,7 +13,7 @@ Python web framework experience. To code along with this tutorial, the developer will need a UNIX machine with development tools (Mac OS X with XCode, any Linux or BSD -variant, etc.) *or* a Windows system of any kind. +variant, and so on) *or* a Windows system of any kind. .. warning:: diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index 20bfdf754..d00eab956 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -4,7 +4,7 @@ Basic Layout ============ -The starter files generated by the ``zodb`` scaffold are very basic, but +The starter files generated by the ``zodb`` cookiecutter are very basic, but they provide a good orientation for the high-level patterns common to most :term:`traversal`-based (and :term:`ZODB`-based) :app:`Pyramid` projects. @@ -44,7 +44,11 @@ Open ``tutorial/__init__.py``. It should already contain the following: #. *Line 15*. Include support for the :term:`Chameleon` template rendering bindings, allowing us to use the ``.pt`` templates. -#. *Line 16*. Register a "static view", which answers requests whose URL +#. *Line 16*. Include support for ``pyramid_tm``, allowing Pyramid requests to join the active transaction as provided by the `transaction <https://pypi.python.org/pypi/transaction>`_ package. + +#. *Line 17*. Include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application. + +#. *Line 18*. Register a "static view", which answers requests whose URL paths start with ``/static``, using the :meth:`pyramid.config.Configurator.add_static_view` method. This statement registers a view that will serve up static assets, such as CSS @@ -54,19 +58,19 @@ Open ``tutorial/__init__.py``. It should already contain the following: will be ``/static``. The second argument of this tag is the "path", which is a relative :term:`asset specification`, so it finds the resources it should serve within the ``static`` directory inside the ``tutorial`` - package. Alternatively the scaffold could have used an *absolute* asset + package. Alternatively the cookiecutter could have used an *absolute* asset specification as the path (``tutorial:static``). -#. *Line 17*. Perform a :term:`scan`. A scan will find :term:`configuration +#. *Line 19*. Perform a :term:`scan`. A scan will find :term:`configuration decoration`, such as view configuration decorators (e.g., ``@view_config``) in the source code of the ``tutorial`` package and will take actions based on these decorators. We don't pass any arguments to :meth:`~pyramid.config.Configurator.scan`, which implies that the scan should take place in the current package (in this case, ``tutorial``). - The scaffold could have equivalently said ``config.scan('tutorial')``, but + The cookiecutter could have equivalently said ``config.scan('tutorial')``, but it chose to omit the package name argument. -#. *Line 18*. Use the +#. *Line 20*. Use the :meth:`pyramid.config.Configurator.make_wsgi_app` method to return a :term:`WSGI` application. @@ -79,7 +83,7 @@ hierarchically in a :term:`resource tree`. This tree is consulted by tree represents the site structure, but it *also* represents the :term:`domain model` of the application, because each resource is a node stored persistently in a :term:`ZODB` database. The ``models.py`` file is -where the ``zodb`` scaffold put the classes that implement our +where the ``zodb`` cookiecutter put the classes that implement our resource objects, each of which also happens to be a domain model object. Here is the source for ``models.py``: @@ -93,7 +97,7 @@ Here is the source for ``models.py``: because the class inherits from the :class:`persistent.mapping.PersistentMapping` class. The ``__parent__`` and ``__name__`` are important parts of the :term:`traversal` protocol. - By default, have these as ``None`` indicating that this is the + By default, set these to ``None`` to indicate that this is the :term:`root` object. #. *Lines 8-14*. ``appmaker`` is used to return the *application @@ -110,7 +114,7 @@ Here is the source for ``models.py``: Views With ``views.py`` ----------------------- -Our scaffold generated a default ``views.py`` on our behalf. It +Our cookiecutter generated a default ``views.py`` on our behalf. It contains a single view, which is used to render the page shown when you visit the URL ``http://localhost:6543/``. @@ -156,7 +160,7 @@ Let's try to understand the components in this module: #. *Lines 6-7*. We define a :term:`view callable` named ``my_view``, which we decorated in the step above. This view callable is a *function* we - write generated by the ``zodb`` scaffold that is given a + write generated by the ``zodb`` cookiecutter that is given a ``request`` and which returns a dictionary. The ``mytemplate.pt`` :term:`renderer` named by the asset specification in the step above will convert this dictionary to a :term:`response` on our behalf. @@ -168,8 +172,8 @@ Let's try to understand the components in this module: Configuration in ``development.ini`` ------------------------------------ -The ``development.ini`` (in the tutorial :term:`project` directory, as -opposed to the tutorial :term:`package` directory) looks like this: +The ``development.ini`` (in the ``tutorial`` :term:`project` directory, as +opposed to the ``tutorial`` :term:`package` directory) looks like this: .. literalinclude:: src/basiclayout/development.ini :language: ini diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst index 73dce14d5..419fede62 100644 --- a/docs/tutorials/wiki/definingmodels.rst +++ b/docs/tutorials/wiki/definingmodels.rst @@ -4,7 +4,7 @@ Defining the Domain Model ========================= -The first change we'll make to our stock ``pcreate``-generated application will +The first change we'll make to our stock cookiecutter-generated application will be to define two :term:`resource` constructors, one representing a wiki page, and another representing the wiki as a mapping of wiki page names to page objects. We'll do this inside our ``models.py`` file. @@ -50,7 +50,9 @@ The first thing we want to do is remove the ``MyModel`` class from the generated ``models.py`` file. The ``MyModel`` class is only a sample and we're not going to use it. -Then, we'll add a ``Wiki`` class. We want it to inherit from the +Then we'll add an import at the top for the :class:`persistent.Persistent` class. We'll use this for a new ``Page`` class in a moment. + +Then we'll add a ``Wiki`` class. We want it to inherit from the :class:`persistent.mapping.PersistentMapping` class because it provides mapping behavior, and it makes sure that our Wiki page is stored as a "first-class" persistent object in our ZODB database. diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index ac94d8059..442d5ed18 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -4,7 +4,7 @@ Defining Views ============== -A :term:`view callable` in a :term:`traversal` -based :app:`Pyramid` +A :term:`view callable` in a :term:`traversal`-based :app:`Pyramid` application is typically a simple Python function that accepts two parameters: :term:`context` and :term:`request`. A view callable is assumed to return a :term:`response` object. @@ -16,7 +16,7 @@ assumed to return a :term:`response` object. this one-argument pattern used in other :app:`Pyramid` tutorials and applications. Either calling convention will work in any :app:`Pyramid` application; the calling conventions can be used - interchangeably as necessary. In :term:`traversal` based applications, + interchangeably as necessary. In :term:`traversal`-based applications, URLs are mapped to a context :term:`resource`, and since our :term:`resource tree` also represents our application's "domain model", we're often interested in the context because @@ -36,7 +36,7 @@ Declaring Dependencies in Our ``setup.py`` File The view code in our application will depend on a package which is not a dependency of the original "tutorial" application. The original "tutorial" -application was generated by the ``pcreate`` command; it doesn't know +application was generated by the cookiecutter; it doesn't know about our custom application requirements. We need to add a dependency on the ``docutils`` package to our ``tutorial`` @@ -52,6 +52,7 @@ Open ``setup.py`` and edit it to look like the following: Only the highlighted line needs to be added. +.. _wiki-running-pip-install: Running ``pip install -e .`` ============================ @@ -74,15 +75,15 @@ On Windows: .. code-block:: doscon - c:\pyramidtut> cd tutorial - c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e . + c:\> cd tutorial + c:\tutorial> %VENV%\Scripts\pip install -e . Success executing this command will end with a line to the console something like: .. code-block:: text - Successfully installed docutils-0.12 tutorial-0.0 + Successfully installed docutils-0.13.1 tutorial Adding view functions in ``views.py`` @@ -98,11 +99,11 @@ like the following: We added some imports and created a regular expression to find "WikiWords". We got rid of the ``my_view`` view function and its decorator that was added -when we originally rendered the ``zodb`` scaffold. It was only an example and +when we originally rendered the ``zodb`` cookiecutter. It was only an example and isn't relevant to our application. Then we added four :term:`view callable` functions to our ``views.py`` -module: +module: * ``view_wiki()`` - Displays the wiki itself. It will answer on the root URL. * ``view_page()`` - Displays an individual page. @@ -126,8 +127,7 @@ Following is the code for the ``view_wiki`` view function and its decorator: .. literalinclude:: src/views/tutorial/views.py :lines: 12-14 - :lineno-start: 12 - :linenos: + :lineno-match: :language: python .. note:: In our code, we use an *import* that is *relative* to our package @@ -165,8 +165,7 @@ Here is the code for the ``view_page`` view function and its decorator: .. literalinclude:: src/views/tutorial/views.py :lines: 16-33 - :lineno-start: 16 - :linenos: + :lineno-match: :language: python The ``view_page`` function is configured to respond as the default view @@ -219,8 +218,7 @@ Here is the code for the ``add_page`` view function and its decorator: .. literalinclude:: src/views/tutorial/views.py :lines: 35-50 - :lineno-start: 35 - :linenos: + :lineno-match: :language: python The ``add_page`` function is configured to respond when the context resource @@ -247,7 +245,7 @@ are found *after* the :term:`view name` in the URL segments given in the add view is invoked via, e.g., ``http://localhost:6543/add_page/SomeName``, the :term:`subpath` will be a tuple: ``('SomeName',)``. -The add view takes the zeroth element of the subpath (the wiki page name), +The add view takes the zero\ :sup:`th` element of the subpath (the wiki page name), and aliases it to the name attribute in order to know the name of the page we're trying to add. @@ -274,8 +272,7 @@ Here is the code for the ``edit_page`` view function and its decorator: .. literalinclude:: src/views/tutorial/views.py :lines: 52-60 - :lineno-start: 52 - :linenos: + :lineno-match: :language: python The ``edit_page`` function is configured to respond when the context is @@ -316,26 +313,26 @@ extension to be recognized as such. The ``view.pt`` template ------------------------ -Create ``tutorial/templates/view.pt`` and add the following -content: +Rename ``tutorial/templates/mytemplate.pt`` to ``tutorial/templates/view.pt`` and edit the emphasized lines to look like the following: .. literalinclude:: src/views/tutorial/templates/view.pt :linenos: :language: html + :emphasize-lines: 11-12,37-52 This template is used by ``view_page()`` for displaying a single wiki page. It includes: - A ``div`` element that is replaced with the ``content`` value provided by - the view (lines 36-38). ``content`` contains HTML, so the ``structure`` + the view (lines 37-39). ``content`` contains HTML, so the ``structure`` keyword is used to prevent escaping it (i.e., changing ">" to ">", etc.) - A link that points at the "edit" URL which invokes the ``edit_page`` view - for the page being viewed (lines 40-42). + for the page being viewed (lines 41-43). The ``edit.pt`` template ------------------------ -Create ``tutorial/templates/edit.pt`` and add the following content: +Copy ``tutorial/templates/view.pt`` to ``tutorial/templates/edit.pt`` and edit the emphasized lines to look like the following: .. literalinclude:: src/views/tutorial/templates/edit.pt :linenos: @@ -344,12 +341,12 @@ Create ``tutorial/templates/edit.pt`` and add the following content: This template is used by ``add_page()`` and ``edit_page()`` for adding and editing a wiki page. It displays a page containing a form that includes: -- A 10 row by 60 column ``textarea`` field named ``body`` that is filled - with any existing page data when it is rendered (line 45). -- A submit button that has the name ``form.submitted`` (line 48). +- A 10-row by 60-column ``textarea`` field named ``body`` that is filled + with any existing page data when it is rendered (line 46). +- A submit button that has the name ``form.submitted`` (line 49). The form POSTs back to the ``save_url`` argument supplied by the view (line -43). The view will use the ``body`` and ``form.submitted`` values. +44). The view will use the ``body`` and ``form.submitted`` values. .. note:: Our templates use a ``request`` object that none of our tutorial views return in their dictionary. ``request`` is one of several names that @@ -394,5 +391,5 @@ each of the following URLs, checking that the result is as expected: - http://localhost:6543/add_page/SomePageName invokes the add view for a Page. - To generate an error, visit http://localhost:6543/add_page which will - generate an ``IndexErrorr: tuple index out of range`` error. You'll see an + generate an ``IndexError: tuple index out of range`` error. You'll see an interactive traceback facility provided by :term:`pyramid_debugtoolbar`. diff --git a/docs/tutorials/wiki/design.rst b/docs/tutorials/wiki/design.rst index f2a02176b..30d443bb8 100644 --- a/docs/tutorials/wiki/design.rst +++ b/docs/tutorials/wiki/design.rst @@ -43,11 +43,8 @@ editing, and viewing wiki pages, plus one view for the wiki front page. Two templates will be used, one for viewing, and one for both adding and editing wiki pages. -The default templating systems in :app:`Pyramid` are -:term:`Chameleon` and :term:`Mako`. Chameleon is a variant of -:term:`ZPT`, which is an XML-based templating language. Mako is a -non-XML-based templating language. Because we had to pick one, -we chose Chameleon for this tutorial. +As of version 1.5 :app:`Pyramid` no longer ships with templating systems. In this tutorial, we will use :term:`Chameleon`. Chameleon is a variant of :term:`ZPT`, which is an XML-based templating language. + Security -------- diff --git a/docs/tutorials/wiki/distributing.rst b/docs/tutorials/wiki/distributing.rst index 386b880e6..fb0a552e0 100644 --- a/docs/tutorials/wiki/distributing.rst +++ b/docs/tutorials/wiki/distributing.rst @@ -6,9 +6,8 @@ Distributing Your Application Once your application works properly, you can create a "tarball" from it by using the ``setup.py sdist`` command. The following commands assume your -current working directory is the ``tutorial`` package we've created and that -the parent directory of the ``tutorial`` package is a virtual environment -representing a :app:`Pyramid` environment. +current working directory contains the ``tutorial`` package and the +``setup.py`` file. On UNIX: @@ -20,7 +19,7 @@ On Windows: .. code-block:: doscon - c:\pyramidtut> %VENV%\Scripts\python setup.py sdist + c:\tutorial> %VENV%\Scripts\python setup.py sdist The output of such a command will be something like: @@ -35,7 +34,7 @@ The output of such a command will be something like: Note that this command creates a tarball in the "dist" subdirectory named ``tutorial-0.0.tar.gz``. You can send this file to your friends to show them your cool new application. They should be able to install it by pointing the -``pip install .`` command directly at it. Or you can upload it to `PyPI +``pip install`` command directly at it. Or you can upload it to `PyPI <https://pypi.python.org/pypi>`_ and share it with the rest of the world, where it can be downloaded via ``pip install`` remotely like any other package people download from PyPI. diff --git a/docs/tutorials/wiki/index.rst b/docs/tutorials/wiki/index.rst index 7808c7623..7bd58656b 100644 --- a/docs/tutorials/wiki/index.rst +++ b/docs/tutorials/wiki/index.rst @@ -5,13 +5,12 @@ ZODB + Traversal Wiki Tutorial This tutorial introduces a :term:`ZODB` and :term:`traversal`-based :app:`Pyramid` application to a developer familiar with Python. It will be -most familiar to developers with previous :term:`Zope` experience. When the -is finished, the developer will have created a basic Wiki application with +most familiar to developers with previous :term:`Zope` experience. When +finished, the developer will have created a basic Wiki application with authentication. For cut and paste purposes, the source code for all stages of this -tutorial can be browsed on GitHub at `docs/tutorials/wiki/src -<https://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src>`_, +tutorial can be browsed on GitHub at `GitHub <https://github.com/Pylons/pyramid/>`_ for a specific branch or version under ``docs/tutorials/wiki/src``, which corresponds to the same location if you have Pyramid sources. .. toctree:: diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index 6172b122b..de057b1cc 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -15,159 +15,125 @@ install Pyramid**. Thereby you will satisfy the following requirements. * You've satisfied the :ref:`requirements-for-installing-packages`. -Create directory to contain the project ---------------------------------------- - -We need a workspace for our project files. - -On UNIX -^^^^^^^ - -.. code-block:: bash +Install cookiecutter +-------------------- +We will use a :term:`cookiecutter` to create a Python package project from a Python package project template. See `Cookiecutter Installation <https://cookiecutter.readthedocs.io/en/latest/installation.html>`_ for instructions. - $ mkdir ~/pyramidtut -On Windows -^^^^^^^^^^ - -.. code-block:: doscon - - c:\> mkdir pyramidtut - - -Create and use a virtual Python environment -------------------------------------------- +Generate a Pyramid project from a cookiecutter +---------------------------------------------- -Next let's create a virtual environment workspace for our project. We will use -the ``VENV`` environment variable instead of the absolute path of the virtual -environment. +We will create a Pyramid project in your home directory for UNIX or at the root for Windows. It is assumed you know the path to where you installed ``cookiecutter``. Issue the following commands and override the defaults in the prompts as follows. On UNIX ^^^^^^^ .. code-block:: bash - $ export VENV=~/pyramidtut - $ python3 -m venv $VENV + $ cd ~ + $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-zodb On Windows ^^^^^^^^^^ .. code-block:: doscon - c:\> set VENV=c:\pyramidtut + c:\> cd \ + c:\> cookiecutter https://github.com/Pylons/pyramid-cookiecutter-zodb -Each version of Python uses different paths, so you will need to adjust the -path to the command for your Python version. +On all operating systems +^^^^^^^^^^^^^^^^^^^^^^^^ +If prompted for the first item, accept the default ``yes`` by hitting return. -Python 2.7: - -.. code-block:: doscon - - c:\> c:\Python27\Scripts\virtualenv %VENV% - -Python 3.5: - -.. code-block:: doscon - - c:\> c:\Python35\Scripts\python -m venv %VENV% +.. code-block:: text + You've cloned ~/.cookiecutters/pyramid-cookiecutter-zodb before. + Is it okay to delete and re-clone it? [yes]: yes + project_name [Pyramid Scaffold]: myproj + repo_name [myproj]: tutorial -Upgrade ``pip`` and ``setuptools`` in the virtual environment -------------------------------------------------------------- +Change directory into your newly created project +------------------------------------------------ On UNIX ^^^^^^^ .. code-block:: bash - $ $VENV/bin/pip install --upgrade pip setuptools + $ cd tutorial On Windows ^^^^^^^^^^ .. code-block:: doscon - c:\> %VENV%\Scripts\pip install --upgrade pip setuptools + c:\> cd tutorial -Install Pyramid into the virtual Python environment ---------------------------------------------------- +Set and use a ``VENV`` environment variable +------------------------------------------- + +We will set the ``VENV`` environment variable to the absolute path of the virtual environment, and use it going forward. On UNIX ^^^^^^^ -.. parsed-literal:: +.. code-block:: bash - $ $VENV/bin/pip install "pyramid==\ |release|\ " + $ export VENV=~/tutorial On Windows ^^^^^^^^^^ -.. parsed-literal:: - - c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ " +.. code-block:: doscon + c:\tutorial> set VENV=c:\tutorial -Change directory to your virtual Python environment ---------------------------------------------------- -Change directory to the ``pyramidtut`` directory, which is both your workspace -and your virtual environment. +Create a virtual environment +---------------------------- On UNIX ^^^^^^^ .. code-block:: bash - $ cd pyramidtut + $ python3 -m venv $VENV On Windows ^^^^^^^^^^ -.. code-block:: doscon +Each version of Python uses different paths, so you might need to adjust the path to the command for your Python version. Recent versions of the Python 3 installer for Windows now install a Python launcher. - c:\> cd pyramidtut +Python 2.7: +.. code-block:: doscon -.. _making_a_project: + c:\tutorial> c:\Python27\Scripts\virtualenv %VENV% -Making a project ----------------- +Python 3.6: -Your next step is to create a project. For this tutorial, we will use -the :term:`scaffold` named ``zodb``, which generates an application -that uses :term:`ZODB` and :term:`traversal`. +.. code-block:: doscon -:app:`Pyramid` supplies a variety of scaffolds to generate sample projects. We -will use ``pcreate``, a script that comes with Pyramid, to create our project -using a scaffold. + c:\tutorial> python -m venv %VENV% -By passing ``zodb`` into the ``pcreate`` command, the script creates the files -needed to use ZODB. By passing in our application name ``tutorial``, the script -inserts that application name into all the required files. -The below instructions assume your current working directory is "pyramidtut". +Upgrade packaging tools in the virtual environment +-------------------------------------------------- On UNIX ^^^^^^^ .. code-block:: bash - $ $VENV/bin/pcreate -s zodb tutorial + $ $VENV/bin/pip install --upgrade pip setuptools On Windows ^^^^^^^^^^ .. code-block:: doscon - c:\pyramidtut> %VENV%\Scripts\pcreate -s zodb tutorial - -.. note:: If you are using Windows, the ``zodb`` scaffold may not deal - gracefully with installation into a location that contains spaces in the - path. If you experience startup problems, try putting both the virtual - environment and the project into directories that do not contain spaces in - their paths. + c:\tutorial> %VENV%\Scripts\pip install --upgrade pip setuptools .. _installing_project_in_dev_mode_zodb: @@ -175,76 +141,51 @@ On Windows Installing the project in development mode ------------------------------------------ -In order to do development on the project easily, you must "register" the -project as a development egg in your workspace using the ``pip install -e .`` -command. In order to do so, change directory to the ``tutorial`` directory that -you created in :ref:`making_a_project`, and run the ``pip install -e .`` -command using the virtual environment Python interpreter. +In order to do development on the project easily, you must "register" the project as a development egg in your workspace. We will install testing requirements at the same time. We do so with the following command. On UNIX ^^^^^^^ .. code-block:: bash - $ cd tutorial - $ $VENV/bin/pip install -e . + $ $VENV/bin/pip install -e ".[testing]" On Windows ^^^^^^^^^^ .. code-block:: doscon - c:\pyramidtut> cd tutorial - c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e . - -The console will show ``pip`` checking for packages and installing missing -packages. Success executing this command will show a line like the following: - -.. code-block:: bash + c:\tutorial> %VENV%\Scripts\pip install -e ".[testing]" - Successfully installed BTrees-4.2.0 Chameleon-2.24 Mako-1.0.4 \ - MarkupSafe-0.23 Pygments-2.1.3 ZConfig-3.1.0 ZEO-4.2.0b1 ZODB-4.2.0 \ - ZODB3-3.11.0 mock-2.0.0 pbr-1.8.1 persistent-4.1.1 pyramid-chameleon-0.3 \ - pyramid-debugtoolbar-2.4.2 pyramid-mako-1.0.2 pyramid-tm-0.12.1 \ - pyramid-zodbconn-0.7 six-1.10.0 transaction-1.4.4 tutorial waitress-0.8.10 \ - zc.lockfile-1.1.0 zdaemon-4.1.0 zodbpickle-0.6.0 zodburi-2.0 +On all operating systems +^^^^^^^^^^^^^^^^^^^^^^^^ +The console will show ``pip`` checking for packages and installing missing packages. Success executing this command will show a line like the following: -.. _install-testing-requirements-zodb: +.. code-block:: bash -Install testing requirements ----------------------------- + Successfully installed BTrees-4.3.1 Chameleon-3.0 Mako-1.0.6 \ + MarkupSafe-0.23 PasteDeploy-1.5.2 Pygments-2.1.3 WebOb-1.6.3 \ + WebTest-2.0.23 ZConfig-3.1.0 ZEO-5.0.4 ZODB-5.1.1 ZODB3-3.11.0 \ + beautifulsoup4-4.5.1 coverage-4.2 mock-2.0.0 pbr-1.10.0 persistent-4.2.2 \ + py-1.4.31 pyramid-1.7.3 pyramid-chameleon-0.3 pyramid-debugtoolbar-3.0.5 \ + pyramid-mako-1.0.2 pyramid-tm-1.1.1 pyramid-zodbconn-0.7 pytest-3.0.5 \ + pytest-cov-2.4.0 repoze.lru-0.6 six-1.10.0 transaction-2.0.3 \ + translationstring-1.3 tutorial venusian-1.0 waitress-1.0.1 \ + zc.lockfile-1.2.1 zdaemon-4.2.0 zodbpickle-0.6.0 zodburi-2.0 \ + zope.deprecation-4.2.0 zope.interface-4.3.3 -In order to run tests, we need to install the testing requirements. This is -done through our project's ``setup.py`` file, in the ``tests_require`` and -``extras_require`` stanzas, and by issuing the command below for your -operating system. +Testing requirements are defined in our project's ``setup.py`` file, in the ``tests_require`` and ``extras_require`` stanzas. .. literalinclude:: src/installation/setup.py - :language: python - :linenos: - :lineno-start: 22 - :lines: 22-26 + :language: python + :lineno-match: + :lines: 22-26 .. literalinclude:: src/installation/setup.py - :language: python - :linenos: - :lineno-start: 45 - :lines: 45-47 - -On UNIX -^^^^^^^ - -.. code-block:: bash - - $ $VENV/bin/pip install -e ".[testing]" - -On Windows -^^^^^^^^^^ - -.. code-block:: doscon - - c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e ".[testing]" + :language: python + :lineno-match: + :lines: 46-48 .. _running_tests: @@ -269,7 +210,7 @@ On Windows .. code-block:: doscon - c:\pyramidtut\tutorial> %VENV%\Scripts\py.test -q + c:\tutorial> %VENV%\Scripts\py.test -q For a successful test run, you should see output that ends like this: @@ -284,7 +225,7 @@ Expose test coverage information You can run the ``py.test`` command to see test coverage information. This runs the tests in the same way that ``py.test`` does, but provides additional -"coverage" information, exposing which lines of your project are covered by the +:term:`coverage` information, exposing which lines of your project are covered by the tests. We've already installed the ``pytest-cov`` package into our virtual @@ -302,41 +243,39 @@ On Windows .. code-block:: doscon - c:\pyramidtut\tutorial> %VENV%\Scripts\py.test --cov \ - --cov-report=term-missing + c:\tutorial> %VENV%\Scripts\py.test --cov --cov-report=term-missing If successful, you will see output something like this: .. code-block:: bash - ======================== test session starts ======================== - platform Python 3.5.1, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 - rootdir: /Users/stevepiercy/projects/pyramidtut/tutorial, inifile: - plugins: cov-2.2.1 - collected 1 items - - tutorial/tests.py . - ------------------ coverage: platform Python 3.5.1 ------------------ - Name Stmts Miss Cover Missing - ---------------------------------------------------- - tutorial/__init__.py 12 7 42% 7-8, 14-18 - tutorial/models.py 10 6 40% 9-14 - tutorial/tests.py 12 0 100% - tutorial/views.py 4 0 100% - ---------------------------------------------------- - TOTAL 38 13 66% - - ===================== 1 passed in 0.31 seconds ====================== + ======================== test session starts ======================== + platform Python 3.6.0, pytest-3.0.5, py-1.4.31, pluggy-0.4.0 + rootdir: /Users/stevepiercy/tutorial, inifile: + plugins: cov-2.4.0 + collected 1 items + + tutorial/tests.py . + ------------------ coverage: platform Python 3.6.0 ------------------ + Name Stmts Miss Cover Missing + ------------------------------------------------------- + tutorial/__init__.py 14 9 36% 7-8, 14-20 + tutorial/models.py 10 6 40% 9-14 + tutorial/views.py 4 0 100% + ------------------------------------------------------- + TOTAL 28 15 46% + + ===================== 1 passed in 0.31 seconds ====================== Our package doesn't quite have 100% test coverage. -.. _test_and_coverage_scaffold_defaults_zodb: +.. _test_and_coverage_cookiecutter_defaults_zodb: -Test and coverage scaffold defaults ------------------------------------ +Test and coverage cookiecutter defaults +--------------------------------------- -Scaffolds include configuration defaults for ``py.test`` and test coverage. +Cookiecutters include configuration defaults for ``py.test`` and test coverage. These configuration files are ``pytest.ini`` and ``.coveragerc``, located at the root of your package. Without these defaults, we would need to specify the path to the module on which we want to run tests and coverage. @@ -353,11 +292,10 @@ On Windows .. code-block:: doscon - c:\pyramidtut\tutorial> %VENV%\Scripts\py.test --cov=tutorial \ - --cov-report=term-missing tutorial\tests.py -q + c:\tutorial> %VENV%\Scripts\py.test --cov=tutorial tutorial\tests.py -q py.test follows :ref:`conventions for Python test discovery -<pytest:test discovery>`, and the configuration defaults from the scaffold +<pytest:test discovery>`, and the configuration defaults from the cookiecutter tell ``py.test`` where to find the module on which we want to run tests and coverage. @@ -370,7 +308,8 @@ coverage. Start the application --------------------- -Start the application. +Start the application. See :ref:`what_is_this_pserve_thing` for more +information on ``pserve``. On UNIX ^^^^^^^ @@ -384,7 +323,7 @@ On Windows .. code-block:: doscon - c:\pyramidtut\tutorial> %VENV%\Scripts\pserve development.ini --reload + c:\tutorial> %VENV%\Scripts\pserve development.ini --reload .. note:: @@ -395,9 +334,10 @@ If successful, you will see something like this on your console: .. code-block:: text - Starting subprocess with file monitor - Starting server in PID 82349. - serving on http://127.0.0.1:6543 + Starting subprocess with file monitor + Starting server in PID 44078. + Serving on http://localhost:6543 + Serving on http://localhost:6543 This means the server is ready to accept requests. @@ -414,13 +354,13 @@ page. You can read more about the purpose of the icon at application while you develop. -Decisions the ``zodb`` scaffold has made for you ------------------------------------------------- +Decisions the ``zodb`` cookiecutter has made for you +---------------------------------------------------- -Creating a project using the ``zodb`` scaffold makes the following +Creating a project using the ``zodb`` cookiecutter makes the following assumptions: -- You are willing to use :term:`ZODB` as persistent storage. +- You are willing to use :term:`ZODB` for persistent storage. - You are willing to use :term:`traversal` to map URLs to code. diff --git a/docs/tutorials/wiki/src/authorization/.coveragerc b/docs/tutorials/wiki/src/authorization/.coveragerc new file mode 100644 index 000000000..a1d87d03d --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = tutorial +omit = tutorial/test* diff --git a/docs/tutorials/wiki/src/authorization/CHANGES.txt b/docs/tutorials/wiki/src/authorization/CHANGES.txt index 35a34f332..14b902fd1 100644 --- a/docs/tutorials/wiki/src/authorization/CHANGES.txt +++ b/docs/tutorials/wiki/src/authorization/CHANGES.txt @@ -1,4 +1,4 @@ 0.0 --- -- Initial version +- Initial version. diff --git a/docs/tutorials/wiki/src/authorization/README.txt b/docs/tutorials/wiki/src/authorization/README.txt index dcb3605b8..5ec53bf9d 100644 --- a/docs/tutorials/wiki/src/authorization/README.txt +++ b/docs/tutorials/wiki/src/authorization/README.txt @@ -1,12 +1,29 @@ -tutorial README -================== +myproj +====== Getting Started --------------- -- cd <directory containing this file> +- Change directory into your newly created project. -- $VENV/bin/pip install -e . + cd tutorial -- $VENV/bin/pserve development.ini +- Create a Python virtual environment. + python3 -m venv env + +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools wheel + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/tutorials/wiki/src/authorization/development.ini b/docs/tutorials/wiki/src/authorization/development.ini index 6bf4b198e..74e7457d6 100644 --- a/docs/tutorials/wiki/src/authorization/development.ini +++ b/docs/tutorials/wiki/src/authorization/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -13,10 +13,7 @@ pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar - pyramid_zodbconn - pyramid_tm -tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 # By default, the toolbar only appears for clients from IP addresses @@ -29,12 +26,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -host = 127.0.0.1 -port = 6543 +listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/authorization/production.ini b/docs/tutorials/wiki/src/authorization/production.ini index 4e9892e7b..60b6fe253 100644 --- a/docs/tutorials/wiki/src/authorization/production.ini +++ b/docs/tutorials/wiki/src/authorization/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -11,11 +11,7 @@ pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en -pyramid.includes = - pyramid_tm - pyramid_zodbconn -tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 ### @@ -24,12 +20,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -host = 0.0.0.0 -port = 6543 +listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/authorization/pytest.ini b/docs/tutorials/wiki/src/authorization/pytest.ini new file mode 100644 index 000000000..8b76bc410 --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tutorial +python_files = *.py diff --git a/docs/tutorials/wiki/src/authorization/setup.py b/docs/tutorials/wiki/src/authorization/setup.py index beeed75c9..4a9f041e3 100644 --- a/docs/tutorials/wiki/src/authorization/setup.py +++ b/docs/tutorials/wiki/src/authorization/setup.py @@ -18,37 +18,40 @@ requires = [ 'ZODB3', 'waitress', 'docutils', - ] + 'bcrypt', +] tests_require = [ 'WebTest >= 1.3.1', # py3 compat - 'pytest', # includes virtualenv + 'pytest', 'pytest-cov', - ] +] -setup(name='tutorial', - version='0.0', - description='tutorial', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web pylons pyramid', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - extras_require={ - 'testing': tests_require, - }, - install_requires=requires, - entry_points="""\ - [paste.app_factory] - main = tutorial:main - """, - ) +setup( + name='tutorial', + version='0.0', + description='myproj', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = tutorial:main', + ], + }, +) diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py index 39b94abd1..8af2ee5c0 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py @@ -22,6 +22,8 @@ def main(global_config, **settings): config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) config.include('pyramid_chameleon') + config.include('pyramid_tm') + config.include('pyramid_zodbconn') config.add_static_view('static', 'static', cache_max_age=3600) config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/authorization/tutorial/security.py b/docs/tutorials/wiki/src/authorization/tutorial/security.py index d88c9c71f..cbb3acd5d 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/security.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/security.py @@ -1,5 +1,18 @@ -USERS = {'editor':'editor', - 'viewer':'viewer'} +import bcrypt + + +def hash_password(pw): + hashed_pw = bcrypt.hashpw(pw.encode('utf-8'), bcrypt.gensalt()) + # return unicode instead of bytes because databases handle it better + return hashed_pw.decode('utf-8') + +def check_password(expected_hash, pw): + if expected_hash is not None: + return bcrypt.checkpw(pw.encode('utf-8'), expected_hash.encode('utf-8')) + return False + +USERS = {'editor': hash_password('editor'), + 'viewer': hash_password('viewer')} GROUPS = {'editor':['group:editors']} def groupfinder(userid, request): diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt index 823fa8972..19adc5932 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt @@ -23,6 +23,7 @@ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> <![endif]--> </head> + <body> <div class="starter-template"> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt index 4a938e9bb..02f7038fe 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt @@ -23,6 +23,7 @@ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> <![endif]--> </head> + <body> <div class="starter-template"> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt index fa35d758d..17a715b50 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt @@ -23,6 +23,7 @@ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> <![endif]--> </head> + <body> <div class="starter-template"> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/tests.py b/docs/tutorials/wiki/src/authorization/tutorial/tests.py index 40f3c47af..ca7a47279 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/tests.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/tests.py @@ -14,4 +14,4 @@ class ViewTests(unittest.TestCase): from .views import my_view request = testing.DummyRequest() info = my_view(request) - self.assertEqual(info['project'], 'tutorial') + self.assertEqual(info['project'], 'myproj') diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views.py index c271d2cc1..ea2da01af 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/views.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/views.py @@ -14,7 +14,7 @@ from pyramid.security import ( ) -from .security import USERS +from .security import USERS, check_password from .models import Page # regular expression used to find WikiWords @@ -43,7 +43,6 @@ def view_page(context, request): content = publish_parts(context.data, writer_name='html')['html_body'] content = wikiwords.sub(check, content) edit_url = request.resource_url(context, 'edit_page') - return dict(page=context, content=content, edit_url=edit_url, logged_in=request.authenticated_userid) @@ -63,7 +62,6 @@ def add_page(context, request): page = Page('') page.__name__ = pagename page.__parent__ = context - return dict(page=page, save_url=save_url, logged_in=request.authenticated_userid) @@ -94,7 +92,7 @@ def login(request): if 'form.submitted' in request.params: login = request.params['login'] password = request.params['password'] - if USERS.get(login) == password: + if check_password(USERS.get(login), password): headers = remember(request, login) return HTTPFound(location=came_from, headers=headers) diff --git a/docs/tutorials/wiki/src/basiclayout/.coveragerc b/docs/tutorials/wiki/src/basiclayout/.coveragerc new file mode 100644 index 000000000..a1d87d03d --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = tutorial +omit = tutorial/test* diff --git a/docs/tutorials/wiki/src/basiclayout/CHANGES.txt b/docs/tutorials/wiki/src/basiclayout/CHANGES.txt index 35a34f332..14b902fd1 100644 --- a/docs/tutorials/wiki/src/basiclayout/CHANGES.txt +++ b/docs/tutorials/wiki/src/basiclayout/CHANGES.txt @@ -1,4 +1,4 @@ 0.0 --- -- Initial version +- Initial version. diff --git a/docs/tutorials/wiki/src/basiclayout/README.txt b/docs/tutorials/wiki/src/basiclayout/README.txt index dcb3605b8..5ec53bf9d 100644 --- a/docs/tutorials/wiki/src/basiclayout/README.txt +++ b/docs/tutorials/wiki/src/basiclayout/README.txt @@ -1,12 +1,29 @@ -tutorial README -================== +myproj +====== Getting Started --------------- -- cd <directory containing this file> +- Change directory into your newly created project. -- $VENV/bin/pip install -e . + cd tutorial -- $VENV/bin/pserve development.ini +- Create a Python virtual environment. + python3 -m venv env + +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools wheel + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/tutorials/wiki/src/basiclayout/development.ini b/docs/tutorials/wiki/src/basiclayout/development.ini index 6bf4b198e..74e7457d6 100644 --- a/docs/tutorials/wiki/src/basiclayout/development.ini +++ b/docs/tutorials/wiki/src/basiclayout/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -13,10 +13,7 @@ pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar - pyramid_zodbconn - pyramid_tm -tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 # By default, the toolbar only appears for clients from IP addresses @@ -29,12 +26,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -host = 127.0.0.1 -port = 6543 +listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/basiclayout/production.ini b/docs/tutorials/wiki/src/basiclayout/production.ini index 4e9892e7b..60b6fe253 100644 --- a/docs/tutorials/wiki/src/basiclayout/production.ini +++ b/docs/tutorials/wiki/src/basiclayout/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -11,11 +11,7 @@ pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en -pyramid.includes = - pyramid_tm - pyramid_zodbconn -tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 ### @@ -24,12 +20,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -host = 0.0.0.0 -port = 6543 +listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/basiclayout/pytest.ini b/docs/tutorials/wiki/src/basiclayout/pytest.ini new file mode 100644 index 000000000..8b76bc410 --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tutorial +python_files = *.py diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py index 46b395568..5d1e9c7b5 100644 --- a/docs/tutorials/wiki/src/basiclayout/setup.py +++ b/docs/tutorials/wiki/src/basiclayout/setup.py @@ -17,37 +17,39 @@ requires = [ 'transaction', 'ZODB3', 'waitress', - ] +] tests_require = [ 'WebTest >= 1.3.1', # py3 compat - 'pytest', # includes virtualenv + 'pytest', 'pytest-cov', - ] +] -setup(name='tutorial', - version='0.0', - description='tutorial', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web pylons pyramid', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - extras_require={ - 'testing': tests_require, - }, - install_requires=requires, - entry_points="""\ - [paste.app_factory] - main = tutorial:main - """, - ) +setup( + name='tutorial', + version='0.0', + description='myproj', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = tutorial:main', + ], + }, +) diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py index f2a86df47..728f7ac02 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py @@ -13,6 +13,8 @@ def main(global_config, **settings): """ config = Configurator(root_factory=root_factory, settings=settings) config.include('pyramid_chameleon') + config.include('pyramid_tm') + config.include('pyramid_zodbconn') config.add_static_view('static', 'static', cache_max_age=3600) config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt index f8cbe2e2c..3ac122711 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt @@ -8,7 +8,7 @@ <meta name="author" content="Pylons Project"> <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> - <title>ZODB Scaffold for The Pyramid Web Framework</title> + <title>Cookiecutter ZODB project for the Pyramid Web Framework</title> <!-- Bootstrap core CSS --> <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> @@ -33,18 +33,16 @@ </div> <div class="col-md-10"> <div class="content"> - <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1> - <p class="lead">Welcome to <span class="font-normal">${project}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB Project</span></h1> + <p class="lead">Welcome to <span class="font-normal">${project}</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p> </div> </div> </div> <div class="row"> <div class="links"> <ul> - <li class="current-version">Generated by v1.7</li> - <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li> <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> - <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> </ul> </div> diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py index 40f3c47af..ca7a47279 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py @@ -14,4 +14,4 @@ class ViewTests(unittest.TestCase): from .views import my_view request = testing.DummyRequest() info = my_view(request) - self.assertEqual(info['project'], 'tutorial') + self.assertEqual(info['project'], 'myproj') diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py index 628ce15ed..c1878bdd0 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py @@ -4,4 +4,4 @@ from .models import MyModel @view_config(context=MyModel, renderer='templates/mytemplate.pt') def my_view(request): - return {'project': 'tutorial'} + return {'project': 'myproj'} diff --git a/docs/tutorials/wiki/src/installation/.coveragerc b/docs/tutorials/wiki/src/installation/.coveragerc new file mode 100644 index 000000000..a1d87d03d --- /dev/null +++ b/docs/tutorials/wiki/src/installation/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = tutorial +omit = tutorial/test* diff --git a/docs/tutorials/wiki/src/installation/CHANGES.txt b/docs/tutorials/wiki/src/installation/CHANGES.txt index 35a34f332..14b902fd1 100644 --- a/docs/tutorials/wiki/src/installation/CHANGES.txt +++ b/docs/tutorials/wiki/src/installation/CHANGES.txt @@ -1,4 +1,4 @@ 0.0 --- -- Initial version +- Initial version. diff --git a/docs/tutorials/wiki/src/installation/README.txt b/docs/tutorials/wiki/src/installation/README.txt index dcb3605b8..5ec53bf9d 100644 --- a/docs/tutorials/wiki/src/installation/README.txt +++ b/docs/tutorials/wiki/src/installation/README.txt @@ -1,12 +1,29 @@ -tutorial README -================== +myproj +====== Getting Started --------------- -- cd <directory containing this file> +- Change directory into your newly created project. -- $VENV/bin/pip install -e . + cd tutorial -- $VENV/bin/pserve development.ini +- Create a Python virtual environment. + python3 -m venv env + +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools wheel + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/tutorials/wiki/src/installation/development.ini b/docs/tutorials/wiki/src/installation/development.ini index 6bf4b198e..74e7457d6 100644 --- a/docs/tutorials/wiki/src/installation/development.ini +++ b/docs/tutorials/wiki/src/installation/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -13,10 +13,7 @@ pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar - pyramid_zodbconn - pyramid_tm -tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 # By default, the toolbar only appears for clients from IP addresses @@ -29,12 +26,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -host = 127.0.0.1 -port = 6543 +listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/installation/production.ini b/docs/tutorials/wiki/src/installation/production.ini index 4e9892e7b..60b6fe253 100644 --- a/docs/tutorials/wiki/src/installation/production.ini +++ b/docs/tutorials/wiki/src/installation/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -11,11 +11,7 @@ pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en -pyramid.includes = - pyramid_tm - pyramid_zodbconn -tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 ### @@ -24,12 +20,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -host = 0.0.0.0 -port = 6543 +listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/installation/pytest.ini b/docs/tutorials/wiki/src/installation/pytest.ini new file mode 100644 index 000000000..8b76bc410 --- /dev/null +++ b/docs/tutorials/wiki/src/installation/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tutorial +python_files = *.py diff --git a/docs/tutorials/wiki/src/installation/setup.py b/docs/tutorials/wiki/src/installation/setup.py index 46b395568..5d1e9c7b5 100644 --- a/docs/tutorials/wiki/src/installation/setup.py +++ b/docs/tutorials/wiki/src/installation/setup.py @@ -17,37 +17,39 @@ requires = [ 'transaction', 'ZODB3', 'waitress', - ] +] tests_require = [ 'WebTest >= 1.3.1', # py3 compat - 'pytest', # includes virtualenv + 'pytest', 'pytest-cov', - ] +] -setup(name='tutorial', - version='0.0', - description='tutorial', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web pylons pyramid', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - extras_require={ - 'testing': tests_require, - }, - install_requires=requires, - entry_points="""\ - [paste.app_factory] - main = tutorial:main - """, - ) +setup( + name='tutorial', + version='0.0', + description='myproj', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = tutorial:main', + ], + }, +) diff --git a/docs/tutorials/wiki/src/installation/tutorial/__init__.py b/docs/tutorials/wiki/src/installation/tutorial/__init__.py index f2a86df47..728f7ac02 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/installation/tutorial/__init__.py @@ -13,6 +13,8 @@ def main(global_config, **settings): """ config = Configurator(root_factory=root_factory, settings=settings) config.include('pyramid_chameleon') + config.include('pyramid_tm') + config.include('pyramid_zodbconn') config.add_static_view('static', 'static', cache_max_age=3600) config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt index f8cbe2e2c..3ac122711 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt @@ -8,7 +8,7 @@ <meta name="author" content="Pylons Project"> <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> - <title>ZODB Scaffold for The Pyramid Web Framework</title> + <title>Cookiecutter ZODB project for the Pyramid Web Framework</title> <!-- Bootstrap core CSS --> <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> @@ -33,18 +33,16 @@ </div> <div class="col-md-10"> <div class="content"> - <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1> - <p class="lead">Welcome to <span class="font-normal">${project}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB Project</span></h1> + <p class="lead">Welcome to <span class="font-normal">${project}</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p> </div> </div> </div> <div class="row"> <div class="links"> <ul> - <li class="current-version">Generated by v1.7</li> - <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li> <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> - <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> </ul> </div> diff --git a/docs/tutorials/wiki/src/installation/tutorial/tests.py b/docs/tutorials/wiki/src/installation/tutorial/tests.py index 40f3c47af..ca7a47279 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/tests.py +++ b/docs/tutorials/wiki/src/installation/tutorial/tests.py @@ -14,4 +14,4 @@ class ViewTests(unittest.TestCase): from .views import my_view request = testing.DummyRequest() info = my_view(request) - self.assertEqual(info['project'], 'tutorial') + self.assertEqual(info['project'], 'myproj') diff --git a/docs/tutorials/wiki/src/installation/tutorial/views.py b/docs/tutorials/wiki/src/installation/tutorial/views.py index 628ce15ed..c1878bdd0 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/views.py +++ b/docs/tutorials/wiki/src/installation/tutorial/views.py @@ -4,4 +4,4 @@ from .models import MyModel @view_config(context=MyModel, renderer='templates/mytemplate.pt') def my_view(request): - return {'project': 'tutorial'} + return {'project': 'myproj'} diff --git a/docs/tutorials/wiki/src/models/.coveragerc b/docs/tutorials/wiki/src/models/.coveragerc new file mode 100644 index 000000000..a1d87d03d --- /dev/null +++ b/docs/tutorials/wiki/src/models/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = tutorial +omit = tutorial/test* diff --git a/docs/tutorials/wiki/src/models/CHANGES.txt b/docs/tutorials/wiki/src/models/CHANGES.txt index 35a34f332..14b902fd1 100644 --- a/docs/tutorials/wiki/src/models/CHANGES.txt +++ b/docs/tutorials/wiki/src/models/CHANGES.txt @@ -1,4 +1,4 @@ 0.0 --- -- Initial version +- Initial version. diff --git a/docs/tutorials/wiki/src/models/README.txt b/docs/tutorials/wiki/src/models/README.txt index dcb3605b8..5ec53bf9d 100644 --- a/docs/tutorials/wiki/src/models/README.txt +++ b/docs/tutorials/wiki/src/models/README.txt @@ -1,12 +1,29 @@ -tutorial README -================== +myproj +====== Getting Started --------------- -- cd <directory containing this file> +- Change directory into your newly created project. -- $VENV/bin/pip install -e . + cd tutorial -- $VENV/bin/pserve development.ini +- Create a Python virtual environment. + python3 -m venv env + +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools wheel + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/tutorials/wiki/src/models/development.ini b/docs/tutorials/wiki/src/models/development.ini index 6bf4b198e..74e7457d6 100644 --- a/docs/tutorials/wiki/src/models/development.ini +++ b/docs/tutorials/wiki/src/models/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -13,10 +13,7 @@ pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar - pyramid_zodbconn - pyramid_tm -tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 # By default, the toolbar only appears for clients from IP addresses @@ -29,12 +26,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -host = 127.0.0.1 -port = 6543 +listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/models/production.ini b/docs/tutorials/wiki/src/models/production.ini index 4e9892e7b..60b6fe253 100644 --- a/docs/tutorials/wiki/src/models/production.ini +++ b/docs/tutorials/wiki/src/models/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -11,11 +11,7 @@ pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en -pyramid.includes = - pyramid_tm - pyramid_zodbconn -tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 ### @@ -24,12 +20,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -host = 0.0.0.0 -port = 6543 +listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/models/pytest.ini b/docs/tutorials/wiki/src/models/pytest.ini new file mode 100644 index 000000000..8b76bc410 --- /dev/null +++ b/docs/tutorials/wiki/src/models/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tutorial +python_files = *.py diff --git a/docs/tutorials/wiki/src/models/setup.py b/docs/tutorials/wiki/src/models/setup.py index 46b395568..5d1e9c7b5 100644 --- a/docs/tutorials/wiki/src/models/setup.py +++ b/docs/tutorials/wiki/src/models/setup.py @@ -17,37 +17,39 @@ requires = [ 'transaction', 'ZODB3', 'waitress', - ] +] tests_require = [ 'WebTest >= 1.3.1', # py3 compat - 'pytest', # includes virtualenv + 'pytest', 'pytest-cov', - ] +] -setup(name='tutorial', - version='0.0', - description='tutorial', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web pylons pyramid', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - extras_require={ - 'testing': tests_require, - }, - install_requires=requires, - entry_points="""\ - [paste.app_factory] - main = tutorial:main - """, - ) +setup( + name='tutorial', + version='0.0', + description='myproj', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = tutorial:main', + ], + }, +) diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py index f2a86df47..728f7ac02 100644 --- a/docs/tutorials/wiki/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py @@ -13,6 +13,8 @@ def main(global_config, **settings): """ config = Configurator(root_factory=root_factory, settings=settings) config.include('pyramid_chameleon') + config.include('pyramid_tm') + config.include('pyramid_zodbconn') config.add_static_view('static', 'static', cache_max_age=3600) config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt index f8cbe2e2c..3ac122711 100644 --- a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt @@ -8,7 +8,7 @@ <meta name="author" content="Pylons Project"> <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> - <title>ZODB Scaffold for The Pyramid Web Framework</title> + <title>Cookiecutter ZODB project for the Pyramid Web Framework</title> <!-- Bootstrap core CSS --> <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> @@ -33,18 +33,16 @@ </div> <div class="col-md-10"> <div class="content"> - <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1> - <p class="lead">Welcome to <span class="font-normal">${project}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB Project</span></h1> + <p class="lead">Welcome to <span class="font-normal">${project}</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p> </div> </div> </div> <div class="row"> <div class="links"> <ul> - <li class="current-version">Generated by v1.7</li> - <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li> <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> - <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> </ul> </div> diff --git a/docs/tutorials/wiki/src/models/tutorial/tests.py b/docs/tutorials/wiki/src/models/tutorial/tests.py index 40f3c47af..ca7a47279 100644 --- a/docs/tutorials/wiki/src/models/tutorial/tests.py +++ b/docs/tutorials/wiki/src/models/tutorial/tests.py @@ -14,4 +14,4 @@ class ViewTests(unittest.TestCase): from .views import my_view request = testing.DummyRequest() info = my_view(request) - self.assertEqual(info['project'], 'tutorial') + self.assertEqual(info['project'], 'myproj') diff --git a/docs/tutorials/wiki/src/models/tutorial/views.py b/docs/tutorials/wiki/src/models/tutorial/views.py index 628ce15ed..c1878bdd0 100644 --- a/docs/tutorials/wiki/src/models/tutorial/views.py +++ b/docs/tutorials/wiki/src/models/tutorial/views.py @@ -4,4 +4,4 @@ from .models import MyModel @view_config(context=MyModel, renderer='templates/mytemplate.pt') def my_view(request): - return {'project': 'tutorial'} + return {'project': 'myproj'} diff --git a/docs/tutorials/wiki/src/tests/.coveragerc b/docs/tutorials/wiki/src/tests/.coveragerc new file mode 100644 index 000000000..a1d87d03d --- /dev/null +++ b/docs/tutorials/wiki/src/tests/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = tutorial +omit = tutorial/test* diff --git a/docs/tutorials/wiki/src/tests/CHANGES.txt b/docs/tutorials/wiki/src/tests/CHANGES.txt index 35a34f332..14b902fd1 100644 --- a/docs/tutorials/wiki/src/tests/CHANGES.txt +++ b/docs/tutorials/wiki/src/tests/CHANGES.txt @@ -1,4 +1,4 @@ 0.0 --- -- Initial version +- Initial version. diff --git a/docs/tutorials/wiki/src/tests/README.txt b/docs/tutorials/wiki/src/tests/README.txt index dcb3605b8..5ec53bf9d 100644 --- a/docs/tutorials/wiki/src/tests/README.txt +++ b/docs/tutorials/wiki/src/tests/README.txt @@ -1,12 +1,29 @@ -tutorial README -================== +myproj +====== Getting Started --------------- -- cd <directory containing this file> +- Change directory into your newly created project. -- $VENV/bin/pip install -e . + cd tutorial -- $VENV/bin/pserve development.ini +- Create a Python virtual environment. + python3 -m venv env + +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools wheel + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/tutorials/wiki/src/tests/development.ini b/docs/tutorials/wiki/src/tests/development.ini index 6bf4b198e..74e7457d6 100644 --- a/docs/tutorials/wiki/src/tests/development.ini +++ b/docs/tutorials/wiki/src/tests/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -13,10 +13,7 @@ pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar - pyramid_zodbconn - pyramid_tm -tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 # By default, the toolbar only appears for clients from IP addresses @@ -29,12 +26,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -host = 127.0.0.1 -port = 6543 +listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/tests/production.ini b/docs/tutorials/wiki/src/tests/production.ini index 4e9892e7b..60b6fe253 100644 --- a/docs/tutorials/wiki/src/tests/production.ini +++ b/docs/tutorials/wiki/src/tests/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -11,11 +11,7 @@ pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en -pyramid.includes = - pyramid_tm - pyramid_zodbconn -tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 ### @@ -24,12 +20,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -host = 0.0.0.0 -port = 6543 +listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/tests/pytest.ini b/docs/tutorials/wiki/src/tests/pytest.ini new file mode 100644 index 000000000..8b76bc410 --- /dev/null +++ b/docs/tutorials/wiki/src/tests/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tutorial +python_files = *.py diff --git a/docs/tutorials/wiki/src/tests/setup.py b/docs/tutorials/wiki/src/tests/setup.py index beeed75c9..4a9f041e3 100644 --- a/docs/tutorials/wiki/src/tests/setup.py +++ b/docs/tutorials/wiki/src/tests/setup.py @@ -18,37 +18,40 @@ requires = [ 'ZODB3', 'waitress', 'docutils', - ] + 'bcrypt', +] tests_require = [ 'WebTest >= 1.3.1', # py3 compat - 'pytest', # includes virtualenv + 'pytest', 'pytest-cov', - ] +] -setup(name='tutorial', - version='0.0', - description='tutorial', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web pylons pyramid', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - extras_require={ - 'testing': tests_require, - }, - install_requires=requires, - entry_points="""\ - [paste.app_factory] - main = tutorial:main - """, - ) +setup( + name='tutorial', + version='0.0', + description='myproj', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = tutorial:main', + ], + }, +) diff --git a/docs/tutorials/wiki/src/tests/tutorial/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/__init__.py index 39b94abd1..8af2ee5c0 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/tests/tutorial/__init__.py @@ -22,6 +22,8 @@ def main(global_config, **settings): config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) config.include('pyramid_chameleon') + config.include('pyramid_tm') + config.include('pyramid_zodbconn') config.add_static_view('static', 'static', cache_max_age=3600) config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/tests/tutorial/security.py b/docs/tutorials/wiki/src/tests/tutorial/security.py index d88c9c71f..cbb3acd5d 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/security.py +++ b/docs/tutorials/wiki/src/tests/tutorial/security.py @@ -1,5 +1,18 @@ -USERS = {'editor':'editor', - 'viewer':'viewer'} +import bcrypt + + +def hash_password(pw): + hashed_pw = bcrypt.hashpw(pw.encode('utf-8'), bcrypt.gensalt()) + # return unicode instead of bytes because databases handle it better + return hashed_pw.decode('utf-8') + +def check_password(expected_hash, pw): + if expected_hash is not None: + return bcrypt.checkpw(pw.encode('utf-8'), expected_hash.encode('utf-8')) + return False + +USERS = {'editor': hash_password('editor'), + 'viewer': hash_password('viewer')} GROUPS = {'editor':['group:editors']} def groupfinder(userid, request): diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt index 823fa8972..19adc5932 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt @@ -23,6 +23,7 @@ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> <![endif]--> </head> + <body> <div class="starter-template"> diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt index 4a938e9bb..02f7038fe 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt @@ -23,6 +23,7 @@ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> <![endif]--> </head> + <body> <div class="starter-template"> diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt index fa35d758d..17a715b50 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt @@ -23,6 +23,7 @@ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> <![endif]--> </head> + <body> <div class="starter-template"> diff --git a/docs/tutorials/wiki/src/tests/tutorial/tests.py b/docs/tutorials/wiki/src/tests/tutorial/tests.py index 04beaea44..098e9c1bd 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/tests.py +++ b/docs/tutorials/wiki/src/tests/tutorial/tests.py @@ -122,6 +122,17 @@ class EditPageTests(unittest.TestCase): self.assertEqual(response.location, 'http://example.com/') self.assertEqual(context.data, 'Hello yo!') +class SecurityTests(unittest.TestCase): + def test_hashing(self): + from .security import hash_password, check_password + password = 'secretpassword' + hashed_password = hash_password(password) + self.assertTrue(check_password(hashed_password, password)) + + self.assertFalse(check_password(hashed_password, 'attackerpassword')) + + self.assertFalse(check_password(None, password)) + class FunctionalTests(unittest.TestCase): viewer_login = '/login?login=viewer&password=viewer' \ diff --git a/docs/tutorials/wiki/src/tests/tutorial/views.py b/docs/tutorials/wiki/src/tests/tutorial/views.py index c271d2cc1..ea2da01af 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/views.py +++ b/docs/tutorials/wiki/src/tests/tutorial/views.py @@ -14,7 +14,7 @@ from pyramid.security import ( ) -from .security import USERS +from .security import USERS, check_password from .models import Page # regular expression used to find WikiWords @@ -43,7 +43,6 @@ def view_page(context, request): content = publish_parts(context.data, writer_name='html')['html_body'] content = wikiwords.sub(check, content) edit_url = request.resource_url(context, 'edit_page') - return dict(page=context, content=content, edit_url=edit_url, logged_in=request.authenticated_userid) @@ -63,7 +62,6 @@ def add_page(context, request): page = Page('') page.__name__ = pagename page.__parent__ = context - return dict(page=page, save_url=save_url, logged_in=request.authenticated_userid) @@ -94,7 +92,7 @@ def login(request): if 'form.submitted' in request.params: login = request.params['login'] password = request.params['password'] - if USERS.get(login) == password: + if check_password(USERS.get(login), password): headers = remember(request, login) return HTTPFound(location=came_from, headers=headers) diff --git a/docs/tutorials/wiki/src/views/.coveragerc b/docs/tutorials/wiki/src/views/.coveragerc new file mode 100644 index 000000000..a1d87d03d --- /dev/null +++ b/docs/tutorials/wiki/src/views/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = tutorial +omit = tutorial/test* diff --git a/docs/tutorials/wiki/src/views/CHANGES.txt b/docs/tutorials/wiki/src/views/CHANGES.txt index 35a34f332..14b902fd1 100644 --- a/docs/tutorials/wiki/src/views/CHANGES.txt +++ b/docs/tutorials/wiki/src/views/CHANGES.txt @@ -1,4 +1,4 @@ 0.0 --- -- Initial version +- Initial version. diff --git a/docs/tutorials/wiki/src/views/README.txt b/docs/tutorials/wiki/src/views/README.txt index dcb3605b8..5ec53bf9d 100644 --- a/docs/tutorials/wiki/src/views/README.txt +++ b/docs/tutorials/wiki/src/views/README.txt @@ -1,12 +1,29 @@ -tutorial README -================== +myproj +====== Getting Started --------------- -- cd <directory containing this file> +- Change directory into your newly created project. -- $VENV/bin/pip install -e . + cd tutorial -- $VENV/bin/pserve development.ini +- Create a Python virtual environment. + python3 -m venv env + +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools wheel + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/tutorials/wiki/src/views/development.ini b/docs/tutorials/wiki/src/views/development.ini index 6bf4b198e..74e7457d6 100644 --- a/docs/tutorials/wiki/src/views/development.ini +++ b/docs/tutorials/wiki/src/views/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -13,10 +13,7 @@ pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar - pyramid_zodbconn - pyramid_tm -tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 # By default, the toolbar only appears for clients from IP addresses @@ -29,12 +26,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -host = 127.0.0.1 -port = 6543 +listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/views/production.ini b/docs/tutorials/wiki/src/views/production.ini index 4e9892e7b..60b6fe253 100644 --- a/docs/tutorials/wiki/src/views/production.ini +++ b/docs/tutorials/wiki/src/views/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -11,11 +11,7 @@ pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en -pyramid.includes = - pyramid_tm - pyramid_zodbconn -tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 ### @@ -24,12 +20,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -host = 0.0.0.0 -port = 6543 +listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/views/pytest.ini b/docs/tutorials/wiki/src/views/pytest.ini new file mode 100644 index 000000000..8b76bc410 --- /dev/null +++ b/docs/tutorials/wiki/src/views/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tutorial +python_files = *.py diff --git a/docs/tutorials/wiki/src/views/setup.py b/docs/tutorials/wiki/src/views/setup.py index beeed75c9..598ad8146 100644 --- a/docs/tutorials/wiki/src/views/setup.py +++ b/docs/tutorials/wiki/src/views/setup.py @@ -18,37 +18,39 @@ requires = [ 'ZODB3', 'waitress', 'docutils', - ] +] tests_require = [ 'WebTest >= 1.3.1', # py3 compat - 'pytest', # includes virtualenv + 'pytest', 'pytest-cov', - ] +] -setup(name='tutorial', - version='0.0', - description='tutorial', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web pylons pyramid', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - extras_require={ - 'testing': tests_require, - }, - install_requires=requires, - entry_points="""\ - [paste.app_factory] - main = tutorial:main - """, - ) +setup( + name='tutorial', + version='0.0', + description='myproj', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = tutorial:main', + ], + }, +) diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py index f2a86df47..728f7ac02 100644 --- a/docs/tutorials/wiki/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py @@ -13,6 +13,8 @@ def main(global_config, **settings): """ config = Configurator(root_factory=root_factory, settings=settings) config.include('pyramid_chameleon') + config.include('pyramid_tm') + config.include('pyramid_zodbconn') config.add_static_view('static', 'static', cache_max_age=3600) config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt index 93580658b..5caaef4af 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt @@ -23,6 +23,7 @@ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> <![endif]--> </head> + <body> <div class="starter-template"> diff --git a/docs/tutorials/wiki/src/views/tutorial/tests.py b/docs/tutorials/wiki/src/views/tutorial/tests.py index 40f3c47af..ca7a47279 100644 --- a/docs/tutorials/wiki/src/views/tutorial/tests.py +++ b/docs/tutorials/wiki/src/views/tutorial/tests.py @@ -14,4 +14,4 @@ class ViewTests(unittest.TestCase): from .views import my_view request = testing.DummyRequest() info = my_view(request) - self.assertEqual(info['project'], 'tutorial') + self.assertEqual(info['project'], 'myproj') diff --git a/docs/tutorials/wiki/src/views/tutorial/views.py b/docs/tutorials/wiki/src/views/tutorial/views.py index 61517c31d..fd2b0edc1 100644 --- a/docs/tutorials/wiki/src/views/tutorial/views.py +++ b/docs/tutorials/wiki/src/views/tutorial/views.py @@ -24,13 +24,13 @@ def view_page(context, request): view_url = request.resource_url(page) return '<a href="%s">%s</a>' % (view_url, word) else: - add_url = request.application_url + '/add_page/' + word + add_url = request.application_url + '/add_page/' + word return '<a href="%s">%s</a>' % (add_url, word) content = publish_parts(context.data, writer_name='html')['html_body'] content = wikiwords.sub(check, content) edit_url = request.resource_url(context, 'edit_page') - return dict(page = context, content = content, edit_url = edit_url) + return dict(page=context, content=content, edit_url=edit_url) @view_config(name='add_page', context='.models.Wiki', renderer='templates/edit.pt') @@ -42,19 +42,19 @@ def add_page(context, request): page.__name__ = pagename page.__parent__ = context context[pagename] = page - return HTTPFound(location = request.resource_url(page)) + return HTTPFound(location=request.resource_url(page)) save_url = request.resource_url(context, 'add_page', pagename) page = Page('') page.__name__ = pagename page.__parent__ = context - return dict(page = page, save_url = save_url) + return dict(page=page, save_url=save_url) @view_config(name='edit_page', context='.models.Page', renderer='templates/edit.pt') def edit_page(context, request): if 'form.submitted' in request.params: context.data = request.params['body'] - return HTTPFound(location = request.resource_url(context)) + return HTTPFound(location=request.resource_url(context)) return dict(page=context, - save_url=request.resource_url(context, 'edit_page')) + save_url=request.resource_url(context, 'edit_page'))
\ No newline at end of file diff --git a/docs/tutorials/wiki/tests.rst b/docs/tutorials/wiki/tests.rst index 85a023cc9..cd82c0118 100644 --- a/docs/tutorials/wiki/tests.rst +++ b/docs/tutorials/wiki/tests.rst @@ -16,7 +16,7 @@ We write tests for the ``model`` classes and the ``appmaker``. Changing we'll write a test class for the ``appmaker``. To do so, we'll retain the ``tutorial.tests.ViewTests`` class that was -generated as part of the ``zodb`` scaffold. We'll add three test classes: one +generated as part of the ``zodb`` cookiecutter. We'll add three test classes: one for the ``Page`` model named ``PageModelTests``, one for the ``Wiki`` model named ``WikiModelTests``, and one for the appmaker named ``AppmakerTests``. @@ -24,8 +24,8 @@ Test the views ============== We'll modify our ``tests.py`` file, adding tests for each view function we -added previously. As a result, we'll *delete* the ``ViewTests`` class that -the ``zodb`` scaffold provided, and add four other test classes: +added previously. As a result, we'll delete the ``ViewTests`` class that +the ``zodb`` cookiecutter provided, and add four other test classes: ``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and ``EditPageTests``. These test the ``view_wiki``, ``view_page``, ``add_page``, and ``edit_page`` views. @@ -52,25 +52,25 @@ Running the tests ================= We can run these tests by using ``py.test`` similarly to how we did in -:ref:`running_tests`. Courtesy of the scaffold, our testing dependencies have +:ref:`running_tests`. Courtesy of the cookiecutter, our testing dependencies have already been satisfied and ``py.test`` and coverage have already been configured, so we can jump right to running tests. On UNIX: -.. code-block:: text +.. code-block:: bash $ $VENV/bin/py.test -q On Windows: -.. code-block:: text +.. code-block:: doscon - c:\pyramidtut\tutorial> %VENV%\Scripts\py.test -q + c:\tutorial> %VENV%\Scripts\py.test -q The expected result should look like the following: .. code-block:: text - ........................ - 24 passed in 2.46 seconds + ......................... + 25 passed in 6.87 seconds diff --git a/docs/tutorials/wiki2/authentication.rst b/docs/tutorials/wiki2/authentication.rst index 5447db861..ff59ce70b 100644 --- a/docs/tutorials/wiki2/authentication.rst +++ b/docs/tutorials/wiki2/authentication.rst @@ -92,7 +92,7 @@ Our authentication policy is expecting a new setting, ``auth.secret``. Open the file ``development.ini`` and add the highlighted line below: .. literalinclude:: src/authentication/development.ini - :lines: 18-20 + :lines: 17-19 :emphasize-lines: 3 :lineno-match: :language: ini diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst index 98a14c644..233231f8d 100644 --- a/docs/tutorials/wiki2/basiclayout.rst +++ b/docs/tutorials/wiki2/basiclayout.rst @@ -4,7 +4,7 @@ Basic Layout ============ -The starter files generated by the ``alchemy`` scaffold are very basic, but +The starter files generated by the ``alchemy`` cookiecutter are very basic, but they provide a good orientation for the high-level patterns common to most :term:`URL dispatch`-based :app:`Pyramid` projects. @@ -29,7 +29,6 @@ code: .. literalinclude:: src/basiclayout/tutorial/__init__.py :end-before: main - :linenos: :lineno-match: :language: py @@ -38,7 +37,6 @@ the ``main`` function we've defined in our ``__init__.py``: .. literalinclude:: src/basiclayout/tutorial/__init__.py :pyobject: main - :linenos: :lineno-match: :language: py @@ -179,7 +177,7 @@ decorator in order to create a view configuration within our application. Without being processed by ``scan``, the decorator effectively does nothing. ``@view_config`` is inert without being detected via a :term:`scan`. -The sample ``my_view()`` created by the scaffold uses a ``try:`` and +The sample ``my_view()`` created by the cookiecutter uses a ``try:`` and ``except:`` clause to detect if there is a problem accessing the project database and provide an alternate error response. That response will include the text shown at the end of the file, which will be displayed in the browser @@ -191,7 +189,7 @@ Content models with the ``models`` package In an SQLAlchemy-based application, a *model* object is an object composed by querying the SQL database. The ``models`` package is where the ``alchemy`` -scaffold put the classes that implement our models. +cookiecutter put the classes that implement our models. First, open ``tutorial/models/meta.py``, which should already contain the following: @@ -222,7 +220,6 @@ application's database schema. .. literalinclude:: src/basiclayout/tutorial/models/meta.py :lines: 15-16 :lineno-match: - :linenos: :language: py Next open ``tutorial/models/mymodel.py``, which should already contain the @@ -239,7 +236,6 @@ we have defined one named ``MyModel`` in ``mymodel.py``: .. literalinclude:: src/basiclayout/tutorial/models/mymodel.py :pyobject: MyModel :lineno-match: - :linenos: :language: py Our example model does not require an ``__init__`` method because SQLAlchemy @@ -292,7 +288,6 @@ database engine using :func:`sqlalchemy.engine_from_config` from the .. literalinclude:: src/basiclayout/tutorial/models/__init__.py :pyobject: get_engine :lineno-match: - :linenos: :language: py The function ``get_session_factory`` accepts an :term:`SQLAlchemy` database @@ -303,7 +298,6 @@ used for creating sessions bound to the database engine. .. literalinclude:: src/basiclayout/tutorial/models/__init__.py :pyobject: get_session_factory :lineno-match: - :linenos: :language: py The function ``get_tm_session`` registers a database session with a transaction @@ -314,7 +308,6 @@ unless an exception is raised, in which case the transaction will be aborted. .. literalinclude:: src/basiclayout/tutorial/models/__init__.py :pyobject: get_tm_session :lineno-match: - :linenos: :language: py Finally, we define an ``includeme`` function, which is a hook for use with @@ -328,7 +321,6 @@ of an incoming request to our application. .. literalinclude:: src/basiclayout/tutorial/models/__init__.py :pyobject: includeme :lineno-match: - :linenos: :language: py That's about all there is to it regarding models, views, and initialization diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst index 9f7b82d1d..5cebb943c 100644 --- a/docs/tutorials/wiki2/definingmodels.rst +++ b/docs/tutorials/wiki2/definingmodels.rst @@ -4,7 +4,7 @@ Defining the Domain Model ========================= -The first change we'll make to our stock ``pcreate``-generated application will +The first change we'll make to our stock cookiecutter-generated application will be to define a wiki page :term:`domain model`. .. note:: @@ -22,10 +22,10 @@ Declaring dependencies in our ``setup.py`` file The models code in our application will depend on a package which is not a dependency of the original "tutorial" application. The original "tutorial" -application was generated by the ``pcreate`` command; it doesn't know about our +application was generated by the cookiecutter; it doesn't know about our custom application requirements. -We need to add a dependency, the ``bcrypt`` package, to our ``tutorial`` +We need to add a dependency, the `bcrypt <https://pypi.python.org/pypi/bcrypt>`_ package, to our ``tutorial`` package's ``setup.py`` file by assigning this dependency to the ``requires`` parameter in the ``setup()`` function. @@ -38,6 +38,10 @@ Open ``tutorial/setup.py`` and edit it to look like the following: Only the highlighted line needs to be added. +.. note:: + + We are using the ``bcrypt`` package from PyPI to hash our passwords securely. There are other one-way hash algorithms for passwords if ``bcrypt`` is an issue on your system. Just make sure that it's an algorithm approved for storing passwords versus a generic one-way hash. + Running ``pip install -e .`` ============================ @@ -53,31 +57,31 @@ On UNIX: .. code-block:: bash - $ cd tutorial - $ $VENV/bin/pip install -e . + $ $VENV/bin/pip install -e . On Windows: .. code-block:: doscon - c:\pyramidtut> cd tutorial - c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e . + c:\tutorial> %VENV%\Scripts\pip install -e . Success executing this command will end with a line to the console something -like this:: +like the following. - Successfully installed bcrypt-2.0.0 cffi-1.5.2 pycparser-2.14 tutorial-0.0 +.. code-block:: text + + Successfully installed bcrypt-3.1.2 cffi-1.9.1 pycparser-2.17 tutorial Remove ``mymodel.py`` ---------------------- +===================== Let's delete the file ``tutorial/models/mymodel.py``. The ``MyModel`` class is only a sample and we're not going to use it. Add ``user.py`` ---------------- +=============== Create a new file ``tutorial/models/user.py`` with the following contents: @@ -98,12 +102,12 @@ and ``role`` (all instances of :class:`sqlalchemy.schema.Column`). These will map to columns in the ``users`` table. The ``id`` attribute will be the primary key in the table. The ``name`` attribute will be a text column, each value of which needs to be unique within the column. The ``password_hash`` is a nullable -text attribute that will contain a securely hashed password [1]_. Finally, the +text attribute that will contain a securely hashed password. Finally, the ``role`` text attribute will hold the role of the user. There are two helper methods that will help us later when using the user objects. The first is ``set_password`` which will take a raw password and -transform it using bcrypt_ into an irreversible representation, a process known +transform it using ``bcrypt`` into an irreversible representation, a process known as "hashing". The second method, ``check_password``, will allow us to compare the hashed value of the submitted password against the hashed value of the password stored in the user's record in the database. If the two hashed values @@ -116,7 +120,7 @@ authenticate as any user. Add ``page.py`` ---------------- +=============== Create a new file ``tutorial/models/page.py`` with the following contents: @@ -138,7 +142,7 @@ guaranteed that an instance of ``page`` will have a corresponding Edit ``models/__init__.py`` ---------------------------- +=========================== Since we are using a package for our models, we also need to update our ``__init__.py`` file to ensure that the models are attached to the metadata. @@ -155,12 +159,16 @@ Here we align our imports with the names of the models, ``Page`` and ``User``. Edit ``scripts/initializedb.py`` --------------------------------- +================================ We haven't looked at the details of this file yet, but within the ``scripts`` directory of your ``tutorial`` package is a file named ``initializedb.py``. Code in this file is executed whenever we run the ``initialize_tutorial_db`` -command, as we did in the installation step of this tutorial [2]_. +command, as we did in the installation step of this tutorial. + +.. note:: + + The command is named ``initialize_tutorial_db`` because of the mapping defined in the ``[console_scripts]`` entry point of our project's ``setup.py`` file. Since we've changed our model, we need to make changes to our ``initializedb.py`` script. In particular, we'll replace our import of @@ -180,7 +188,7 @@ Only the highlighted lines need to be changed. Installing the project and re-initializing the database -------------------------------------------------------- +======================================================= Because our model has changed, and in order to reinitialize the database, we need to rerun the ``initialize_tutorial_db`` command to pick up the changes @@ -191,53 +199,53 @@ Success will look something like this: .. code-block:: bash - 2016-05-22 04:12:09,226 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 - 2016-05-22 04:12:09,226 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] () - 2016-05-22 04:12:09,226 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 - 2016-05-22 04:12:09,227 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] () - 2016-05-22 04:12:09,227 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] PRAGMA table_info("users") - 2016-05-22 04:12:09,227 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () - 2016-05-22 04:12:09,228 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] PRAGMA table_info("pages") - 2016-05-22 04:12:09,228 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () - 2016-05-22 04:12:09,229 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] - CREATE TABLE users ( - id INTEGER NOT NULL, - name TEXT NOT NULL, - role TEXT NOT NULL, - password_hash TEXT, - CONSTRAINT pk_users PRIMARY KEY (id), - CONSTRAINT uq_users_name UNIQUE (name) - ) - - - 2016-05-22 04:12:09,229 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () - 2016-05-22 04:12:09,230 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT - 2016-05-22 04:12:09,230 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] - CREATE TABLE pages ( - id INTEGER NOT NULL, - name TEXT NOT NULL, - data TEXT NOT NULL, - creator_id INTEGER NOT NULL, - CONSTRAINT pk_pages PRIMARY KEY (id), - CONSTRAINT uq_pages_name UNIQUE (name), - CONSTRAINT fk_pages_creator_id_users FOREIGN KEY(creator_id) REFERENCES users (id) - ) - - - 2016-05-22 04:12:09,231 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () - 2016-05-22 04:12:09,231 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT - 2016-05-22 04:12:09,782 INFO [sqlalchemy.engine.base.Engine:646][MainThread] BEGIN (implicit) - 2016-05-22 04:12:09,783 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO users (name, role, password_hash) VALUES (?, ?, ?) - 2016-05-22 04:12:09,784 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('editor', 'editor', b'$2b$12$K/WLVKRl5fMAb6UM58ueTetXlE3rlc5cRK5zFPimK598scXBR/xWC') - 2016-05-22 04:12:09,784 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO users (name, role, password_hash) VALUES (?, ?, ?) - 2016-05-22 04:12:09,784 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('basic', 'basic', b'$2b$12$JfwLyCJGv3t.RTSmIrh3B.FKXRT9FevkAqafWdK5oq7Hl4mgAQORe') - 2016-05-22 04:12:09,785 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO pages (name, data, creator_id) VALUES (?, ?, ?) - 2016-05-22 04:12:09,785 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('FrontPage', 'This is the front page', 1) - 2016-05-22 04:12:09,786 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT + 2016-12-20 02:51:11,195 INFO [sqlalchemy.engine.base.Engine:1235][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 + 2016-12-20 02:51:11,195 INFO [sqlalchemy.engine.base.Engine:1236][MainThread] () + 2016-12-20 02:51:11,195 INFO [sqlalchemy.engine.base.Engine:1235][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 + 2016-12-20 02:51:11,195 INFO [sqlalchemy.engine.base.Engine:1236][MainThread] () + 2016-12-20 02:51:11,196 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] PRAGMA table_info("pages") + 2016-12-20 02:51:11,196 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] () + 2016-12-20 02:51:11,196 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] PRAGMA table_info("users") + 2016-12-20 02:51:11,197 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] () + 2016-12-20 02:51:11,197 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] + CREATE TABLE users ( + id INTEGER NOT NULL, + name TEXT NOT NULL, + role TEXT NOT NULL, + password_hash TEXT, + CONSTRAINT pk_users PRIMARY KEY (id), + CONSTRAINT uq_users_name UNIQUE (name) + ) + + + 2016-12-20 02:51:11,197 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] () + 2016-12-20 02:51:11,198 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT + 2016-12-20 02:51:11,199 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] + CREATE TABLE pages ( + id INTEGER NOT NULL, + name TEXT NOT NULL, + data TEXT NOT NULL, + creator_id INTEGER NOT NULL, + CONSTRAINT pk_pages PRIMARY KEY (id), + CONSTRAINT uq_pages_name UNIQUE (name), + CONSTRAINT fk_pages_creator_id_users FOREIGN KEY(creator_id) REFERENCES users (id) + ) + + + 2016-12-20 02:51:11,199 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] () + 2016-12-20 02:51:11,200 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT + 2016-12-20 02:51:11,755 INFO [sqlalchemy.engine.base.Engine:679][MainThread] BEGIN (implicit) + 2016-12-20 02:51:11,755 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] INSERT INTO users (name, role, password_hash) VALUES (?, ?, ?) + 2016-12-20 02:51:11,755 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ('editor', 'editor', '$2b$12$ds7h2Zb7.l6TEFup5h8f4ekA9GRfEpE1yQGDRvT9PConw73kKuupG') + 2016-12-20 02:51:11,756 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] INSERT INTO users (name, role, password_hash) VALUES (?, ?, ?) + 2016-12-20 02:51:11,756 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ('basic', 'basic', '$2b$12$KgruXP5Vv7rikr6dGB3TF.flGXYpiE0Li9K583EVomjY.SYmQOsyi') + 2016-12-20 02:51:11,757 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] INSERT INTO pages (name, data, creator_id) VALUES (?, ?, ?) + 2016-12-20 02:51:11,757 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ('FrontPage', 'This is the front page', 1) + 2016-12-20 02:51:11,757 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT View the application in a browser ---------------------------------- +================================= We can't. At this point, our system is in a "non-runnable" state; we'll need to change view-related files in the next chapter to be able to start the @@ -250,14 +258,3 @@ your console that ends with this exception: ImportError: cannot import name MyModel This will also happen if you attempt to run the tests. - -.. _bcrypt: https://pypi.python.org/pypi/bcrypt - -.. [1] We are using the bcrypt_ package from PyPI to hash our passwords - securely. There are other one-way hash algorithms for passwords if - bcrypt is an issue on your system. Just make sure that it's an - algorithm approved for storing passwords versus a generic one-way hash. - -.. [2] The command is named ``initialize_tutorial_db`` because of the mapping - defined in the ``[console_scripts]`` entry point of our project's - ``setup.py`` file. diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst index 996bff88c..3c343e155 100644 --- a/docs/tutorials/wiki2/definingviews.rst +++ b/docs/tutorials/wiki2/definingviews.rst @@ -42,7 +42,7 @@ installed, so re-run the ``$VENV/bin/pip install -e .`` command. Static assets -------------- +============= Our templates name static assets, including CSS and images. We don't need to create these files within our package's ``static`` directory because they @@ -133,7 +133,7 @@ The highlighted lines need to be added or edited. We added some imports, and created a regular expression to find "WikiWords". We got rid of the ``my_view`` view function and its decorator that was added -when we originally rendered the ``alchemy`` scaffold. It was only an example +when we originally rendered the ``alchemy`` cookiecutter. It was only an example and isn't relevant to our application. We also deleted the ``db_err_msg`` string. @@ -340,7 +340,7 @@ indicated by the emphasized lines: .. literalinclude:: src/views/tutorial/templates/layout.jinja2 :linenos: - :emphasize-lines: 11,35-36 + :emphasize-lines: 11,35-37 :language: html Since we're using a templating engine, we can factor common boilerplate out of @@ -350,7 +350,7 @@ template inheritance via blocks. - We have defined two placeholders in the layout template where a child template can override the content. These blocks are named ``subtitle`` (line 11) and ``content`` (line 36). -- Please refer to the Jinja2_ documentation for more information about template +- Please refer to the `Jinja2 documentation <http://jinja.pocoo.org/>`_ for more information about template inheritance. @@ -375,7 +375,7 @@ This template is used by ``view_page()`` for displaying a single wiki page. view (line 6). ``content`` contains HTML, so the ``|safe`` filter is used to prevent escaping it (e.g., changing ">" to ">"). - We create a link that points at the "edit" URL, which when clicked invokes - the ``edit_page`` view for the requested page (line 9). + the ``edit_page`` view for the requested page (lines 8-10). The ``edit.jinja2`` template @@ -436,7 +436,7 @@ There are several important things to note about this configuration: the view. Finally, we may delete the ``tutorial/templates/mytemplate.jinja2`` template -that was provided by the ``alchemy`` scaffold, as we have created our own +that was provided by the ``alchemy`` cookiecutter, as we have created our own templates for the wiki. .. note:: @@ -475,5 +475,3 @@ each of the following URLs, checking that the result is as expected: will generate a ``NoResultFound: No row was found for one()`` error. You'll see an interactive traceback facility provided by :term:`pyramid_debugtoolbar`. - -.. _jinja2: http://jinja.pocoo.org/ diff --git a/docs/tutorials/wiki2/design.rst b/docs/tutorials/wiki2/design.rst index 523a6e6d8..515aff276 100644 --- a/docs/tutorials/wiki2/design.rst +++ b/docs/tutorials/wiki2/design.rst @@ -23,22 +23,22 @@ We'll be using an SQLite database to hold our wiki data, and we'll be using Within the database, we will define two tables: -- The `users` table which will store the `id`, `name`, `password_hash` and - `role` of each wiki user. -- The `pages` table, whose elements will store the wiki pages. - There are four columns: `id`, `name`, `data` and `creator_id`. +- The ``users`` table which will store the ``id``, ``name``, ``password_hash`` and + ``role`` of each wiki user. +- The ``pages`` table, whose elements will store the wiki pages. + There are four columns: ``id``, ``name``, ``data`` and ``creator_id``. -There is a one-to-many relationship between `users` and `pages` tracking -the user who created each wiki page defined by the `creator_id` column on the -`pages` table. +There is a one-to-many relationship between ``users`` and ``pages`` tracking +the user who created each wiki page defined by the ``creator_id`` column on the +``pages`` table. -URLs like ``/PageName`` will try to find an element in the `pages` table that +URLs like ``/PageName`` will try to find an element in the ``pages`` table that has a corresponding name. To add a page to the wiki, a new row is created and the text is stored in -`data`. +``data``. -A page named ``FrontPage`` containing the text *This is the front page*, will +A page named ``FrontPage`` containing the text "This is the front page" will be created when the storage is initialized, and will be used as the wiki home page. @@ -61,12 +61,12 @@ We'll eventually be adding security to our application. To do this, we'll be using a very simple role-based security model. We'll assign a single role category to each user in our system. -`basic` - An authenticated user who can view content and create new pages. A `basic` +``basic`` + An authenticated user who can view content and create new pages. A ``basic`` user may also edit the pages they have created but not pages created by other users. -`editor` +``editor`` An authenticated user who can create and edit any content in the system. In order to accomplish this we'll need to define an authentication policy @@ -101,16 +101,12 @@ in the following table: +----------------------+-----------------------+-------------+----------------+------------+ | URL | Action | View | Template | Permission | -| | | | | | +======================+=======================+=============+================+============+ | / | Redirect to | view_wiki | | | | | /FrontPage | | | | +----------------------+-----------------------+-------------+----------------+------------+ | /PageName | Display existing | view_page | view.jinja2 | view | | | page [2]_ | [1]_ | | | -| | | | | | -| | | | | | -| | | | | | +----------------------+-----------------------+-------------+----------------+------------+ | /PageName/edit_page | Display edit form | edit_page | edit.jinja2 | edit | | | with existing | | | | @@ -149,14 +145,13 @@ in the following table: | | login form with | | | | | | "login failed" | | | | | | message. | | | | -| | | | | | +----------------------+-----------------------+-------------+----------------+------------+ | /logout | Redirect to | logout | | | | | /FrontPage | | | | +----------------------+-----------------------+-------------+----------------+------------+ .. [1] This is the default view for a Page context when there is no view name. -.. [2] Pyramid will return a default 404 Not Found page if the page *PageName* +.. [2] Pyramid will return a default 404 Not Found page if the page ``PageName`` does not exist yet. .. [3] ``pyramid.exceptions.Forbidden`` is reached when a user tries to invoke a view that is not authorized by the authorization policy. diff --git a/docs/tutorials/wiki2/distributing.rst b/docs/tutorials/wiki2/distributing.rst index f38a733f4..dd6b1b750 100644 --- a/docs/tutorials/wiki2/distributing.rst +++ b/docs/tutorials/wiki2/distributing.rst @@ -19,22 +19,22 @@ On Windows: .. code-block:: doscon - c:\pyramidtut> %VENV%\Scripts\python setup.py sdist + c:\tutorial> %VENV%\Scripts\python setup.py sdist The output of such a command will be something like: .. code-block:: text running sdist - # .. more output .. + # more output creating dist Creating tar archive removing 'tutorial-0.0' (and everything under it) -Note that this command creates a tarball in the "dist" subdirectory named +Note that this command creates a tarball in the ``dist`` subdirectory named ``tutorial-0.0.tar.gz``. You can send this file to your friends to show them your cool new application. They should be able to install it by pointing the -``easy_install`` command directly at it. Or you can upload it to `PyPI +``pip install`` command directly at it. Or you can upload it to `PyPI <https://pypi.python.org/pypi>`_ and share it with the rest of the world, where -it can be downloaded via ``easy_install`` remotely like any other package -people download from PyPI. +it can be downloaded via ``pip install`` remotely like any other package people +download from PyPI. diff --git a/docs/tutorials/wiki2/index.rst b/docs/tutorials/wiki2/index.rst index 18e9f552e..740a7f014 100644 --- a/docs/tutorials/wiki2/index.rst +++ b/docs/tutorials/wiki2/index.rst @@ -4,13 +4,11 @@ SQLAlchemy + URL dispatch wiki tutorial ======================================= This tutorial introduces an :term:`SQLAlchemy` and :term:`URL dispatch`-based -:app:`Pyramid` application to a developer familiar with Python. When the -tutorial is finished, the developer will have created a basic wiki +:app:`Pyramid` application to a developer familiar with Python. When finished, the developer will have created a basic wiki application with authentication and authorization. For cut and paste purposes, the source code for all stages of this tutorial can -be browsed on GitHub at `docs/tutorials/wiki2/src -<https://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src>`_, +be browsed on `GitHub <https://github.com/Pylons/pyramid/>`_, which corresponds to the same location if you have Pyramid sources. .. toctree:: @@ -26,4 +24,3 @@ which corresponds to the same location if you have Pyramid sources. authorization tests distributing - diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst index 0440c2d1d..c61d4360d 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -15,178 +15,137 @@ install Pyramid**. Thereby you will satisfy the following requirements. * You've satisfied the :ref:`requirements-for-installing-packages`. -Create directory to contain the project ---------------------------------------- +Install SQLite3 and its development packages +-------------------------------------------- -We need a workspace for our project files. +If you used a package manager to install your Python or if you compiled your Python from source, then you must install SQLite3 and its development packages. If you downloaded your Python as an installer from https://www.python.org, then you already have it installed and can skip this step. -On UNIX -^^^^^^^ +If you need to install the SQLite3 packages, then, for example, using the Debian system and ``apt-get``, the command would be the following: .. code-block:: bash - $ mkdir ~/pyramidtut - -On Windows -^^^^^^^^^^ + $ sudo apt-get install libsqlite3-dev -.. code-block:: doscon - c:\> mkdir pyramidtut +Install cookiecutter +-------------------- +We will use a :term:`cookiecutter` to create a Python package project from a Python package project template. See `Cookiecutter Installation <https://cookiecutter.readthedocs.io/en/latest/installation.html>`_ for instructions. -Create and use a virtual Python environment -------------------------------------------- +Generate a Pyramid project from a cookiecutter +---------------------------------------------- -Next let's create a virtual environment workspace for our project. We will use -the ``VENV`` environment variable instead of the absolute path of the virtual -environment. +We will create a Pyramid project in your home directory for UNIX or at the root for Windows. It is assumed you know the path to where you installed ``cookiecutter``. Issue the following commands and override the defaults in the prompts as follows. On UNIX ^^^^^^^ .. code-block:: bash - $ export VENV=~/pyramidtut - $ python3 -m venv $VENV + $ cd ~ + $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-alchemy On Windows ^^^^^^^^^^ .. code-block:: doscon - c:\> set VENV=c:\pyramidtut - -Each version of Python uses different paths, so you will need to adjust the -path to the command for your Python version. - -Python 2.7: - -.. code-block:: doscon - - c:\> c:\Python27\Scripts\virtualenv %VENV% + c:\> cd \ + c:\> cookiecutter https://github.com/Pylons/pyramid-cookiecutter-alchemy -Python 3.5: +On all operating systems +^^^^^^^^^^^^^^^^^^^^^^^^ +If prompted for the first item, accept the default ``yes`` by hitting return. -.. code-block:: doscon - - c:\> c:\Python35\Scripts\python -m venv %VENV% +.. code-block:: text + You've cloned ~/.cookiecutters/pyramid-cookiecutter-alchemy before. + Is it okay to delete and re-clone it? [yes]: yes + project_name [Pyramid Scaffold]: myproj + repo_name [myproj]: tutorial -Upgrade ``pip`` and ``setuptools`` in the virtual environment -------------------------------------------------------------- +Change directory into your newly created project +------------------------------------------------ On UNIX ^^^^^^^ .. code-block:: bash - $ $VENV/bin/pip install --upgrade pip setuptools + $ cd tutorial On Windows ^^^^^^^^^^ .. code-block:: doscon - c:\> %VENV%\Scripts\pip install --upgrade pip setuptools + c:\> cd tutorial -Install Pyramid into the virtual Python environment ---------------------------------------------------- +Set and use a ``VENV`` environment variable +------------------------------------------- + +We will set the ``VENV`` environment variable to the absolute path of the virtual environment, and use it going forward. On UNIX ^^^^^^^ -.. parsed-literal:: +.. code-block:: bash - $ $VENV/bin/pip install "pyramid==\ |release|\ " + $ export VENV=~/tutorial On Windows ^^^^^^^^^^ -.. parsed-literal:: - - c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ " - - -Install SQLite3 and its development packages --------------------------------------------- - -If you used a package manager to install your Python or if you compiled -your Python from source, then you must install SQLite3 and its -development packages. If you downloaded your Python as an installer -from https://www.python.org, then you already have it installed and can skip -this step. - -If you need to install the SQLite3 packages, then, for example, using -the Debian system and ``apt-get``, the command would be the following: - -.. code-block:: bash - - $ sudo apt-get install libsqlite3-dev +.. code-block:: doscon + c:\tutorial> set VENV=c:\tutorial -Change directory to your virtual Python environment ---------------------------------------------------- -Change directory to the ``pyramidtut`` directory, which is both your workspace -and your virtual environment. +Create a virtual environment +---------------------------- On UNIX ^^^^^^^ .. code-block:: bash - $ cd pyramidtut + $ python3 -m venv $VENV On Windows ^^^^^^^^^^ -.. code-block:: doscon +Each version of Python uses different paths, so you will need to adjust the path to the command for your Python version. Recent versions of the Python 3 installer for Windows now install a Python launcher. - c:\> cd pyramidtut +Python 2.7: +.. code-block:: doscon -.. _sql_making_a_project: + c:\tutorial> c:\Python27\Scripts\virtualenv %VENV% -Making a project ----------------- +Python 3.6: -Your next step is to create a project. For this tutorial we will use -the :term:`scaffold` named ``alchemy`` which generates an application -that uses :term:`SQLAlchemy` and :term:`URL dispatch`. +.. code-block:: doscon -:app:`Pyramid` supplies a variety of scaffolds to generate sample projects. We -will use ``pcreate``, a script that comes with Pyramid, to create our project -using a scaffold. + c:\tutorial> python -m venv %VENV% -By passing ``alchemy`` into the ``pcreate`` command, the script creates the -files needed to use SQLAlchemy. By passing in our application name -``tutorial``, the script inserts that application name into all the required -files. For example, ``pcreate`` creates the ``initialize_tutorial_db`` in the -``pyramidtut/bin`` directory. -The below instructions assume your current working directory is "pyramidtut". +Upgrade packaging tools in the virtual environment +-------------------------------------------------- On UNIX ^^^^^^^ .. code-block:: bash - $ $VENV/bin/pcreate -s alchemy tutorial + $ $VENV/bin/pip install --upgrade pip setuptools On Windows ^^^^^^^^^^ .. code-block:: doscon - c:\pyramidtut> %VENV%\Scripts\pcreate -s alchemy tutorial - -.. note:: If you are using Windows, the ``alchemy`` scaffold may not deal - gracefully with installation into a location that contains spaces in the - path. If you experience startup problems, try putting both the virtual - environment and the project into directories that do not contain spaces in - their paths. + c:\tutorial> %VENV%\Scripts\pip install --upgrade pip setuptools .. _installing_project_in_dev_mode: @@ -194,74 +153,49 @@ On Windows Installing the project in development mode ------------------------------------------ -In order to do development on the project easily, you must "register" the -project as a development egg in your workspace using the ``pip install -e .`` -command. In order to do so, change directory to the ``tutorial`` directory that -you created in :ref:`sql_making_a_project`, and run the ``pip install -e .`` -command using the virtual environment Python interpreter. +In order to do development on the project easily, you must "register" the project as a development egg in your workspace. We will install testing requirements at the same time. We do so with the following command. On UNIX ^^^^^^^ .. code-block:: bash - $ cd tutorial - $ $VENV/bin/pip install -e . + $ $VENV/bin/pip install -e ".[testing]" On Windows ^^^^^^^^^^ .. code-block:: doscon - c:\pyramidtut> cd tutorial - c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e . + c:\tutorial> %VENV%\Scripts\pip install -e ".[testing]" -The console will show ``pip`` checking for packages and installing missing -packages. Success executing this command will show a line like the following: +On all operating systems +^^^^^^^^^^^^^^^^^^^^^^^^ -.. code-block:: bash +The console will show ``pip`` checking for packages and installing missing packages. Success executing this command will show a line like the following: - Successfully installed Chameleon-2.24 Mako-1.0.4 MarkupSafe-0.23 \ - Pygments-2.1.3 SQLAlchemy-1.0.12 pyramid-chameleon-0.3 \ - pyramid-debugtoolbar-2.4.2 pyramid-mako-1.0.2 pyramid-tm-0.12.1 \ - transaction-1.4.4 tutorial waitress-0.8.10 zope.sqlalchemy-0.7.6 - - -.. _install-testing-requirements: +.. code-block:: bash -Install testing requirements ----------------------------- + Successfully installed Jinja2-2.8 Mako-1.0.6 MarkupSafe-0.23 \ + PasteDeploy-1.5.2 Pygments-2.1.3 SQLAlchemy-1.1.4 WebOb-1.6.3 \ + WebTest-2.0.24 beautifulsoup4-4.5.1 coverage-4.2 py-1.4.32 pyramid-1.7.3 \ + pyramid-debugtoolbar-3.0.5 pyramid-jinja2-2.7 pyramid-mako-1.0.2 \ + pyramid-tm-1.1.1 pytest-3.0.5 pytest-cov-2.4.0 repoze.lru-0.6 six-1.10.0 \ + transaction-2.0.3 translationstring-1.3 tutorial venusian-1.0 \ + waitress-1.0.1 zope.deprecation-4.2.0 zope.interface-4.3.3 \ + zope.sqlalchemy-0.7.7 -In order to run tests, we need to install the testing requirements. This is -done through our project's ``setup.py`` file, in the ``tests_require`` and -``extras_require`` stanzas, and by issuing the command below for your -operating system. +Testing requirements are defined in our project's ``setup.py`` file, in the ``tests_require`` and ``extras_require`` stanzas. .. literalinclude:: src/installation/setup.py :language: python - :linenos: - :lineno-start: 22 + :lineno-match: :lines: 22-26 .. literalinclude:: src/installation/setup.py :language: python - :linenos: - :lineno-start: 45 - :lines: 45-47 - -On UNIX -^^^^^^^ - -.. code-block:: bash - - $ $VENV/bin/pip install -e ".[testing]" - -On Windows -^^^^^^^^^^ - -.. code-block:: doscon - - c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e ".[testing]" + :lineno-match: + :lines: 46-48 .. _sql_running_tests: @@ -286,7 +220,7 @@ On Windows .. code-block:: doscon - c:\pyramidtut\tutorial> %VENV%\Scripts\py.test -q + c:\tutorial> %VENV%\Scripts\py.test -q For a successful test run, you should see output that ends like this: @@ -301,7 +235,7 @@ Expose test coverage information You can run the ``py.test`` command to see test coverage information. This runs the tests in the same way that ``py.test`` does, but provides additional -"coverage" information, exposing which lines of your project are covered by the +:term:`coverage` information, exposing which lines of your project are covered by the tests. We've already installed the ``pytest-cov`` package into our virtual @@ -319,46 +253,45 @@ On Windows .. code-block:: doscon - c:\pyramidtut\tutorial> %VENV%\Scripts\py.test --cov \ - --cov-report=term-missing + c:\tutorial> %VENV%\Scripts\py.test --cov --cov-report=term-missing If successful, you will see output something like this: .. code-block:: bash - ======================== test session starts ======================== - platform Python 3.5.1, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 - rootdir: /Users/stevepiercy/projects/pyramidtut/tutorial, inifile: - plugins: cov-2.2.1 - collected 2 items - - tutorial/tests.py .. - ------------------ coverage: platform Python 3.5.1 ------------------ - Name Stmts Miss Cover Missing - ---------------------------------------------------------------- - tutorial/__init__.py 8 6 25% 7-12 - tutorial/models/__init__.py 22 0 100% - tutorial/models/meta.py 5 0 100% - tutorial/models/mymodel.py 8 0 100% - tutorial/routes.py 3 2 33% 2-3 - tutorial/scripts/__init__.py 0 0 100% - tutorial/scripts/initializedb.py 26 16 38% 22-25, 29-45 - tutorial/views/__init__.py 0 0 100% - tutorial/views/default.py 12 0 100% - tutorial/views/notfound.py 4 2 50% 6-7 - ---------------------------------------------------------------- - TOTAL 88 26 70% - ===================== 2 passed in 0.57 seconds ====================== + ======================== test session starts ======================== + platform Python 3.6.0, pytest-3.0.5, py-1.4.31, pluggy-0.4.0 + rootdir: /Users/stevepiercy/tutorial, inifile: + plugins: cov-2.4.0 + collected 2 items + + tutorial/tests.py .. + ------------------ coverage: platform Python 3.6.0 ------------------ + Name Stmts Miss Cover Missing + ---------------------------------------------------------------- + tutorial/__init__.py 8 6 25% 7-12 + tutorial/models/__init__.py 22 0 100% + tutorial/models/meta.py 5 0 100% + tutorial/models/mymodel.py 8 0 100% + tutorial/routes.py 3 2 33% 2-3 + tutorial/scripts/__init__.py 0 0 100% + tutorial/scripts/initializedb.py 26 16 38% 22-25, 29-45 + tutorial/views/__init__.py 0 0 100% + tutorial/views/default.py 12 0 100% + tutorial/views/notfound.py 4 2 50% 6-7 + ---------------------------------------------------------------- + TOTAL 88 26 70% + ===================== 2 passed in 0.57 seconds ====================== Our package doesn't quite have 100% test coverage. -.. _test_and_coverage_scaffold_defaults_sql: +.. _test_and_coverage_cookiecutter_defaults_sql: -Test and coverage scaffold defaults ------------------------------------ +Test and coverage cookiecutter defaults +--------------------------------------- -Scaffolds include configuration defaults for ``py.test`` and test coverage. +Cookiecutters include configuration defaults for ``py.test`` and test coverage. These configuration files are ``pytest.ini`` and ``.coveragerc``, located at the root of your package. Without these defaults, we would need to specify the path to the module on which we want to run tests and coverage. @@ -375,11 +308,10 @@ On Windows .. code-block:: doscon - c:\pyramidtut\tutorial> %VENV%\Scripts\py.test --cov=tutorial \ - --cov-report=term-missing tutorial\tests.py -q + c:\tutorial> %VENV%\Scripts\py.test --cov=tutorial tutorial\tests.py -q py.test follows :ref:`conventions for Python test discovery -<pytest:test discovery>`, and the configuration defaults from the scaffold +<pytest:test discovery>`, and the configuration defaults from the cookiecutter tell ``py.test`` where to find the module on which we want to run tests and coverage. @@ -417,36 +349,36 @@ On Windows .. code-block:: doscon - c:\pyramidtut\tutorial> %VENV%\Scripts\initialize_tutorial_db development.ini + c:\tutorial> %VENV%\Scripts\initialize_tutorial_db development.ini The output to your console should be something like this: .. code-block:: bash - 2016-05-22 04:03:28,888 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 - 2016-05-22 04:03:28,888 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] () - 2016-05-22 04:03:28,888 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 - 2016-05-22 04:03:28,889 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] () - 2016-05-22 04:03:28,890 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] PRAGMA table_info("models") - 2016-05-22 04:03:28,890 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () - 2016-05-22 04:03:28,892 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] - CREATE TABLE models ( - id INTEGER NOT NULL, - name TEXT, - value INTEGER, - CONSTRAINT pk_models PRIMARY KEY (id) - ) - - - 2016-05-22 04:03:28,892 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () - 2016-05-22 04:03:28,893 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT - 2016-05-22 04:03:28,893 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] CREATE UNIQUE INDEX my_index ON models (name) - 2016-05-22 04:03:28,893 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () - 2016-05-22 04:03:28,894 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT - 2016-05-22 04:03:28,896 INFO [sqlalchemy.engine.base.Engine:646][MainThread] BEGIN (implicit) - 2016-05-22 04:03:28,897 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO models (name, value) VALUES (?, ?) - 2016-05-22 04:03:28,897 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('one', 1) - 2016-05-22 04:03:28,898 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT + 2016-12-18 21:30:08,675 INFO [sqlalchemy.engine.base.Engine:1235][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 + 2016-12-18 21:30:08,675 INFO [sqlalchemy.engine.base.Engine:1236][MainThread] () + 2016-12-18 21:30:08,676 INFO [sqlalchemy.engine.base.Engine:1235][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 + 2016-12-18 21:30:08,676 INFO [sqlalchemy.engine.base.Engine:1236][MainThread] () + 2016-12-18 21:30:08,676 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] PRAGMA table_info("models") + 2016-12-18 21:30:08,676 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] () + 2016-12-18 21:30:08,677 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] + CREATE TABLE models ( + id INTEGER NOT NULL, + name TEXT, + value INTEGER, + CONSTRAINT pk_models PRIMARY KEY (id) + ) + + + 2016-12-18 21:30:08,677 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] () + 2016-12-18 21:30:08,678 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT + 2016-12-18 21:30:08,679 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] CREATE UNIQUE INDEX my_index ON models (name) + 2016-12-18 21:30:08,679 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] () + 2016-12-18 21:30:08,679 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT + 2016-12-18 21:30:08,681 INFO [sqlalchemy.engine.base.Engine:679][MainThread] BEGIN (implicit) + 2016-12-18 21:30:08,682 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] INSERT INTO models (name, value) VALUES (?, ?) + 2016-12-18 21:30:08,682 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ('one', 1) + 2016-12-18 21:30:08,682 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT Success! You should now have a ``tutorial.sqlite`` file in your current working directory. This is an SQLite database with a single table defined in it @@ -457,7 +389,8 @@ working directory. This is an SQLite database with a single table defined in it Start the application --------------------- -Start the application. +Start the application. See :ref:`what_is_this_pserve_thing` for more +information on ``pserve``. On UNIX ^^^^^^^ @@ -471,7 +404,7 @@ On Windows .. code-block:: doscon - c:\pyramidtut\tutorial> %VENV%\Scripts\pserve development.ini --reload + c:\tutorial> %VENV%\Scripts\pserve development.ini --reload .. note:: @@ -482,9 +415,10 @@ If successful, you will see something like this on your console: .. code-block:: text - Starting subprocess with file monitor - Starting server in PID 82349. - serving on http://127.0.0.1:6543 + Starting subprocess with file monitor + Starting server in PID 44078. + Serving on http://localhost:6543 + Serving on http://localhost:6543 This means the server is ready to accept requests. @@ -501,13 +435,15 @@ page. You can read more about the purpose of the icon at application while you develop. -Decisions the ``alchemy`` scaffold has made for you ---------------------------------------------------- +Decisions the ``alchemy`` cookiecutter has made for you +------------------------------------------------------- -Creating a project using the ``alchemy`` scaffold makes the following +Creating a project using the ``alchemy`` cookiecutter makes the following assumptions: -- You are willing to use :term:`SQLAlchemy` as a database access tool. +- You are willing to use SQLite for persistent storage, although almost any SQL database could be used with SQLAlchemy. + +- You are willing to use :term:`SQLAlchemy` for a database access tool. - You are willing to use :term:`URL dispatch` to map URLs to code. diff --git a/docs/tutorials/wiki2/src/authentication/.coveragerc b/docs/tutorials/wiki2/src/authentication/.coveragerc new file mode 100644 index 000000000..a1d87d03d --- /dev/null +++ b/docs/tutorials/wiki2/src/authentication/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = tutorial +omit = tutorial/test* diff --git a/docs/tutorials/wiki2/src/authentication/CHANGES.txt b/docs/tutorials/wiki2/src/authentication/CHANGES.txt index 35a34f332..14b902fd1 100644 --- a/docs/tutorials/wiki2/src/authentication/CHANGES.txt +++ b/docs/tutorials/wiki2/src/authentication/CHANGES.txt @@ -1,4 +1,4 @@ 0.0 --- -- Initial version +- Initial version. diff --git a/docs/tutorials/wiki2/src/authentication/MANIFEST.in b/docs/tutorials/wiki2/src/authentication/MANIFEST.in index 42cd299b5..05cc195d9 100644 --- a/docs/tutorials/wiki2/src/authentication/MANIFEST.in +++ b/docs/tutorials/wiki2/src/authentication/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki2/src/authentication/README.txt b/docs/tutorials/wiki2/src/authentication/README.txt index 5b0101e5f..81102a869 100644 --- a/docs/tutorials/wiki2/src/authentication/README.txt +++ b/docs/tutorials/wiki2/src/authentication/README.txt @@ -1,14 +1,33 @@ -tutorial README -================== +myproj +====== Getting Started --------------- -- cd <directory containing this file> +- Change directory into your newly created project. -- $VENV/bin/pip install -e . + cd tutorial -- $VENV/bin/initialize_tutorial_db development.ini +- Create a Python virtual environment. -- $VENV/bin/pserve development.ini + python3 -m venv env +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools wheel + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Configure the database. + + env/bin/initialize_tutorial_db development.ini + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/tutorials/wiki2/src/authentication/development.ini b/docs/tutorials/wiki2/src/authentication/development.ini index 4a6c9325c..0786c1f66 100644 --- a/docs/tutorials/wiki2/src/authentication/development.ini +++ b/docs/tutorials/wiki2/src/authentication/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -13,7 +13,6 @@ pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar - pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite @@ -29,12 +28,11 @@ auth.secret = seekrit [server:main] use = egg:waitress#main -host = 127.0.0.1 -port = 6543 +listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki2/src/authentication/production.ini b/docs/tutorials/wiki2/src/authentication/production.ini index a13a0ca19..05d60feec 100644 --- a/docs/tutorials/wiki2/src/authentication/production.ini +++ b/docs/tutorials/wiki2/src/authentication/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -16,14 +16,17 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite auth.secret = real-seekrit +### +# wsgi server configuration +### + [server:main] use = egg:waitress#main -host = 0.0.0.0 -port = 6543 +listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki2/src/authentication/pytest.ini b/docs/tutorials/wiki2/src/authentication/pytest.ini new file mode 100644 index 000000000..8b76bc410 --- /dev/null +++ b/docs/tutorials/wiki2/src/authentication/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tutorial +python_files = *.py diff --git a/docs/tutorials/wiki2/src/authentication/setup.py b/docs/tutorials/wiki2/src/authentication/setup.py index def3ce1f6..cc1aa421c 100644 --- a/docs/tutorials/wiki2/src/authentication/setup.py +++ b/docs/tutorials/wiki2/src/authentication/setup.py @@ -19,39 +19,42 @@ requires = [ 'transaction', 'zope.sqlalchemy', 'waitress', - ] +] tests_require = [ 'WebTest >= 1.3.1', # py3 compat - 'pytest', # includes virtualenv + 'pytest', 'pytest-cov', - ] +] -setup(name='tutorial', - version='0.0', - description='tutorial', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web wsgi bfg pylons pyramid', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - extras_require={ - 'testing': tests_require, - }, - install_requires=requires, - entry_points="""\ - [paste.app_factory] - main = tutorial:main - [console_scripts] - initialize_tutorial_db = tutorial.scripts.initializedb:main - """, - ) +setup( + name='tutorial', + version='0.0', + description='myproj', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = tutorial:main', + ], + 'console_scripts': [ + 'initialize_tutorial_db = tutorial.scripts.initializedb:main', + ], + }, +) diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/tests.py b/docs/tutorials/wiki2/src/authentication/tutorial/tests.py index 99e95efd3..ce650ca7c 100644 --- a/docs/tutorials/wiki2/src/authentication/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/authentication/tutorial/tests.py @@ -54,7 +54,7 @@ class TestMyViewSuccessCondition(BaseTest): from .views.default import my_view info = my_view(dummy_request(self.session)) self.assertEqual(info['one'].name, 'one') - self.assertEqual(info['project'], 'tutorial') + self.assertEqual(info['project'], 'myproj') class TestMyViewFailureCondition(BaseTest): diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py b/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py index 1b071434c..2d058d874 100644 --- a/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py @@ -1,4 +1,4 @@ -import cgi +from pyramid.compat import escape import re from docutils.core import publish_parts @@ -32,10 +32,10 @@ def view_page(request): exists = request.dbsession.query(Page).filter_by(name=word).all() if exists: view_url = request.route_url('view_page', pagename=word) - return '<a href="%s">%s</a>' % (view_url, cgi.escape(word)) + return '<a href="%s">%s</a>' % (view_url, escape(word)) else: add_url = request.route_url('add_page', pagename=word) - return '<a href="%s">%s</a>' % (add_url, cgi.escape(word)) + return '<a href="%s">%s</a>' % (add_url, escape(word)) content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(add_link, content) diff --git a/docs/tutorials/wiki2/src/authorization/.coveragerc b/docs/tutorials/wiki2/src/authorization/.coveragerc new file mode 100644 index 000000000..a1d87d03d --- /dev/null +++ b/docs/tutorials/wiki2/src/authorization/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = tutorial +omit = tutorial/test* diff --git a/docs/tutorials/wiki2/src/authorization/CHANGES.txt b/docs/tutorials/wiki2/src/authorization/CHANGES.txt index 35a34f332..14b902fd1 100644 --- a/docs/tutorials/wiki2/src/authorization/CHANGES.txt +++ b/docs/tutorials/wiki2/src/authorization/CHANGES.txt @@ -1,4 +1,4 @@ 0.0 --- -- Initial version +- Initial version. diff --git a/docs/tutorials/wiki2/src/authorization/MANIFEST.in b/docs/tutorials/wiki2/src/authorization/MANIFEST.in index 42cd299b5..05cc195d9 100644 --- a/docs/tutorials/wiki2/src/authorization/MANIFEST.in +++ b/docs/tutorials/wiki2/src/authorization/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki2/src/authorization/README.txt b/docs/tutorials/wiki2/src/authorization/README.txt index 5b0101e5f..81102a869 100644 --- a/docs/tutorials/wiki2/src/authorization/README.txt +++ b/docs/tutorials/wiki2/src/authorization/README.txt @@ -1,14 +1,33 @@ -tutorial README -================== +myproj +====== Getting Started --------------- -- cd <directory containing this file> +- Change directory into your newly created project. -- $VENV/bin/pip install -e . + cd tutorial -- $VENV/bin/initialize_tutorial_db development.ini +- Create a Python virtual environment. -- $VENV/bin/pserve development.ini + python3 -m venv env +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools wheel + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Configure the database. + + env/bin/initialize_tutorial_db development.ini + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/tutorials/wiki2/src/authorization/development.ini b/docs/tutorials/wiki2/src/authorization/development.ini index 4a6c9325c..0786c1f66 100644 --- a/docs/tutorials/wiki2/src/authorization/development.ini +++ b/docs/tutorials/wiki2/src/authorization/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -13,7 +13,6 @@ pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar - pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite @@ -29,12 +28,11 @@ auth.secret = seekrit [server:main] use = egg:waitress#main -host = 127.0.0.1 -port = 6543 +listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki2/src/authorization/production.ini b/docs/tutorials/wiki2/src/authorization/production.ini index a13a0ca19..05d60feec 100644 --- a/docs/tutorials/wiki2/src/authorization/production.ini +++ b/docs/tutorials/wiki2/src/authorization/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -16,14 +16,17 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite auth.secret = real-seekrit +### +# wsgi server configuration +### + [server:main] use = egg:waitress#main -host = 0.0.0.0 -port = 6543 +listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki2/src/authorization/pytest.ini b/docs/tutorials/wiki2/src/authorization/pytest.ini new file mode 100644 index 000000000..8b76bc410 --- /dev/null +++ b/docs/tutorials/wiki2/src/authorization/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tutorial +python_files = *.py diff --git a/docs/tutorials/wiki2/src/authorization/setup.py b/docs/tutorials/wiki2/src/authorization/setup.py index def3ce1f6..cc1aa421c 100644 --- a/docs/tutorials/wiki2/src/authorization/setup.py +++ b/docs/tutorials/wiki2/src/authorization/setup.py @@ -19,39 +19,42 @@ requires = [ 'transaction', 'zope.sqlalchemy', 'waitress', - ] +] tests_require = [ 'WebTest >= 1.3.1', # py3 compat - 'pytest', # includes virtualenv + 'pytest', 'pytest-cov', - ] +] -setup(name='tutorial', - version='0.0', - description='tutorial', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web wsgi bfg pylons pyramid', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - extras_require={ - 'testing': tests_require, - }, - install_requires=requires, - entry_points="""\ - [paste.app_factory] - main = tutorial:main - [console_scripts] - initialize_tutorial_db = tutorial.scripts.initializedb:main - """, - ) +setup( + name='tutorial', + version='0.0', + description='myproj', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = tutorial:main', + ], + 'console_scripts': [ + 'initialize_tutorial_db = tutorial.scripts.initializedb:main', + ], + }, +) diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py index 9358993ea..65c12ed3b 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py @@ -1,4 +1,4 @@ -import cgi +from pyramid.compat import escape import re from docutils.core import publish_parts @@ -25,10 +25,10 @@ def view_page(request): exists = request.dbsession.query(Page).filter_by(name=word).all() if exists: view_url = request.route_url('view_page', pagename=word) - return '<a href="%s">%s</a>' % (view_url, cgi.escape(word)) + return '<a href="%s">%s</a>' % (view_url, escape(word)) else: add_url = request.route_url('add_page', pagename=word) - return '<a href="%s">%s</a>' % (add_url, cgi.escape(word)) + return '<a href="%s">%s</a>' % (add_url, escape(word)) content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(add_link, content) diff --git a/docs/tutorials/wiki2/src/basiclayout/.coveragerc b/docs/tutorials/wiki2/src/basiclayout/.coveragerc new file mode 100644 index 000000000..a1d87d03d --- /dev/null +++ b/docs/tutorials/wiki2/src/basiclayout/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = tutorial +omit = tutorial/test* diff --git a/docs/tutorials/wiki2/src/basiclayout/CHANGES.txt b/docs/tutorials/wiki2/src/basiclayout/CHANGES.txt index 35a34f332..14b902fd1 100644 --- a/docs/tutorials/wiki2/src/basiclayout/CHANGES.txt +++ b/docs/tutorials/wiki2/src/basiclayout/CHANGES.txt @@ -1,4 +1,4 @@ 0.0 --- -- Initial version +- Initial version. diff --git a/docs/tutorials/wiki2/src/basiclayout/MANIFEST.in b/docs/tutorials/wiki2/src/basiclayout/MANIFEST.in index 42cd299b5..05cc195d9 100644 --- a/docs/tutorials/wiki2/src/basiclayout/MANIFEST.in +++ b/docs/tutorials/wiki2/src/basiclayout/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki2/src/basiclayout/README.txt b/docs/tutorials/wiki2/src/basiclayout/README.txt index 5b0101e5f..81102a869 100644 --- a/docs/tutorials/wiki2/src/basiclayout/README.txt +++ b/docs/tutorials/wiki2/src/basiclayout/README.txt @@ -1,14 +1,33 @@ -tutorial README -================== +myproj +====== Getting Started --------------- -- cd <directory containing this file> +- Change directory into your newly created project. -- $VENV/bin/pip install -e . + cd tutorial -- $VENV/bin/initialize_tutorial_db development.ini +- Create a Python virtual environment. -- $VENV/bin/pserve development.ini + python3 -m venv env +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools wheel + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Configure the database. + + env/bin/initialize_tutorial_db development.ini + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/tutorials/wiki2/src/basiclayout/development.ini b/docs/tutorials/wiki2/src/basiclayout/development.ini index 22b733e10..be80882a5 100644 --- a/docs/tutorials/wiki2/src/basiclayout/development.ini +++ b/docs/tutorials/wiki2/src/basiclayout/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -13,7 +13,6 @@ pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar - pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite @@ -27,12 +26,11 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main -host = 127.0.0.1 -port = 6543 +listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki2/src/basiclayout/production.ini b/docs/tutorials/wiki2/src/basiclayout/production.ini index d2ecfe22a..c01ad9a7e 100644 --- a/docs/tutorials/wiki2/src/basiclayout/production.ini +++ b/docs/tutorials/wiki2/src/basiclayout/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -14,14 +14,17 @@ pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +### +# wsgi server configuration +### + [server:main] use = egg:waitress#main -host = 0.0.0.0 -port = 6543 +listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki2/src/basiclayout/pytest.ini b/docs/tutorials/wiki2/src/basiclayout/pytest.ini new file mode 100644 index 000000000..8b76bc410 --- /dev/null +++ b/docs/tutorials/wiki2/src/basiclayout/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tutorial +python_files = *.py diff --git a/docs/tutorials/wiki2/src/basiclayout/setup.py b/docs/tutorials/wiki2/src/basiclayout/setup.py index ede0a82ef..d3992a8f2 100644 --- a/docs/tutorials/wiki2/src/basiclayout/setup.py +++ b/docs/tutorials/wiki2/src/basiclayout/setup.py @@ -17,39 +17,42 @@ requires = [ 'transaction', 'zope.sqlalchemy', 'waitress', - ] +] tests_require = [ 'WebTest >= 1.3.1', # py3 compat - 'pytest', # includes virtualenv + 'pytest', 'pytest-cov', - ] +] -setup(name='tutorial', - version='0.0', - description='tutorial', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web wsgi bfg pylons pyramid', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - extras_require={ - 'testing': tests_require, - }, - install_requires=requires, - entry_points="""\ - [paste.app_factory] - main = tutorial:main - [console_scripts] - initialize_tutorial_db = tutorial.scripts.initializedb:main - """, - ) +setup( + name='tutorial', + version='0.0', + description='myproj', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = tutorial:main', + ], + 'console_scripts': [ + 'initialize_tutorial_db = tutorial.scripts.initializedb:main', + ], + }, +) diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py index 3fc82cfba..5ca037787 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py @@ -5,7 +5,7 @@ import zope.sqlalchemy # import or define all models here to ensure they are attached to the # Base.metadata prior to any initialization routines -from .mymodel import MyModel # noqa +from .mymodel import MyModel # flake8: noqa # run configure_mappers after defining all of the models to ensure # all relationships can be setup diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 index ab8c5ea3d..1f658c834 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 @@ -8,7 +8,7 @@ <meta name="author" content="Pylons Project"> <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}"> - <title>Alchemy Scaffold for The Pyramid Web Framework</title> + <title>Cookiecutter Alchemy project for the Pyramid Web Framework</title> <!-- Bootstrap core CSS --> <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> @@ -29,7 +29,7 @@ <div class="container"> <div class="row"> <div class="col-md-2"> - <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework"> + <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png') }}" alt="pyramid web framework"> </div> <div class="col-md-10"> {% block content %} @@ -40,10 +40,8 @@ <div class="row"> <div class="links"> <ul> - <li class="current-version">Generated by v1.7</li> - <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li> <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> - <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> </ul> </div> diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2 b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2 index 6b49869c4..359f55ffa 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2 +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2 @@ -2,7 +2,7 @@ {% block content %} <div class="content"> - <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1> - <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy project</span></h1> + <p class="lead">Welcome to <span class="font-normal">myproj</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p> </div> {% endblock content %} diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py index 99e95efd3..ce650ca7c 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py @@ -54,7 +54,7 @@ class TestMyViewSuccessCondition(BaseTest): from .views.default import my_view info = my_view(dummy_request(self.session)) self.assertEqual(info['one'].name, 'one') - self.assertEqual(info['project'], 'tutorial') + self.assertEqual(info['project'], 'myproj') class TestMyViewFailureCondition(BaseTest): diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/views/default.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/views/default.py index ad0c728d7..a404d4154 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/views/default.py @@ -13,7 +13,7 @@ def my_view(request): one = query.filter(MyModel.name == 'one').first() except DBAPIError: return Response(db_err_msg, content_type='text/plain', status=500) - return {'one': one, 'project': 'tutorial'} + return {'one': one, 'project': 'myproj'} db_err_msg = """\ diff --git a/docs/tutorials/wiki2/src/installation/.coveragerc b/docs/tutorials/wiki2/src/installation/.coveragerc new file mode 100644 index 000000000..a1d87d03d --- /dev/null +++ b/docs/tutorials/wiki2/src/installation/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = tutorial +omit = tutorial/test* diff --git a/docs/tutorials/wiki2/src/installation/CHANGES.txt b/docs/tutorials/wiki2/src/installation/CHANGES.txt index 35a34f332..14b902fd1 100644 --- a/docs/tutorials/wiki2/src/installation/CHANGES.txt +++ b/docs/tutorials/wiki2/src/installation/CHANGES.txt @@ -1,4 +1,4 @@ 0.0 --- -- Initial version +- Initial version. diff --git a/docs/tutorials/wiki2/src/installation/MANIFEST.in b/docs/tutorials/wiki2/src/installation/MANIFEST.in index 42cd299b5..05cc195d9 100644 --- a/docs/tutorials/wiki2/src/installation/MANIFEST.in +++ b/docs/tutorials/wiki2/src/installation/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki2/src/installation/README.txt b/docs/tutorials/wiki2/src/installation/README.txt index 5b0101e5f..81102a869 100644 --- a/docs/tutorials/wiki2/src/installation/README.txt +++ b/docs/tutorials/wiki2/src/installation/README.txt @@ -1,14 +1,33 @@ -tutorial README -================== +myproj +====== Getting Started --------------- -- cd <directory containing this file> +- Change directory into your newly created project. -- $VENV/bin/pip install -e . + cd tutorial -- $VENV/bin/initialize_tutorial_db development.ini +- Create a Python virtual environment. -- $VENV/bin/pserve development.ini + python3 -m venv env +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools wheel + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Configure the database. + + env/bin/initialize_tutorial_db development.ini + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/tutorials/wiki2/src/installation/development.ini b/docs/tutorials/wiki2/src/installation/development.ini index 22b733e10..be80882a5 100644 --- a/docs/tutorials/wiki2/src/installation/development.ini +++ b/docs/tutorials/wiki2/src/installation/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -13,7 +13,6 @@ pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar - pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite @@ -27,12 +26,11 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main -host = 127.0.0.1 -port = 6543 +listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki2/src/installation/production.ini b/docs/tutorials/wiki2/src/installation/production.ini index d2ecfe22a..c01ad9a7e 100644 --- a/docs/tutorials/wiki2/src/installation/production.ini +++ b/docs/tutorials/wiki2/src/installation/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -14,14 +14,17 @@ pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +### +# wsgi server configuration +### + [server:main] use = egg:waitress#main -host = 0.0.0.0 -port = 6543 +listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki2/src/installation/pytest.ini b/docs/tutorials/wiki2/src/installation/pytest.ini new file mode 100644 index 000000000..8b76bc410 --- /dev/null +++ b/docs/tutorials/wiki2/src/installation/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tutorial +python_files = *.py diff --git a/docs/tutorials/wiki2/src/installation/setup.py b/docs/tutorials/wiki2/src/installation/setup.py index ede0a82ef..d3992a8f2 100644 --- a/docs/tutorials/wiki2/src/installation/setup.py +++ b/docs/tutorials/wiki2/src/installation/setup.py @@ -17,39 +17,42 @@ requires = [ 'transaction', 'zope.sqlalchemy', 'waitress', - ] +] tests_require = [ 'WebTest >= 1.3.1', # py3 compat - 'pytest', # includes virtualenv + 'pytest', 'pytest-cov', - ] +] -setup(name='tutorial', - version='0.0', - description='tutorial', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web wsgi bfg pylons pyramid', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - extras_require={ - 'testing': tests_require, - }, - install_requires=requires, - entry_points="""\ - [paste.app_factory] - main = tutorial:main - [console_scripts] - initialize_tutorial_db = tutorial.scripts.initializedb:main - """, - ) +setup( + name='tutorial', + version='0.0', + description='myproj', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = tutorial:main', + ], + 'console_scripts': [ + 'initialize_tutorial_db = tutorial.scripts.initializedb:main', + ], + }, +) diff --git a/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py index 3fc82cfba..5ca037787 100644 --- a/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py @@ -5,7 +5,7 @@ import zope.sqlalchemy # import or define all models here to ensure they are attached to the # Base.metadata prior to any initialization routines -from .mymodel import MyModel # noqa +from .mymodel import MyModel # flake8: noqa # run configure_mappers after defining all of the models to ensure # all relationships can be setup diff --git a/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 index ab8c5ea3d..1f658c834 100644 --- a/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 +++ b/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 @@ -8,7 +8,7 @@ <meta name="author" content="Pylons Project"> <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}"> - <title>Alchemy Scaffold for The Pyramid Web Framework</title> + <title>Cookiecutter Alchemy project for the Pyramid Web Framework</title> <!-- Bootstrap core CSS --> <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> @@ -29,7 +29,7 @@ <div class="container"> <div class="row"> <div class="col-md-2"> - <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework"> + <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png') }}" alt="pyramid web framework"> </div> <div class="col-md-10"> {% block content %} @@ -40,10 +40,8 @@ <div class="row"> <div class="links"> <ul> - <li class="current-version">Generated by v1.7</li> - <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li> <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> - <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> </ul> </div> diff --git a/docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja2 b/docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja2 index 6b49869c4..359f55ffa 100644 --- a/docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja2 +++ b/docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja2 @@ -2,7 +2,7 @@ {% block content %} <div class="content"> - <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1> - <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy project</span></h1> + <p class="lead">Welcome to <span class="font-normal">myproj</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p> </div> {% endblock content %} diff --git a/docs/tutorials/wiki2/src/installation/tutorial/tests.py b/docs/tutorials/wiki2/src/installation/tutorial/tests.py index 99e95efd3..ce650ca7c 100644 --- a/docs/tutorials/wiki2/src/installation/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/installation/tutorial/tests.py @@ -54,7 +54,7 @@ class TestMyViewSuccessCondition(BaseTest): from .views.default import my_view info = my_view(dummy_request(self.session)) self.assertEqual(info['one'].name, 'one') - self.assertEqual(info['project'], 'tutorial') + self.assertEqual(info['project'], 'myproj') class TestMyViewFailureCondition(BaseTest): diff --git a/docs/tutorials/wiki2/src/installation/tutorial/views/default.py b/docs/tutorials/wiki2/src/installation/tutorial/views/default.py index ad0c728d7..a404d4154 100644 --- a/docs/tutorials/wiki2/src/installation/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/installation/tutorial/views/default.py @@ -13,7 +13,7 @@ def my_view(request): one = query.filter(MyModel.name == 'one').first() except DBAPIError: return Response(db_err_msg, content_type='text/plain', status=500) - return {'one': one, 'project': 'tutorial'} + return {'one': one, 'project': 'myproj'} db_err_msg = """\ diff --git a/docs/tutorials/wiki2/src/models/.coveragerc b/docs/tutorials/wiki2/src/models/.coveragerc new file mode 100644 index 000000000..a1d87d03d --- /dev/null +++ b/docs/tutorials/wiki2/src/models/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = tutorial +omit = tutorial/test* diff --git a/docs/tutorials/wiki2/src/models/CHANGES.txt b/docs/tutorials/wiki2/src/models/CHANGES.txt index 35a34f332..14b902fd1 100644 --- a/docs/tutorials/wiki2/src/models/CHANGES.txt +++ b/docs/tutorials/wiki2/src/models/CHANGES.txt @@ -1,4 +1,4 @@ 0.0 --- -- Initial version +- Initial version. diff --git a/docs/tutorials/wiki2/src/models/MANIFEST.in b/docs/tutorials/wiki2/src/models/MANIFEST.in index 42cd299b5..05cc195d9 100644 --- a/docs/tutorials/wiki2/src/models/MANIFEST.in +++ b/docs/tutorials/wiki2/src/models/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki2/src/models/README.txt b/docs/tutorials/wiki2/src/models/README.txt index 5b0101e5f..81102a869 100644 --- a/docs/tutorials/wiki2/src/models/README.txt +++ b/docs/tutorials/wiki2/src/models/README.txt @@ -1,14 +1,33 @@ -tutorial README -================== +myproj +====== Getting Started --------------- -- cd <directory containing this file> +- Change directory into your newly created project. -- $VENV/bin/pip install -e . + cd tutorial -- $VENV/bin/initialize_tutorial_db development.ini +- Create a Python virtual environment. -- $VENV/bin/pserve development.ini + python3 -m venv env +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools wheel + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Configure the database. + + env/bin/initialize_tutorial_db development.ini + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/tutorials/wiki2/src/models/development.ini b/docs/tutorials/wiki2/src/models/development.ini index 22b733e10..be80882a5 100644 --- a/docs/tutorials/wiki2/src/models/development.ini +++ b/docs/tutorials/wiki2/src/models/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -13,7 +13,6 @@ pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar - pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite @@ -27,12 +26,11 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main -host = 127.0.0.1 -port = 6543 +listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki2/src/models/production.ini b/docs/tutorials/wiki2/src/models/production.ini index d2ecfe22a..c01ad9a7e 100644 --- a/docs/tutorials/wiki2/src/models/production.ini +++ b/docs/tutorials/wiki2/src/models/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -14,14 +14,17 @@ pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +### +# wsgi server configuration +### + [server:main] use = egg:waitress#main -host = 0.0.0.0 -port = 6543 +listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki2/src/models/pytest.ini b/docs/tutorials/wiki2/src/models/pytest.ini new file mode 100644 index 000000000..8b76bc410 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tutorial +python_files = *.py diff --git a/docs/tutorials/wiki2/src/models/setup.py b/docs/tutorials/wiki2/src/models/setup.py index 742a7c59c..faf76aa27 100644 --- a/docs/tutorials/wiki2/src/models/setup.py +++ b/docs/tutorials/wiki2/src/models/setup.py @@ -18,39 +18,42 @@ requires = [ 'transaction', 'zope.sqlalchemy', 'waitress', - ] +] tests_require = [ 'WebTest >= 1.3.1', # py3 compat - 'pytest', # includes virtualenv + 'pytest', 'pytest-cov', - ] +] -setup(name='tutorial', - version='0.0', - description='tutorial', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web wsgi bfg pylons pyramid', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - extras_require={ - 'testing': tests_require, - }, - install_requires=requires, - entry_points="""\ - [paste.app_factory] - main = tutorial:main - [console_scripts] - initialize_tutorial_db = tutorial.scripts.initializedb:main - """, - ) +setup( + name='tutorial', + version='0.0', + description='myproj', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = tutorial:main', + ], + 'console_scripts': [ + 'initialize_tutorial_db = tutorial.scripts.initializedb:main', + ], + }, +) diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 index ab8c5ea3d..1f658c834 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 +++ b/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 @@ -8,7 +8,7 @@ <meta name="author" content="Pylons Project"> <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}"> - <title>Alchemy Scaffold for The Pyramid Web Framework</title> + <title>Cookiecutter Alchemy project for the Pyramid Web Framework</title> <!-- Bootstrap core CSS --> <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> @@ -29,7 +29,7 @@ <div class="container"> <div class="row"> <div class="col-md-2"> - <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework"> + <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png') }}" alt="pyramid web framework"> </div> <div class="col-md-10"> {% block content %} @@ -40,10 +40,8 @@ <div class="row"> <div class="links"> <ul> - <li class="current-version">Generated by v1.7</li> - <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li> <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> - <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> </ul> </div> diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2 b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2 index 6b49869c4..359f55ffa 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2 +++ b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2 @@ -2,7 +2,7 @@ {% block content %} <div class="content"> - <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1> - <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy project</span></h1> + <p class="lead">Welcome to <span class="font-normal">myproj</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p> </div> {% endblock content %} diff --git a/docs/tutorials/wiki2/src/models/tutorial/tests.py b/docs/tutorials/wiki2/src/models/tutorial/tests.py index 99e95efd3..ce650ca7c 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/models/tutorial/tests.py @@ -54,7 +54,7 @@ class TestMyViewSuccessCondition(BaseTest): from .views.default import my_view info = my_view(dummy_request(self.session)) self.assertEqual(info['one'].name, 'one') - self.assertEqual(info['project'], 'tutorial') + self.assertEqual(info['project'], 'myproj') class TestMyViewFailureCondition(BaseTest): diff --git a/docs/tutorials/wiki2/src/models/tutorial/views/default.py b/docs/tutorials/wiki2/src/models/tutorial/views/default.py index ad0c728d7..a404d4154 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/models/tutorial/views/default.py @@ -13,7 +13,7 @@ def my_view(request): one = query.filter(MyModel.name == 'one').first() except DBAPIError: return Response(db_err_msg, content_type='text/plain', status=500) - return {'one': one, 'project': 'tutorial'} + return {'one': one, 'project': 'myproj'} db_err_msg = """\ diff --git a/docs/tutorials/wiki2/src/tests/.coveragerc b/docs/tutorials/wiki2/src/tests/.coveragerc new file mode 100644 index 000000000..a1d87d03d --- /dev/null +++ b/docs/tutorials/wiki2/src/tests/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = tutorial +omit = tutorial/test* diff --git a/docs/tutorials/wiki2/src/tests/CHANGES.txt b/docs/tutorials/wiki2/src/tests/CHANGES.txt index 35a34f332..14b902fd1 100644 --- a/docs/tutorials/wiki2/src/tests/CHANGES.txt +++ b/docs/tutorials/wiki2/src/tests/CHANGES.txt @@ -1,4 +1,4 @@ 0.0 --- -- Initial version +- Initial version. diff --git a/docs/tutorials/wiki2/src/tests/MANIFEST.in b/docs/tutorials/wiki2/src/tests/MANIFEST.in index 42cd299b5..05cc195d9 100644 --- a/docs/tutorials/wiki2/src/tests/MANIFEST.in +++ b/docs/tutorials/wiki2/src/tests/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki2/src/tests/README.txt b/docs/tutorials/wiki2/src/tests/README.txt index 5b0101e5f..81102a869 100644 --- a/docs/tutorials/wiki2/src/tests/README.txt +++ b/docs/tutorials/wiki2/src/tests/README.txt @@ -1,14 +1,33 @@ -tutorial README -================== +myproj +====== Getting Started --------------- -- cd <directory containing this file> +- Change directory into your newly created project. -- $VENV/bin/pip install -e . + cd tutorial -- $VENV/bin/initialize_tutorial_db development.ini +- Create a Python virtual environment. -- $VENV/bin/pserve development.ini + python3 -m venv env +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools wheel + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Configure the database. + + env/bin/initialize_tutorial_db development.ini + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/tutorials/wiki2/src/tests/development.ini b/docs/tutorials/wiki2/src/tests/development.ini index 4a6c9325c..0786c1f66 100644 --- a/docs/tutorials/wiki2/src/tests/development.ini +++ b/docs/tutorials/wiki2/src/tests/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -13,7 +13,6 @@ pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar - pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite @@ -29,12 +28,11 @@ auth.secret = seekrit [server:main] use = egg:waitress#main -host = 127.0.0.1 -port = 6543 +listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki2/src/tests/production.ini b/docs/tutorials/wiki2/src/tests/production.ini index a13a0ca19..05d60feec 100644 --- a/docs/tutorials/wiki2/src/tests/production.ini +++ b/docs/tutorials/wiki2/src/tests/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -16,14 +16,17 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite auth.secret = real-seekrit +### +# wsgi server configuration +### + [server:main] use = egg:waitress#main -host = 0.0.0.0 -port = 6543 +listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki2/src/tests/pytest.ini b/docs/tutorials/wiki2/src/tests/pytest.ini new file mode 100644 index 000000000..8b76bc410 --- /dev/null +++ b/docs/tutorials/wiki2/src/tests/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tutorial +python_files = *.py diff --git a/docs/tutorials/wiki2/src/tests/setup.py b/docs/tutorials/wiki2/src/tests/setup.py index def3ce1f6..cc1aa421c 100644 --- a/docs/tutorials/wiki2/src/tests/setup.py +++ b/docs/tutorials/wiki2/src/tests/setup.py @@ -19,39 +19,42 @@ requires = [ 'transaction', 'zope.sqlalchemy', 'waitress', - ] +] tests_require = [ 'WebTest >= 1.3.1', # py3 compat - 'pytest', # includes virtualenv + 'pytest', 'pytest-cov', - ] +] -setup(name='tutorial', - version='0.0', - description='tutorial', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web wsgi bfg pylons pyramid', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - extras_require={ - 'testing': tests_require, - }, - install_requires=requires, - entry_points="""\ - [paste.app_factory] - main = tutorial:main - [console_scripts] - initialize_tutorial_db = tutorial.scripts.initializedb:main - """, - ) +setup( + name='tutorial', + version='0.0', + description='myproj', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = tutorial:main', + ], + 'console_scripts': [ + 'initialize_tutorial_db = tutorial.scripts.initializedb:main', + ], + }, +) diff --git a/docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py b/docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py index f3c0a6fef..c860ef8cf 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py @@ -28,6 +28,7 @@ def usage(argv): def main(argv=sys.argv): if len(argv) < 2: usage(argv) + return config_uri = argv[1] options = parse_vars(argv[2:]) setup_logging(config_uri) diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py index 715768b2e..0250e71c9 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py @@ -11,6 +11,9 @@ class FunctionalTests(unittest.TestCase): basic_wrong_login = ( '/login?login=basic&password=incorrect' '&next=FrontPage&form.submitted=Login') + basic_login_no_next = ( + '/login?login=basic&password=basic' + '&form.submitted=Login') editor_login = ( '/login?login=editor&password=editor' '&next=FrontPage&form.submitted=Login') @@ -68,6 +71,10 @@ class FunctionalTests(unittest.TestCase): res = self.testapp.get(self.basic_login, status=302) self.assertEqual(res.location, 'http://localhost/FrontPage') + def test_successful_log_in_no_next(self): + res = self.testapp.get(self.basic_login_no_next, status=302) + self.assertEqual(res.location, 'http://localhost/') + def test_failed_log_in(self): res = self.testapp.get(self.basic_wrong_login, status=200) self.assertTrue(b'login' in res.body) @@ -120,3 +127,8 @@ class FunctionalTests(unittest.TestCase): self.testapp.get(self.editor_login, status=302) res = self.testapp.get('/FrontPage', status=200) self.assertTrue(b'FrontPage' in res.body) + + def test_redirect_to_edit_for_existing_page(self): + self.testapp.get(self.editor_login, status=302) + res = self.testapp.get('/add_page/FrontPage', status=302) + self.assertTrue(b'FrontPage' in res.body) diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_initdb.py b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_initdb.py new file mode 100644 index 000000000..f5273456e --- /dev/null +++ b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_initdb.py @@ -0,0 +1,16 @@ +import os +import unittest + + +class TestInitializeDB(unittest.TestCase): + + def test_usage(self): + from ..scripts.initializedb import main + with self.assertRaises(SystemExit): + main(argv=['foo']) + + def test_run(self): + from ..scripts.initializedb import main + main(argv=['foo', 'development.ini']) + self.assertTrue(os.path.exists('tutorial.sqlite')) + os.remove('tutorial.sqlite') diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_security.py b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_security.py new file mode 100644 index 000000000..cbec6420d --- /dev/null +++ b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_security.py @@ -0,0 +1,23 @@ +import unittest +from pyramid.testing import DummyRequest + + +class TestMyAuthenticationPolicy(unittest.TestCase): + + def test_no_user(self): + request = DummyRequest() + request.user = None + + from ..security import MyAuthenticationPolicy + policy = MyAuthenticationPolicy(None) + self.assertEqual(policy.authenticated_userid(request), None) + + def test_authenticated_user(self): + from ..models import User + request = DummyRequest() + request.user = User() + request.user.id = 'foo' + + from ..security import MyAuthenticationPolicy + policy = MyAuthenticationPolicy(None) + self.assertEqual(policy.authenticated_userid(request), 'foo') diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_user_model.py b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_user_model.py new file mode 100644 index 000000000..9490ac990 --- /dev/null +++ b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_user_model.py @@ -0,0 +1,67 @@ +import unittest +import transaction + +from pyramid import testing + + +class BaseTest(unittest.TestCase): + + def setUp(self): + from ..models import get_tm_session + self.config = testing.setUp(settings={ + 'sqlalchemy.url': 'sqlite:///:memory:' + }) + self.config.include('..models') + self.config.include('..routes') + + session_factory = self.config.registry['dbsession_factory'] + self.session = get_tm_session(session_factory, transaction.manager) + + self.init_database() + + def init_database(self): + from ..models.meta import Base + session_factory = self.config.registry['dbsession_factory'] + engine = session_factory.kw['bind'] + Base.metadata.create_all(engine) + + def tearDown(self): + testing.tearDown() + transaction.abort() + + def makeUser(self, name, role): + from ..models import User + return User(name=name, role=role) + + +class TestSetPassword(BaseTest): + + def test_password_hash_saved(self): + user = self.makeUser(name='foo', role='bar') + self.assertFalse(user.password_hash) + + user.set_password('secret') + self.assertTrue(user.password_hash) + + +class TestCheckPassword(BaseTest): + + def test_password_hash_not_set(self): + user = self.makeUser(name='foo', role='bar') + self.assertFalse(user.password_hash) + + self.assertFalse(user.check_password('secret')) + + def test_correct_password(self): + user = self.makeUser(name='foo', role='bar') + user.set_password('secret') + self.assertTrue(user.password_hash) + + self.assertTrue(user.check_password('secret')) + + def test_incorrect_password(self): + user = self.makeUser(name='foo', role='bar') + user.set_password('secret') + self.assertTrue(user.password_hash) + + self.assertFalse(user.check_password('incorrect')) diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views/default.py b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py index 9358993ea..65c12ed3b 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py @@ -1,4 +1,4 @@ -import cgi +from pyramid.compat import escape import re from docutils.core import publish_parts @@ -25,10 +25,10 @@ def view_page(request): exists = request.dbsession.query(Page).filter_by(name=word).all() if exists: view_url = request.route_url('view_page', pagename=word) - return '<a href="%s">%s</a>' % (view_url, cgi.escape(word)) + return '<a href="%s">%s</a>' % (view_url, escape(word)) else: add_url = request.route_url('add_page', pagename=word) - return '<a href="%s">%s</a>' % (add_url, cgi.escape(word)) + return '<a href="%s">%s</a>' % (add_url, escape(word)) content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(add_link, content) diff --git a/docs/tutorials/wiki2/src/views/.coveragerc b/docs/tutorials/wiki2/src/views/.coveragerc new file mode 100644 index 000000000..a1d87d03d --- /dev/null +++ b/docs/tutorials/wiki2/src/views/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = tutorial +omit = tutorial/test* diff --git a/docs/tutorials/wiki2/src/views/CHANGES.txt b/docs/tutorials/wiki2/src/views/CHANGES.txt index 35a34f332..14b902fd1 100644 --- a/docs/tutorials/wiki2/src/views/CHANGES.txt +++ b/docs/tutorials/wiki2/src/views/CHANGES.txt @@ -1,4 +1,4 @@ 0.0 --- -- Initial version +- Initial version. diff --git a/docs/tutorials/wiki2/src/views/MANIFEST.in b/docs/tutorials/wiki2/src/views/MANIFEST.in index 42cd299b5..05cc195d9 100644 --- a/docs/tutorials/wiki2/src/views/MANIFEST.in +++ b/docs/tutorials/wiki2/src/views/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki2/src/views/README.txt b/docs/tutorials/wiki2/src/views/README.txt index 5b0101e5f..81102a869 100644 --- a/docs/tutorials/wiki2/src/views/README.txt +++ b/docs/tutorials/wiki2/src/views/README.txt @@ -1,14 +1,33 @@ -tutorial README -================== +myproj +====== Getting Started --------------- -- cd <directory containing this file> +- Change directory into your newly created project. -- $VENV/bin/pip install -e . + cd tutorial -- $VENV/bin/initialize_tutorial_db development.ini +- Create a Python virtual environment. -- $VENV/bin/pserve development.ini + python3 -m venv env +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools wheel + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Configure the database. + + env/bin/initialize_tutorial_db development.ini + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/docs/tutorials/wiki2/src/views/development.ini b/docs/tutorials/wiki2/src/views/development.ini index 22b733e10..be80882a5 100644 --- a/docs/tutorials/wiki2/src/views/development.ini +++ b/docs/tutorials/wiki2/src/views/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -13,7 +13,6 @@ pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar - pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite @@ -27,12 +26,11 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main -host = 127.0.0.1 -port = 6543 +listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki2/src/views/production.ini b/docs/tutorials/wiki2/src/views/production.ini index d2ecfe22a..c01ad9a7e 100644 --- a/docs/tutorials/wiki2/src/views/production.ini +++ b/docs/tutorials/wiki2/src/views/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -14,14 +14,17 @@ pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +### +# wsgi server configuration +### + [server:main] use = egg:waitress#main -host = 0.0.0.0 -port = 6543 +listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki2/src/views/pytest.ini b/docs/tutorials/wiki2/src/views/pytest.ini new file mode 100644 index 000000000..8b76bc410 --- /dev/null +++ b/docs/tutorials/wiki2/src/views/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tutorial +python_files = *.py diff --git a/docs/tutorials/wiki2/src/views/setup.py b/docs/tutorials/wiki2/src/views/setup.py index def3ce1f6..cc1aa421c 100644 --- a/docs/tutorials/wiki2/src/views/setup.py +++ b/docs/tutorials/wiki2/src/views/setup.py @@ -19,39 +19,42 @@ requires = [ 'transaction', 'zope.sqlalchemy', 'waitress', - ] +] tests_require = [ 'WebTest >= 1.3.1', # py3 compat - 'pytest', # includes virtualenv + 'pytest', 'pytest-cov', - ] +] -setup(name='tutorial', - version='0.0', - description='tutorial', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Programming Language :: Python", - "Framework :: Pyramid", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web wsgi bfg pylons pyramid', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - extras_require={ - 'testing': tests_require, - }, - install_requires=requires, - entry_points="""\ - [paste.app_factory] - main = tutorial:main - [console_scripts] - initialize_tutorial_db = tutorial.scripts.initializedb:main - """, - ) +setup( + name='tutorial', + version='0.0', + description='myproj', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + 'Framework :: Pyramid', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = tutorial:main', + ], + 'console_scripts': [ + 'initialize_tutorial_db = tutorial.scripts.initializedb:main', + ], + }, +) diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2 index 71785157f..7575de8a7 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2 +++ b/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2 @@ -29,7 +29,7 @@ <div class="container"> <div class="row"> <div class="col-md-2"> - <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework"> + <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png') }}" alt="pyramid web framework"> </div> <div class="col-md-10"> <div class="content"> diff --git a/docs/tutorials/wiki2/src/views/tutorial/tests.py b/docs/tutorials/wiki2/src/views/tutorial/tests.py index 99e95efd3..ce650ca7c 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/views/tutorial/tests.py @@ -54,7 +54,7 @@ class TestMyViewSuccessCondition(BaseTest): from .views.default import my_view info = my_view(dummy_request(self.session)) self.assertEqual(info['one'].name, 'one') - self.assertEqual(info['project'], 'tutorial') + self.assertEqual(info['project'], 'myproj') class TestMyViewFailureCondition(BaseTest): diff --git a/docs/tutorials/wiki2/src/views/tutorial/views/default.py b/docs/tutorials/wiki2/src/views/tutorial/views/default.py index bb6300b75..3b95e0f59 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/views/tutorial/views/default.py @@ -1,4 +1,4 @@ -import cgi +from pyramid.compat import escape import re from docutils.core import publish_parts @@ -31,10 +31,10 @@ def view_page(request): exists = request.dbsession.query(Page).filter_by(name=word).all() if exists: view_url = request.route_url('view_page', pagename=word) - return '<a href="%s">%s</a>' % (view_url, cgi.escape(word)) + return '<a href="%s">%s</a>' % (view_url, escape(word)) else: add_url = request.route_url('add_page', pagename=word) - return '<a href="%s">%s</a>' % (add_url, cgi.escape(word)) + return '<a href="%s">%s</a>' % (add_url, escape(word)) content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(add_link, content) diff --git a/docs/tutorials/wiki2/tests.rst b/docs/tutorials/wiki2/tests.rst index e923ff9cb..e1f83a755 100644 --- a/docs/tutorials/wiki2/tests.rst +++ b/docs/tutorials/wiki2/tests.rst @@ -8,7 +8,7 @@ We will now add tests for the models and views as well as a few functional tests in a new ``tests`` subpackage. Tests ensure that an application works, and that it continues to work when changes are made in the future. -The file ``tests.py`` was generated as part of the ``alchemy`` scaffold, but it +The file ``tests.py`` was generated as part of the ``alchemy`` cookiecutter, but it is a common practice to put tests into a ``tests`` subpackage, especially as projects grow in size and complexity. Each module in the test subpackage should contain tests for its corresponding module in our application. Each @@ -50,20 +50,36 @@ so on. View the results of all our edits to ``tests`` subpackage ========================================================= -Open ``tutorial/tests/test_views.py``, and edit it such that it appears as -follows: +Create ``tutorial/tests/test_views.py`` such that it appears as follows: .. literalinclude:: src/tests/tutorial/tests/test_views.py :linenos: :language: python -Open ``tutorial/tests/test_functional.py``, and edit it such that it appears as -follows: +Create ``tutorial/tests/test_functional.py`` such that it appears as follows: .. literalinclude:: src/tests/tutorial/tests/test_functional.py :linenos: :language: python +Create ``tutorial/tests/test_initdb.py`` such that it appears as follows: + +.. literalinclude:: src/tests/tutorial/tests/test_initdb.py + :linenos: + :language: python + +Create ``tutorial/tests/test_security.py`` such that it appears as follows: + +.. literalinclude:: src/tests/tutorial/tests/test_security.py + :linenos: + :language: python + +Create ``tutorial/tests/test_user_model.py`` such that it appears as follows: + +.. literalinclude:: src/tests/tutorial/tests/test_user_model.py + :linenos: + :language: python + .. note:: @@ -77,32 +93,27 @@ follows: Running the tests ================= -We can run these tests similarly to how we did in :ref:`running_tests`: +We can run these tests similarly to how we did in :ref:`running_tests`, but first delete the SQLite database ``tutorial.sqlite``. If you do not delete the database, then you will see an integrity error when running the tests. On UNIX: .. code-block:: bash + $ rm tutorial.sqlite $ $VENV/bin/py.test -q On Windows: .. code-block:: doscon - c:\pyramidtut\tutorial> %VENV%\Scripts\py.test -q + c:\tutorial> del tutorial.sqlite + c:\tutorial> %VENV%\Scripts\py.test -q The expected result should look like the following: .. code-block:: text - ...................... - 22 passed, 1 pytest-warnings in 5.81 seconds - -.. note:: If you use Python 3 during this tutorial, you will see deprecation - warnings in the output, which we will choose to ignore. In making this - tutorial run on both Python 2 and 3, the authors prioritized simplicity and - focus for the learner over accommodating warnings. In your own app or as - extra credit, you may choose to either drop Python 2 support or hack your - code to work without warnings on both Python 2 and 3. + ................................ + 32 passed in 9.90 seconds .. _webtest: http://docs.pylonsproject.org/projects/webtest/en/latest/ diff --git a/docs/typographical-conventions.rst b/docs/typographical-conventions.rst new file mode 100644 index 000000000..5efc49682 --- /dev/null +++ b/docs/typographical-conventions.rst @@ -0,0 +1,338 @@ +.. _typographical-conventions: + +Typographical Conventions +========================= + +.. meta:: + :description: This chapter describes typographical conventions used in the Pyramid documentation. + :keywords: Pyramid, Typographical Conventions + + +.. _typographical-conventions-introduction: + +Introduction +------------ + +This chapter describes typographical conventions used in the Pyramid documentation. + + +.. _typographical-conventions-glossary: + +Glossary +-------- + +A glossary defines terms used throughout the documentation. References to glossary terms appear as follows. + +:term:`request` + +Note it is hyperlinked, and when clicked it will take the user to the term in the Glossary and highlight the term. + + +.. _typographical-conventions-links: + +Links +----- + +Links are presented as follows, and may be clickable. + +`TryPyramid <https://TryPyramid.com>`_ + +.. seealso:: See also :ref:`typographical-conventions-cross-references` for other links within the documentation. + + +.. _typographical-conventions-topic: + +Topic +----- + +A topic is similar to a block quote with a title, or a self-contained section with no subsections. A topic indicates a self-contained idea that is separate from the flow of the document. Topics may occur anywhere a section or transition may occur. + +.. topic:: Topic Title + + Subsequent indented lines comprise + the body of the topic, and are + interpreted as body elements. + + +.. _typographical-conventions-displaying-code: + +Code +---- + +Code may be displayed in blocks or inline. Blocks of code may use syntax highlighting, line numbering, and emphasis. + + +.. _typographical-conventions-syntax-highlighting: + +Syntax highlighting +^^^^^^^^^^^^^^^^^^^ + +XML: + +.. code-block:: xml + + <somesnippet>Some XML</somesnippet> + +Unix shell commands are prefixed with a ``$`` character. (See :term:`venv` for the meaning of ``$VENV``.) + +.. code-block:: bash + + $ $VENV/bin/pip install -e . + +Windows commands are prefixed with a drive letter with an optional directory name. (See :term:`venv` for the meaning of ``%VENV%``.) + +.. code-block:: doscon + + c:\> %VENV%\Scripts\pserve development.ini + +cfg: + +.. code-block:: cfg + + [some-part] + # A random part in the buildout + recipe = collective.recipe.foo + option = value + +ini: + +.. code-block:: ini + + [nosetests] + match=^test + where=pyramid + nocapture=1 + +Interactive Python: + +.. code-block:: pycon + + >>> class Foo: + ... bar = 100 + ... + >>> f = Foo() + >>> f.bar + 100 + >>> f.bar / 0 + Traceback (most recent call last): + File "<stdin>", line 1, in <module> + ZeroDivisionError: integer division or modulo by zero + + +.. _typographical-conventions-long-commands: + +Displaying long commands +^^^^^^^^^^^^^^^^^^^^^^^^ + +When a command that should be typed on one line is too long to fit on the displayed width of a page, the backslash character ``\`` is used to indicate that the subsequent printed line should be part of the command: + +.. code-block:: bash + + $ $VENV/bin/py.test tutorial/tests.py --cov-report term-missing \ + --cov=tutorial -q + + +.. _typographical-conventions-code-block-options: + +Code block options +^^^^^^^^^^^^^^^^^^ + +To emphasize lines, we give the appearance that a highlighting pen has been used on the code. + +.. code-block:: python + :emphasize-lines: 1,3 + + if "foo" == "bar": + # This is Python code + pass + +A code block with line numbers. + +.. code-block:: python + :linenos: + + if "foo" == "bar": + # This is Python code + pass + +Some code blocks may be given a caption. + +.. code-block:: python + :caption: sample.py + :name: sample-py-typographical-conventions + + if "foo" == "bar": + # This is Python code + pass + + +.. _typographical-conventions-inline-code: + +Inline code +^^^^^^^^^^^ + +Inline code is displayed as follows, where the inline code is 'pip install -e ".[docs]"'. + +Install requirements for building documentation: ``pip install -e ".[docs]"`` + + +.. _typographical-conventions-feature-versioning: + +Feature versioning +------------------ + +We designate the version in which something is added, changed, or deprecated in the project. + + +.. _typographical-conventions-version-added: + +Version added +^^^^^^^^^^^^^ + +The version in which a feature is added to a project is displayed as follows. + +.. versionadded:: 1.1 + :func:`pyramid.paster.bootstrap` + + +.. _typographical-conventions-version-changed: + +Version changed +^^^^^^^^^^^^^^^ + +The version in which a feature is changed in a project is displayed as follows. + +.. versionchanged:: 1.8 + Added the ability for ``bootstrap`` to cleanup automatically via the ``with`` statement. + + +.. _typographical-conventions-deprecated: + +Deprecated +^^^^^^^^^^ + +The version in which a feature is deprecated in a project is displayed as follows. + +.. deprecated:: 1.7 + Use the ``require_csrf`` option or read :ref:`auto_csrf_checking` instead to have :class:`pyramid.exceptions.BadCSRFToken` exceptions raised. + + +.. _typographical-conventions-danger: + +Danger +------ + +Danger represents critical information related to a topic or concept, and should recommend to the user "don't do this dangerous thing". + +.. danger:: + + This is danger or an error. + + +.. _typographical-conventions-warnings: + +Warnings +-------- + +Warnings represent limitations and advice related to a topic or concept. + +.. warning:: + + This is a warning. + + +.. _typographical-conventions-notes: + +Notes +----- + +Notes represent additional information related to a topic or concept. + +.. note:: + + This is a note. + + +.. _typographical-conventions-see-also: + +See also +-------- + +"See also" messages refer to topics that are related to the current topic, but have a narrative tone to them instead of merely a link without explanation. "See also" is rendered in a block as well, so that it stands out for the reader's attention. + +.. seealso:: + + See :ref:`Quick Tutorial section on Requirements <qtut_requirements>`. + + +.. _typographical-conventions-todo: + +Todo +---- + +Todo items designated tasks that require further work. + +.. todo:: + + This is a todo item. + + +.. _typographical-conventions-cross-references: + +Cross-references +---------------- + +Cross-references are links that may be to a document, arbitrary location, object, or other items. + + +.. _typographical-conventions-cross-referencing-documents: + +Cross-referencing documents +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Links to pages within this documentation display as follows. + +:doc:`quick_tour` + + +.. _typographical-conventions-cross-referencing-arbitrary-locations: + +Cross-referencing arbitrary locations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Links to sections, and tables and figures with captions, within this documentation display as follows. + +:ref:`i18n_chapter` + + +.. _typographical-conventions-cross-referencing-python: + +Python modules, classes, methods, and functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +All of the following are clickable links to Python modules, classes, methods, and functions. + +Python module names display as follows. + +:mod:`pyramid.config` + +Python class names display as follows. + +:class:`pyramid.config.Configurator` + +Python method names display as follows. + +:meth:`pyramid.config.Configurator.add_view` + +Python function names display as follows. + +:func:`pyramid.renderers.render_to_response` + +Sometimes we show only the last segment of a Python object's name, which displays as follows. + +:func:`~pyramid.renderers.render_to_response` + +The application "Pyramid" itself displays as follows. + +:app:`Pyramid` + diff --git a/docs/whatsnew-1.7.rst b/docs/whatsnew-1.7.rst index 398b12f01..c5f611f04 100644 --- a/docs/whatsnew-1.7.rst +++ b/docs/whatsnew-1.7.rst @@ -126,7 +126,7 @@ Feature Additions - The :attr:`pyramid.tweens.EXCVIEW` tween will now re-raise the original exception if no exception view could be found to handle it. This allows - the exception to be handled upstream by another tween or middelware. + the exception to be handled upstream by another tween or middleware. See https://github.com/Pylons/pyramid/pull/2567 Deprecations diff --git a/docs/whatsnew-1.8.rst b/docs/whatsnew-1.8.rst new file mode 100644 index 000000000..ff16c1a4b --- /dev/null +++ b/docs/whatsnew-1.8.rst @@ -0,0 +1,234 @@ +What's New in Pyramid 1.8 +========================= + +This article explains the new features in :app:`Pyramid` version 1.8 as +compared to its predecessor, :app:`Pyramid` 1.7. It also documents backwards +incompatibilities between the two versions and deprecations added to +:app:`Pyramid` 1.8, as well as software dependency changes and notable +documentation additions. + +Major Feature Additions +----------------------- + +- Added :meth:`pyramid.config.Configurator.add_exception_view` and the + :func:`pyramid.view.exception_view_config` decorator. It is now possible + using these methods or via the new ``exception_only=True`` option to + :meth:`pyramid.config.Configurator.add_view` to add a view which will only + be matched when handling an exception. Previously, any exception views were + also registered for a traversal context that inherited from the exception + class which prevented any exception-only optimizations. + See https://github.com/Pylons/pyramid/pull/2660 + +- ``pserve --reload`` now uses the + `hupper <http://docs.pylonsproject.org/projects/hupper/en/latest/>`_ + library to monitor file changes. This comes with many improvements: + + - If the `watchdog <http://pythonhosted.org/watchdog/>`_ package is + installed then monitoring will be done using inotify instead of + cpu and disk-intensive polling. + + - The monitor is now a separate process that will not crash and starts up + before any of your code. + + - The monitor will not restart the process after a crash until a file is + saved. + + - The monitor works on windows. + + - You can now trigger a reload manually from a pyramid view or any other + code via ``hupper.get_reloader().trigger_reload()``. Kind of neat. + + - You can trigger a reload by issuing a ``SIGHUP`` to the monitor process. + + See https://github.com/Pylons/pyramid/pull/2805 + +Minor Feature Additions +----------------------- + +- Python 3.6 compatibility. + https://github.com/Pylons/pyramid/issues/2835 + +- The ``_get_credentials`` private method of + :class:`pyramid.authentication.BasicAuthAuthenticationPolicy` + has been extracted into standalone function + :func:`pyramid.authentication.extract_http_basic_credentials`, this function + extracts HTTP Basic credentials from a ``request`` object, and returns them + as a named tuple. See https://github.com/Pylons/pyramid/pull/2662 + +- Pyramid 1.4 silently dropped a feature of the configurator that has been + restored. It's again possible for action discriminators to conflict across + different action orders. + See https://github.com/Pylons/pyramid/pull/2757 + +- :func:`pyramid.paster.bootstrap` and its sibling + :func:`pyramid.scripting.prepare` can now be used as context managers to + automatically invoke the ``closer`` and pop threadlocals off of the stack + to prevent memory leaks. See https://github.com/Pylons/pyramid/pull/2760 + +- Added the ``exception_only`` boolean to + :class:`pyramid.interfaces.IViewDeriverInfo` which can be used by view + derivers to determine if they are wrapping a view which only handles + exceptions. This means that it is no longer necessary to perform request-time + checks for ``request.exception`` to determine if the view is handling an + exception - the pipeline can be optimized at config-time. + See https://github.com/Pylons/pyramid/pull/2660 + +- ``pcreate`` learned about ``--package-name`` to allow you to create a new + project in an existing folder with a different package name than the project + name. See https://github.com/Pylons/pyramid/pull/2783 + +- ``pserve`` should now work with ``gevent`` and other workers that need + to monkeypatch the process, assuming the server and / or the app do so + as soon as possible before importing the rest of pyramid. + See https://github.com/Pylons/pyramid/pull/2797 + +- Pyramid no longer copies the settings object passed to the + ``pyramid.config.Configurator(settings=)``. The original ``dict`` is kept. + See https://github.com/Pylons/pyramid/pull/2823 + +- The csrf trusted origins setting may now be a whitespace-separated list of + domains. Previously only a python list was allowed. Also, it can now be set + using the ``PYRAMID_CSRF_TRUSTED_ORIGINS`` environment variable similar to + other settings. See https://github.com/Pylons/pyramid/pull/2823 + +- A new ``[pserve]`` section is supported in your config files with a + ``watch_files`` key that can configure ``pserve --reload`` to monitor custom + file paths. See https://github.com/Pylons/pyramid/pull/2827 + +- Allow streaming responses to be made from subclasses of + :class:`pyramid.httpexceptions.HTTPException`. Previously the response would + be unrolled while testing for a body, making it impossible to stream + a response. + See https://github.com/Pylons/pyramid/pull/2863 + +- Update starter, alchemy and zodb scaffolds to support IPv6 by using the + new ``listen`` directives in waitress. + See https://github.com/Pylons/pyramid/pull/2853 + +- All p* scripts now use argparse instead of optparse. This improves their + ``--help`` output as well as enabling nicer documentation of their options. + See https://github.com/Pylons/pyramid/pull/2864 + +- Added an ``override`` option to + :meth:`pyramid.config.Configurator.add_translation_dirs` to allow + later calls to place translation directories at a higher priority then + earlier calls. See https://github.com/Pylons/pyramid/pull/2902 + +- Added a new ``callback`` option to + :meth:`pyramid.config.Configurator.set_default_csrf_options`` which + can be used to determine per-request whether CSRF checking should be enabled + to allow for a mix authentication methods. Only cookie-based methods + generally require CSRF checking. + See https://github.com/Pylons/pyramid/pull/2778 + +Backwards Incompatibilities +--------------------------- + +- Following the Pyramid deprecation period (1.6 -> 1.8), + daemon support for pserve has been removed. This includes removing the + daemon commands (start, stop, restart, status) as well as the following + arguments: ``--daemon``, ``--pid-file``, ``--log-file``, + ``--monitor-restart``, ``--status``, ``--user``, ``--group``, + ``--stop-daemon`` + + To run your server as a daemon you should use a process manager instead of + pserve. + + See https://github.com/Pylons/pyramid/pull/2615 + +- Change static view to avoid setting the ``Content-Encoding`` response header + to an encoding guessed using Python's ``mimetypes`` module. This was causing + clients to decode the content of gzipped files when downloading them. The + client would end up with a ``foo.txt.gz`` file on disk that was already + decoded, thus should really be ``foo.txt``. Also, the ``Content-Encoding`` + should only have been used if the client itself broadcast support for the + encoding via ``Accept-Encoding`` request headers. + See https://github.com/Pylons/pyramid/pull/2810 + +- ``pcreate`` is now interactive by default. You will be prompted if a file + already exists with different content. Previously if there were similar + files it would silently skip them unless you specified ``--interactive`` + or ``--overwrite``. + See https://github.com/Pylons/pyramid/pull/2775 + +- Support for the ``IContextURL`` interface that was deprecated in Pyramid 1.3 + has been removed. See https://github.com/Pylons/pyramid/pull/2822 + +- Settings are no longer accessible as attributes on the settings object + (e.g. ``request.registry.settings.foo``). This was deprecated in Pyramid 1.2. + See https://github.com/Pylons/pyramid/pull/2823 + +- Removed undocumented argument ``cachebust_match`` from + ``pyramid.static.static_view``. This argument was shipped accidentally + in Pyramid 1.6. See https://github.com/Pylons/pyramid/pull/2681 + +Deprecations +------------ + +- The ``pcreate`` script and the core scaffolds (``starter``, ``alchemy`` and + ``zodb``) have been deprecated. + + They have been replaced with the decision to embrace the popular + `cookiecutter <https://cookiecutter.readthedocs.io/en/latest/>`_ project + as a best-of-breed project templating solution. + + ``pcreate`` was originally introduced when very few alternatives existed + that supported Python 3. Fortunately the situation has improved and + with possible tooling support for cookiecutters being discussed by major + IDEs, and the simplicity of the jinja2 syntax, it is exciting to embrace + the project moving forward! + + All of Pyramid's official scaffolds as well as the tutorials have been + ported to cookiecutters: + + - `pyramid-cookiecutter-starter + <https://github.com/Pylons/pyramid-cookiecutter-starter>`_ + + - `pyramid-cookiecutter-alchemy + <https://github.com/Pylons/pyramid-cookiecutter-alchemy>`_ + + - `pyramid-cookiecutter-zodb + <https://github.com/Pylons/pyramid-cookiecutter-zodb>`_ + + See https://github.com/Pylons/pyramid/pull/2780 + +Documentation Enhancements +-------------------------- + +- Update Typographical Conventions. + https://github.com/Pylons/pyramid/pull/2838 + +- Add `pyramid_nacl_session + <http://docs.pylonsproject.org/projects/pyramid-nacl-session/en/latest/>`_ + to session factories. See https://github.com/Pylons/pyramid/issues/2791 + +- Update HACKING.txt from stale branch that was never merged to master. + See https://github.com/Pylons/pyramid/pull/2782 + +- Updated Windows installation instructions and related bits. + See https://github.com/Pylons/pyramid/issues/2661 + +- Fix an inconsistency in the documentation between view predicates and + route predicates and highlight the differences in their APIs. + See https://github.com/Pylons/pyramid/pull/2764 + +- Clarify a possible misuse of the ``headers`` kwarg to subclasses of + :class:`pyramid.httpexceptions.HTTPException` in which more appropriate + kwargs from the parent class :class:`pyramid.response.Response` should be + used instead. See https://github.com/Pylons/pyramid/pull/2750 + +- The SQLAlchemy + URL Dispatch + Jinja2 (``wiki2``) and + ZODB + Traversal + Chameleon (``wiki``) tutorials have been updated to + utilize the new cookiecutters and drop support for the ``pcreate`` + scaffolds. + + See https://github.com/Pylons/pyramid/pull/2881 and + https://github.com/Pylons/pyramid/pull/2883. + +- Quick Tour, Quick Tutorial, and most files throughout the documentation have + been updated to use cookiecutters instead of pcreate and scaffolds. + See https://github.com/Pylons/pyramid/pull/2888 and + https://github.com/Pylons/pyramid/pull/2889 + +- Updated the ``mod_wsgi`` tutorial to use cookiecutters and Apache 2.4+. + See https://github.com/Pylons/pyramid/pull/2901 diff --git a/docs/whatsnew-1.9.rst b/docs/whatsnew-1.9.rst new file mode 100644 index 000000000..0ba29625c --- /dev/null +++ b/docs/whatsnew-1.9.rst @@ -0,0 +1,61 @@ +What's New in Pyramid 1.9 +========================= + +This article explains the new features in :app:`Pyramid` version 1.9 as compared to its predecessor, :app:`Pyramid` 1.8. It also documents backwards incompatibilities between the two versions and deprecations added to :app:`Pyramid` 1.9, as well as software dependency changes and notable documentation additions. + +Major Feature Additions +----------------------- + +- The file format used by all ``p*`` command line scripts such as ``pserve`` and ``pshell``, as well as the :func:`pyramid.paster.bootstrap` function is now replaceable thanks to a new dependency on `plaster <http://docs.pylonsproject.org/projects/plaster/en/latest/>`_. + + For now, Pyramid is still shipping with integrated support for the PasteDeploy INI format by depending on the `plaster_pastedeploy <https://github.com/Pylons/plaster_pastedeploy>`_ binding library. This may change in the future so it is recommended for applications to start depending on the appropriate plaster binding for their needs. + + See https://github.com/Pylons/pyramid/pull/2985 + +- Added an :term:`execution policy` hook to the request pipeline. An execution policy has the ability to control creation and execution of the request objects before they enter the rest of the pipeline. This means for a single request environ the policy may create more than one request object. + + The execution policy can be replaced using the new :meth:`pyramid.config.Configurator.set_execution_policy` config directive. + + The first library to use this feature is `pyramid_retry <http://docs.pylonsproject.org/projects/pyramid-retry/en/latest/>`_. + + See https://github.com/Pylons/pyramid/pull/2964 + +- CSRF support has been refactored out of sessions and into its own independent API in the :mod:`pyramid.csrf` module. It supports a pluggable :class:`pyramid.interfaces.ICSRFStoragePolicy` which can be used to define your own mechanism for generating and validating CSRF tokens. By default, Pyramid continues to use the :class:`pyramid.csrf.LegacySessionCSRFStoragePolicy` that uses the ``request.session.get_csrf_token`` and ``request.session.new_csrf_token`` APIs under the hood to preserve compatibility with older Pyramid applications. Two new policies are shipped as well, :class:`pyramid.csrf.SessionCSRFStoragePolicy` and :class:`pyramid.csrf.CookieCSRFStoragePolicy` which will store the CSRF tokens in the session and in a standalone cookie, respectively. The storage policy can be changed by using the new :meth:`pyramid.config.Configurator.set_csrf_storage_policy` config directive. + + CSRF tokens should be used via the new :func:`pyramid.csrf.get_csrf_token`, :func:`pyramid.csrf.new_csrf_token` and :func:`pyramid.csrf.check_csrf_token` APIs in order to continue working if the storage policy is changed. Also, the :func:`pyramid.csrf.get_csrf_token` function is now injected into templates to be used conveniently in UI code. + + See https://github.com/Pylons/pyramid/pull/2854 and https://github.com/Pylons/pyramid/pull/3019 + +Minor Feature Additions +----------------------- + +- Support an ``open_url`` config setting in the ``pserve`` section of the config file. This url is used to open a web browser when ``pserve --browser`` is invoked. When this setting is unavailable the ``pserve`` script will attempt to guess the port the server is using from the ``server:<server_name>`` section of the config file but there is no requirement that the server is being run in this format so it may fail. See https://github.com/Pylons/pyramid/pull/2984 + +- The :class:`pyramid.config.Configurator` can now be used as a context manager which will automatically push/pop threadlocals (similar to :meth:`pyramid.config.Configurator.begin` and :meth:`pyramid.config.Configurator.end`). It will also automatically perform a :meth:`pyramid.config.Configurator.commit` at the end and thus it is only recommended to be used at the top-level of your app. See https://github.com/Pylons/pyramid/pull/2874 + +- The threadlocals are now available inside any function invoked via :meth:`pyramid.config.Configurator.include`. This means the only config-time code that cannot rely on threadlocals is code executed from non-actions inside the main. This can be alleviated by invoking :meth:`pyramid.config.Configurator.begin` and :meth:`pyramid.config.Configurator.end` appropriately or using the new context manager feature of the configurator. See https://github.com/Pylons/pyramid/pull/2989 + +Deprecations +------------ + +- Pyramid currently depends on ``plaster_pastedeploy`` to simplify the transition to ``plaster`` by maintaining integrated support for INI files. This dependency on ``plaster_pastedeploy`` should be considered subject to Pyramid's deprecation policy and may be removed in the future. Applications should depend on the appropriate plaster binding to satisfy their needs. + +- Retrieving CSRF token from the session has been deprecated in favor of equivalent methods in the :mod:`pyramid.csrf` module. The CSRF methods (``ISession.get_csrf_token`` and ``ISession.new_csrf_token``) are no longer required on the :class:`pyramid.interfaces.ISession` interface except when using the default :class:`pyramid.csrf.LegacySessionCSRFStoragePolicy`. + + Also, ``pyramid.session.check_csrf_token`` is now located at :func:`pyramid.csrf.check_csrf_token`. + + See https://github.com/Pylons/pyramid/pull/2854 and https://github.com/Pylons/pyramid/pull/3019 + +Backward Incompatibilities +-------------------------- + +- ``request.exception`` and ``request.exc_info`` will only be set if the response was generated by the EXCVIEW tween. This is to avoid any confusion where a response was generated elsewhere in the pipeline and not in direct relation to the original exception. If anyone upstream wants to catch and render responses for exceptions they should set ``request.exception`` and ``request.exc_info`` themselves to indicate the exception that was squashed when generating the response. + + Similar behavior occurs with :meth:`pyramid.request.Request.invoke_exception_view` in which the exception properties are set to reflect the exception if a response is successfully generated by the method. + + This is a very minor incompatibility. Most tweens right now would give priority to the raised exception and ignore ``request.exception``. This change just improves and clarifies that bookkeeping by trying to be more clear about the relationship between the response and its squashed exception. See https://github.com/Pylons/pyramid/pull/3029 and https://github.com/Pylons/pyramid/pull/3031 + +Documentation Enhancements +-------------------------- + +- Added the :term:`execution policy` to the routing diagram in :ref:`router_chapter`. See https://github.com/Pylons/pyramid/pull/2993 |
