Codebase list python-werkzeug / 85c19b5
Imported Upstream version 0.3 SVN-Git Migration 8 years ago
241 changed file(s) with 18326 addition(s) and 2030 deletion(s). Raw diff Collapse all Expand all
77 - Marek Kubica
88 - Thomas Johansson
99 - Marian Sigler
10
11 Contributors of code for werkzeug/examples are:
12
13 - Itay Neeman <itay@neeman.net>
11 ==================
22
33
4 Version 0.3
5 -----------
6 (codename to be selected, release date somewhere around May 2008)
7
8 - added support for redirecting in url routing.
9 - added `Authorization` and `AuthorizationMixin`
10 - added `WWWAuthenticate` and `WWWAuthenticateMixin`
11 - added `parse_list_header`
12 - added `parse_dict_header`
13 - added `parse_authorization_header`
14 - added `parse_www_authenticate_header`
15 - added `_get_current_object` method to `LocalProxy` objects
16 - added `parse_form_data`
17 - `MultiDict`, `CombinedMultiDict`, `Headers`, and `EnvironHeaders` raise
18 special key errors now that are subclasses of `BadRequest` so if you
19 don't catch them they give meaningful HTTP responses.
20 - added support for alternative encoding error handling and the new
21 `HTTPUnicodeError` which (if not cought) behaves like a `BadRequest`.
22 - adde `BadRequest.wrap`.
23 - added ETag-support to the SharedDataMiddleware and added an option
24 to disable caching.
25 - fixed `is_xhr` on the request objects.
26 - fixed error handling of the url adapter's `dispatch` method. (#318)
27 - fixed bug with `SharedDataMiddleware`.
28 - fixed `Accept.values`.
29 - `EnvironHeaders` contain content-type and content-length now
30 - `url_encode` treats lists and tuples in dicts passed to it as multiple
31 values for the same key so that one doesn't have to pass a `MultiDict`
32 to the function.
33 - added `validate_arguments`
34 - added `BaseRequest.application`
35 - improved Python 2.3 support
36 - `run_simple` accepts `use_debugger` and `use_evalex` parameters now,
37 like the `make_runserver` factory function from the script module.
38 - the `environ_property` is now read only by default
39 - it's now possible to initialize requests as "shallow" requests which
40 causes runtime errors if the request object tries to consume the
41 input stream.
42
43
444 Version 0.2
545 -----------
6 (codename Faustkeil, estimated release date Feb 14th 2008)
46 (codename Faustkeil, released Feb 14th 2008)
747
848 - Added `AnyConverter` to the routing system.
949 - Added `werkzeug.contrib.securecookie`
10 - Exceptions have a ``get_response()`` method that return a resposne object
50 - Exceptions have a ``get_response()`` method that return a response object
1151 - fixed the path ordering bug (#293), thanks Thomas Johansson
1252 - `BaseReporterStream` is now part of the werkzeug contrib module. With
1353 Werkzeug 0.3 onwards you will have to import it from there.
0 include Makefile CHANGES LICENSE AUTHORS TODO ez_setup.py
1 recursive-include werkzeug debug/shared/*
0 include Makefile THANKS CHANGES LICENSE AUTHORS TODO ez_setup.py
1 recursive-include werkzeug/debug/shared *
2 recursive-include werkzeug/debug/templates *
23 recursive-include tests *
4 recursive-include docs *
5 recursive-include examples *
6 recursive-include artwork *
00 Metadata-Version: 1.0
11 Name: Werkzeug
2 Version: 0.2
2 Version: 0.3
33 Summary: The Swiss Army knife of Python web development
44 Home-page: http://werkzeug.pocoo.org/
55 Author: Armin Ronacher
66 Author-email: armin.ronacher@active-4.com
77 License: BSD
8 Download-URL: http://werkzeug.pocoo.org/download
98 Description:
109 Werkzeug
1110 ========
5453 Development Version
5554 -------------------
5655
57 The `Werkzeug tip <http://dev.pocoo.org/hg/werkzeug-main/archive/tip.zip#egg=Werkzeug-dev`
56 The `Werkzeug tip <http://dev.pocoo.org/hg/werkzeug-main/archive/tip.zip#egg=Werkzeug-dev>`_
5857 is installable via `easy_install` with ``easy_install Werkzeug==dev``.
5958
6059 Platform: any
61 Classifier: Development Status :: 3 - Alpha
60 Classifier: Development Status :: 4 - Beta
6261 Classifier: Environment :: Web Environment
6362 Classifier: Intended Audience :: Developers
6463 Classifier: License :: OSI Approved :: BSD License
0 Thanks To
1 =========
2
3 All the people listed here helped improving Werkzeug a lot, provided
4 patches, helped working out solutions etc. Thanks to all of you!
5
6 - Ronny Pfannschmidt
7 - Noah Slater <nslater@bytesexual.org>
8 - Alec Thomas
9 - Shannon Behrens
10 - Christoph Rauch
00 Metadata-Version: 1.0
11 Name: Werkzeug
2 Version: 0.2
2 Version: 0.3
33 Summary: The Swiss Army knife of Python web development
44 Home-page: http://werkzeug.pocoo.org/
55 Author: Armin Ronacher
66 Author-email: armin.ronacher@active-4.com
77 License: BSD
8 Download-URL: http://werkzeug.pocoo.org/download
98 Description:
109 Werkzeug
1110 ========
5453 Development Version
5554 -------------------
5655
57 The `Werkzeug tip <http://dev.pocoo.org/hg/werkzeug-main/archive/tip.zip#egg=Werkzeug-dev`
56 The `Werkzeug tip <http://dev.pocoo.org/hg/werkzeug-main/archive/tip.zip#egg=Werkzeug-dev>`_
5857 is installable via `easy_install` with ``easy_install Werkzeug==dev``.
5958
6059 Platform: any
61 Classifier: Development Status :: 3 - Alpha
60 Classifier: Development Status :: 4 - Beta
6261 Classifier: Environment :: Web Environment
6362 Classifier: Intended Audience :: Developers
6463 Classifier: License :: OSI Approved :: BSD License
22 LICENSE
33 MANIFEST.in
44 Makefile
5 THANKS
56 ez_setup.py
67 setup.cfg
78 setup.py
1112 Werkzeug.egg-info/not-zip-safe
1213 Werkzeug.egg-info/requires.txt
1314 Werkzeug.egg-info/top_level.txt
15 artwork/logo.png
16 artwork/logo.svg
17 docs/build/README
18 docs/build/api_stability.html
19 docs/build/debug.html
20 docs/build/deploying.html
21 docs/build/exceptions.html
22 docs/build/favicon.ico
23 docs/build/header.png
24 docs/build/index.html
25 docs/build/installation.html
26 docs/build/libraries.html
27 docs/build/local.html
28 docs/build/organizing.html
29 docs/build/print.css
30 docs/build/pygments.css
31 docs/build/routing.html
32 docs/build/script.html
33 docs/build/serving.html
34 docs/build/style.css
35 docs/build/templates.html
36 docs/build/terms.html
37 docs/build/test.html
38 docs/build/tutorial.html
39 docs/build/tutorial_de.html
40 docs/build/unicode.html
41 docs/build/utils.html
42 docs/build/werkzeug.png
43 docs/build/wrappers.html
44 docs/build/wsgihowto.html
45 docs/src/api_stability.txt
46 docs/src/debug.txt
47 docs/src/deploying.txt
48 docs/src/exceptions.txt
49 docs/src/index.txt
50 docs/src/installation.txt
51 docs/src/libraries.txt
52 docs/src/local.txt
53 docs/src/organizing.txt
54 docs/src/routing.txt
55 docs/src/script.txt
56 docs/src/serving.txt
57 docs/src/templates.txt
58 docs/src/terms.txt
59 docs/src/test.txt
60 docs/src/tutorial.txt
61 docs/src/tutorial_de.txt
62 docs/src/unicode.txt
63 docs/src/utils.txt
64 docs/src/wrappers.txt
65 docs/src/wsgihowto.txt
66 examples/README
67 examples/httpbasicauth.py
68 examples/manage-coolmagic.py
69 examples/manage-couchy.py
70 examples/manage-cupoftee.py
71 examples/manage-i18nurls.py
72 examples/manage-plnt.py
73 examples/manage-shorty.py
74 examples/manage-simplewiki.py
75 examples/manage-webpylike.py
76 examples/objectapp.py
77 examples/upload.py
78 examples/contrib/README
79 examples/contrib/securecookie.py
80 examples/contrib/sessions.py
81 examples/coolmagic/__init__.py
82 examples/coolmagic/application.py
83 examples/coolmagic/helpers.py
84 examples/coolmagic/utils.py
85 examples/coolmagic/public/style.css
86 examples/coolmagic/templates/layout.html
87 examples/coolmagic/templates/static/about.html
88 examples/coolmagic/templates/static/index.html
89 examples/coolmagic/templates/static/not_found.html
90 examples/coolmagic/views/__init__.py
91 examples/coolmagic/views/static.py
92 examples/couchy/README
93 examples/couchy/__init__.py
94 examples/couchy/application.py
95 examples/couchy/models.py
96 examples/couchy/utils.py
97 examples/couchy/views.py
98 examples/couchy/static/style.css
99 examples/couchy/templates/.DS_Store
100 examples/couchy/templates/display.html
101 examples/couchy/templates/layout.html
102 examples/couchy/templates/list.html
103 examples/couchy/templates/new.html
104 examples/couchy/templates/not_found.html
105 examples/cupoftee/__init__.py
106 examples/cupoftee/__init__.pyc
107 examples/cupoftee/application.py
108 examples/cupoftee/application.pyc
109 examples/cupoftee/db.py
110 examples/cupoftee/db.pyc
111 examples/cupoftee/network.py
112 examples/cupoftee/network.pyc
113 examples/cupoftee/pages.py
114 examples/cupoftee/pages.pyc
115 examples/cupoftee/utils.py
116 examples/cupoftee/utils.pyc
117 examples/cupoftee/shared/content.png
118 examples/cupoftee/shared/down.png
119 examples/cupoftee/shared/favicon.ico
120 examples/cupoftee/shared/header.png
121 examples/cupoftee/shared/logo.png
122 examples/cupoftee/shared/style.css
123 examples/cupoftee/shared/up.png
124 examples/cupoftee/templates/layout.html
125 examples/cupoftee/templates/missingpage.html
126 examples/cupoftee/templates/search.html
127 examples/cupoftee/templates/server.html
128 examples/cupoftee/templates/serverlist.html
129 examples/i18nurls/__init__.py
130 examples/i18nurls/application.py
131 examples/i18nurls/urls.py
132 examples/i18nurls/views.py
133 examples/i18nurls/templates/about.html
134 examples/i18nurls/templates/blog.html
135 examples/i18nurls/templates/index.html
136 examples/i18nurls/templates/layout.html
137 examples/partial/README
138 examples/partial/complex_routing.py
139 examples/plnt/__init__.py
140 examples/plnt/__init__.pyc
141 examples/plnt/database.py
142 examples/plnt/database.pyc
143 examples/plnt/sync.py
144 examples/plnt/utils.py
145 examples/plnt/utils.pyc
146 examples/plnt/views.py
147 examples/plnt/views.pyc
148 examples/plnt/webapp.py
149 examples/plnt/webapp.pyc
150 examples/plnt/shared/style.css
151 examples/plnt/templates/about.html
152 examples/plnt/templates/index.html
153 examples/plnt/templates/layout.html
154 examples/shorty/__init__.py
155 examples/shorty/__init__.pyc
156 examples/shorty/application.py
157 examples/shorty/application.pyc
158 examples/shorty/models.py
159 examples/shorty/models.pyc
160 examples/shorty/utils.py
161 examples/shorty/utils.pyc
162 examples/shorty/views.py
163 examples/shorty/views.pyc
164 examples/shorty/static/style.css
165 examples/shorty/templates/display.html
166 examples/shorty/templates/layout.html
167 examples/shorty/templates/list.html
168 examples/shorty/templates/new.html
169 examples/shorty/templates/not_found.html
170 examples/simplewiki/__init__.py
171 examples/simplewiki/__init__.pyc
172 examples/simplewiki/actions.py
173 examples/simplewiki/actions.pyc
174 examples/simplewiki/application.py
175 examples/simplewiki/application.pyc
176 examples/simplewiki/database.py
177 examples/simplewiki/database.pyc
178 examples/simplewiki/specialpages.py
179 examples/simplewiki/specialpages.pyc
180 examples/simplewiki/utils.py
181 examples/simplewiki/utils.pyc
182 examples/simplewiki/shared/style.css
183 examples/simplewiki/templates/action_diff.html
184 examples/simplewiki/templates/action_edit.html
185 examples/simplewiki/templates/action_log.html
186 examples/simplewiki/templates/action_revert.html
187 examples/simplewiki/templates/action_show.html
188 examples/simplewiki/templates/layout.html
189 examples/simplewiki/templates/macros.xml
190 examples/simplewiki/templates/missing_action.html
191 examples/simplewiki/templates/page_index.html
192 examples/simplewiki/templates/page_missing.html
193 examples/simplewiki/templates/recent_changes.html
194 examples/webpylike/example.py
195 examples/webpylike/webpylike.py
14196 tests/conftest.py
197 tests/test_http.py
198 tests/test_http.pyc
15199 tests/test_routing.py
16200 tests/test_routing.pyc
17201 tests/test_templates.py
24208 tests/contrib/test_testtools.pyc
25209 tests/res/test.txt
26210 werkzeug/__init__.py
211 werkzeug/_internal.py
27212 werkzeug/exceptions.py
28213 werkzeug/http.py
29214 werkzeug/local.py
49234 werkzeug/contrib/sessions.py
50235 werkzeug/contrib/testtools.py
51236 werkzeug/debug/__init__.py
237 werkzeug/debug/console.py
52238 werkzeug/debug/render.py
53 werkzeug/debug/util.py
239 werkzeug/debug/repr.py
240 werkzeug/debug/tbtools.py
241 werkzeug/debug/utils.py
54242 werkzeug/debug/shared/body.tmpl
55243 werkzeug/debug/shared/codetable.tmpl
244 werkzeug/debug/shared/console.png
56245 werkzeug/debug/shared/debugger.js
57246 werkzeug/debug/shared/jquery.js
247 werkzeug/debug/shared/less.png
248 werkzeug/debug/shared/more.png
249 werkzeug/debug/shared/source.png
58250 werkzeug/debug/shared/style.css
59251 werkzeug/debug/shared/vartable.tmpl
252 werkzeug/debug/templates/console.html
253 werkzeug/debug/templates/dump_object.html
254 werkzeug/debug/templates/frame.html
255 werkzeug/debug/templates/help_command.html
256 werkzeug/debug/templates/source.html
257 werkzeug/debug/templates/traceback_full.html
258 werkzeug/debug/templates/traceback_plaintext.html
259 werkzeug/debug/templates/traceback_summary.html
Binary diff not shown
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2 <svg
3 xmlns:dc="http://purl.org/dc/elements/1.1/"
4 xmlns:cc="http://web.resource.org/cc/"
5 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6 xmlns:svg="http://www.w3.org/2000/svg"
7 xmlns="http://www.w3.org/2000/svg"
8 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10 width="200"
11 height="200"
12 id="svg2"
13 sodipodi:version="0.32"
14 inkscape:version="0.44"
15 version="1.0"
16 sodipodi:docbase="/home/blackbird/Development/werkzeug/trunk/artwork"
17 sodipodi:docname="logo.svg"
18 inkscape:export-filename="/home/blackbird/Development/werkzeug/www/static/logo.png"
19 inkscape:export-xdpi="72"
20 inkscape:export-ydpi="72">
21 <defs
22 id="defs4" />
23 <sodipodi:namedview
24 id="base"
25 pagecolor="#ffffff"
26 bordercolor="#666666"
27 borderopacity="1.0"
28 gridtolerance="10000"
29 guidetolerance="10"
30 objecttolerance="10"
31 inkscape:pageopacity="0.0"
32 inkscape:pageshadow="2"
33 inkscape:zoom="2.8"
34 inkscape:cx="152.77636"
35 inkscape:cy="85.971332"
36 inkscape:document-units="px"
37 inkscape:current-layer="layer1"
38 width="200px"
39 height="200px"
40 showguides="true"
41 inkscape:guide-bbox="true"
42 inkscape:window-width="1396"
43 inkscape:window-height="975"
44 inkscape:window-x="0"
45 inkscape:window-y="24" />
46 <metadata
47 id="metadata7">
48 <rdf:RDF>
49 <cc:Work
50 rdf:about="">
51 <dc:format>image/svg+xml</dc:format>
52 <dc:type
53 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
54 </cc:Work>
55 </rdf:RDF>
56 </metadata>
57 <g
58 inkscape:label="Layer 1"
59 inkscape:groupmode="layer"
60 id="layer1"
61 transform="translate(-2.761928,-2.061494)">
62 <rect
63 style="opacity:1;fill:#ffe000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:6.47646141;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
64 id="rect4120"
65 width="135.0585"
66 height="138.25838"
67 x="78.779762"
68 y="-67.54702"
69 transform="matrix(0.713806,0.700344,-0.700496,0.713656,0,0)"
70 inkscape:export-filename="/home/blackbird/Development/werkzeug/www/static/rect4120.png"
71 inkscape:export-xdpi="72"
72 inkscape:export-ydpi="72" />
73 <path
74 style="font-size:16.85234642px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Frutiger"
75 d="M 98.512969,162.07226 L 101.50664,159.07859 L 108.68768,163.6985 L 103.80027,156.78495 L 106.59475,153.99048 L 113.91826,165.22967 L 110.56033,168.58759 L 103.21185,163.99374 L 107.874,171.27393 L 104.46485,174.68307 L 93.265503,167.31972 L 96.19088,164.39435 L 103.09304,169.29314 L 98.512969,162.07226 M 106.94761,153.63761 L 113.93665,146.64858 L 116.14772,148.85965 L 112.1239,152.88347 L 113.43512,154.19469 L 117.2825,150.3473 L 119.46142,152.52622 L 115.61404,156.37361 L 116.98311,157.74267 L 121.0866,153.63918 L 123.29767,155.85025 L 116.22896,162.91896 L 106.94761,153.63761 M 114.93833,145.64689 L 120.10612,140.47911 C 120.49693,140.08832 120.82,139.8081 121.07532,139.63845 C 121.44645,139.38733 121.83979,139.20395 122.25536,139.08831 C 122.67094,138.9727 123.08121,138.93525 123.48615,138.97592 C 123.8906,139.00855 124.25908,139.11143 124.59157,139.28455 C 124.92357,139.44962 125.25669,139.69927 125.59093,140.03348 C 125.87802,140.3206 126.10666,140.60615 126.27685,140.89015 C 126.44705,141.1742 126.56803,141.44315 126.63979,141.69702 C 126.71156,141.95093 126.74459,142.19644 126.73887,142.43353 C 126.73475,142.76333 126.6824,143.0942 126.58182,143.42615 C 126.50768,143.66315 126.36645,143.98862 126.15817,144.40258 C 126.55071,144.17287 126.91609,144.02603 127.25432,143.96205 C 127.59631,143.89428 127.9242,143.89206 128.23795,143.95538 C 128.5517,144.01873 128.90218,144.15673 129.28941,144.36941 C 129.51821,144.49199 129.97711,144.79152 130.66612,145.26804 C 131.6744,145.9652 132.31815,146.39268 132.59741,146.55051 L 129.34763,149.8003 L 127.04977,148.04881 C 126.58099,147.68629 126.18956,147.50354 125.87545,147.50055 C 125.56513,147.49378 125.24493,147.65545 124.91483,147.98555 L 124.19772,148.70266 L 127.32149,151.82643 L 124.21968,154.92824 L 114.93833,145.64689 M 119.97964,144.75777 L 122.00431,146.78244 L 122.94908,145.83767 C 123.32851,145.45825 123.53675,145.14289 123.5738,144.89156 C 123.63667,144.46878 123.48384,144.07312 123.11533,143.7046 C 122.75111,143.34039 122.37038,143.16834 121.97316,143.18848 C 121.67618,143.20264 121.32661,143.41082 120.92441,143.813 L 119.97964,144.75777 M 130.74157,137.06821 L 129.09852,131.4867 L 132.66134,127.92389 L 134.22379,134.96147 L 142.07667,136.827 L 142.1988,136.94913 L 138.45955,140.68838 L 132.41916,138.74579 L 136.41064,142.73728 L 133.28038,145.86755 L 123.99903,136.5862 L 127.1293,133.45593 L 130.74157,137.06821 M 140.58375,120.00147 L 143.48256,122.90029 L 143.58315,130.71841 L 147.57851,126.72305 L 150.00169,129.14623 L 142.47197,136.67595 L 139.59244,133.79642 L 139.54343,125.7339 L 135.68466,129.59267 L 133.33861,127.24662 L 140.58375,120.00147 M 141.50575,119.07947 L 148.49479,112.09043 L 150.70586,114.30151 L 146.68204,118.32533 L 147.99326,119.63654 L 151.84064,115.78916 L 154.01956,117.96808 L 150.17218,121.81546 L 151.54125,123.18453 L 155.64474,119.08103 L 157.85582,121.29211 L 150.7871,128.36082 L 141.50575,119.07947 M 149.52494,111.06029 L 152.6552,107.93002 L 158.43355,113.70837 C 158.75492,114.02975 159.01954,114.24505 159.22741,114.35426 C 159.50188,114.49973 159.77903,114.55682 160.05888,114.52552 C 160.3382,114.48613 160.59549,114.34882 160.83074,114.11357 C 161.13427,113.81004 161.26619,113.46388 161.22648,113.07507 C 161.19056,112.68252 160.93906,112.25269 160.47199,111.78561 L 154.6358,105.94943 L 157.64655,102.93867 L 163.19351,108.48562 C 163.9391,109.23123 164.46813,109.92342 164.78062,110.56221 C 165.09359,111.20907 165.24504,111.88035 165.23499,112.57602 C 165.22063,113.26739 165.02474,113.95178 164.64732,114.62915 C 164.2699,115.30653 163.79281,115.93358 163.21609,116.5103 C 162.55588,117.17051 161.88874,117.68339 161.21467,118.04895 C 160.76656,118.29138 160.27148,118.4565 159.72946,118.54432 C 159.18315,118.62786 158.69441,118.63239 158.26327,118.55789 C 157.83212,118.48341 157.36062,118.30787 156.84876,118.03126 C 156.33694,117.75468 155.75964,117.295 155.11688,116.65223 L 149.52494,111.06029 M 166.4528,94.260966 L 168.96597,96.774136 C 167.84388,97.184937 166.92237,97.750778 166.20145,98.471674 C 165.72718,98.945972 165.39985,99.487538 165.21951,100.09638 C 165.04295,100.70144 165.04695,101.29737 165.2315,101.8841 C 165.41986,102.46705 165.80754,103.05205 166.39459,103.6391 C 167.2516,104.4961 168.05462,104.96144 168.80367,105.03511 C 169.54843,105.1045 170.23385,104.82617 170.85991,104.20011 C 171.00029,104.05973 171.1162,103.91383 171.20762,103.76241 L 168.72659,101.28139 L 171.42432,98.583658 L 175.75005,102.90939 C 175.17365,104.15425 174.35994,105.30219 173.30894,106.35319 C 172.46282,107.19932 171.53294,107.85922 170.51931,108.33294 C 169.81854,108.65663 169.12106,108.83563 168.42687,108.86993 C 167.73266,108.90422 167.08321,108.84667 166.47846,108.69723 C 165.87323,108.53973 165.27663,108.2922 164.68864,107.95462 C 164.10064,107.61706 163.54098,107.18261 163.00963,106.65127 C 162.17406,105.81569 161.57571,104.91 161.21457,103.9342 C 160.85345,102.95842 160.73685,101.99948 160.86475,101.0574 C 160.97312,100.32344 161.25371,99.575778 161.70653,98.814422 C 162.15507,98.048817 162.71133,97.333995 163.37532,96.669987 C 163.78889,96.256428 164.29557,95.818326 164.89532,95.355673 C 165.49079,94.888763 166.00995,94.523868 166.4528,94.260966"
76 id="text2827" />
77 <path
78 style="fill:black"
79 d="M 107.53423,60.03639 C 106.44704,59.921621 105.38025,59.905892 104.28215,59.956576 C 103.24482,60.00446 101.71759,60.056269 100.9108,60.089109 C 98.387523,60.191829 95.871679,61.09708 93.830781,62.630378 L 92.878953,63.33433 L 93.57616,63.2781 C 93.958054,63.240194 95.30775,63.321994 96.543845,63.456872 C 99.801727,63.812355 101.2973,64.633168 103.64399,67.369553 C 105.19614,69.179446 105.31697,69.51104 104.72399,70.177638 C 104.12272,70.853516 103.86859,72.011136 104.1504,72.846669 C 104.27088,73.203874 105.22953,74.501832 106.26473,75.729715 C 107.35325,77.020844 108.0999,78.053474 108.02819,78.191255 C 107.83523,78.561981 102.78053,83.598892 97.608303,88.604132 C 95.025713,91.103348 92.469473,93.576956 91.945392,94.084678 C 91.421316,94.592395 87.867941,97.943649 84.025591,101.54644 C 68.348753,116.24587 67.236647,117.45724 66.419741,120.29951 C 66.129905,121.30794 66.129614,121.6132 66.350749,122.69978 C 66.583154,123.84176 67.484264,125.64606 68.169153,126.32256 C 68.608219,126.75625 69.886133,127.53384 70.723909,127.90305 C 71.198994,128.11239 72.194857,128.31206 73.356728,128.40025 C 74.95433,128.52151 75.374196,128.47858 76.393251,128.10465 C 77.048428,127.86427 77.957393,127.3662 78.411555,127.02961 C 79.17613,126.46296 81.726709,123.82935 81.744179,123.5869 C 81.748416,123.52812 82.813205,122.29109 84.090573,120.84456 C 88.850305,115.45451 102.97505,98.764572 110.60969,89.50045 C 112.19394,87.578076 113.64237,85.836707 113.83455,85.629291 L 114.17402,85.247141 L 114.69229,85.750169 C 115.76067,86.834754 117.04876,87.030894 120.24691,86.581887 L 121.92572,86.370466 L 123.36358,88.060031 L 124.80143,89.749598 L 124.52156,90.747795 C 124.0212,92.438968 124.2693,93.005147 126.55542,95.708935 L 128.53447,98.064395 L 129.94491,98.210381 C 131.11588,98.33158 131.53977,98.274944 132.63741,97.879188 C 136.36112,96.536624 139.67644,93.026504 140.07106,89.949822 C 140.27681,88.345505 140.08068,87.828058 139.03696,86.985993 C 138.62905,86.656897 137.74642,85.795129 137.06017,85.080149 C 135.64007,83.600616 134.87945,83.294266 133.29218,83.502489 L 132.31288,83.625819 L 131.20982,82.580783 C 130.59743,82.01031 130.07563,81.487131 130.08154,81.404741 C 130.08747,81.322355 130.33918,81.027664 130.61369,80.753644 C 130.88816,80.479609 131.23264,80.056341 131.37288,79.837159 C 131.72525,79.286331 131.77731,77.974714 131.48545,77.313003 C 131.35192,77.010265 128.98156,74.087366 126.19962,70.827614 C 121.27993,65.063038 121.09656,64.911919 120.16378,64.649783 C 119.63633,64.501546 118.42015,63.915749 117.44849,63.373672 C 114.0546,61.480314 110.79581,60.380697 107.53423,60.03639 z M 108.09605,60.897011 C 108.31193,60.921622 108.66896,60.988563 109.1235,61.067556 C 111.61992,61.501404 115.30019,62.810051 116.59301,63.702413 C 116.96349,63.958139 116.4139,63.862477 115.22153,63.496262 C 114.7446,63.349782 113.7488,63.195445 112.99467,63.137376 C 111.80952,63.046111 111.39064,62.891987 109.91465,62.112405 C 107.93131,61.064875 107.44841,60.823179 108.09605,60.897011 z M 102.72025,60.950471 C 104.09295,61.062095 105.71416,61.370084 107.02496,61.813312 C 108.31947,62.251028 110.72264,63.366038 110.94886,63.631802 C 111.01921,63.714435 110.80621,64.044683 110.48095,64.353743 C 110.13167,64.685628 109.82371,64.837606 109.72639,64.725024 C 109.42489,64.376305 107.29192,63.131341 106.03412,62.577426 C 104.65915,61.97189 102.14865,61.223795 100.94512,61.055629 C 100.30646,60.966392 100.38954,60.951787 101.44051,60.914308 C 101.82032,60.900767 102.26269,60.913264 102.72025,60.950471 z M 99.502536,61.355697 C 99.964116,61.411973 100.47822,61.51904 101.11021,61.650492 C 103.51756,62.151212 104.95229,62.61044 106.56154,63.434485 C 107.79822,64.067732 109.41347,65.219922 109.39607,65.461209 C 109.38932,65.554985 108.59711,66.338165 107.64147,67.205527 C 106.6858,68.072889 105.85842,68.786412 105.79512,68.779861 C 105.52661,68.752068 105.43946,68.425014 105.49477,67.657413 C 105.54395,66.974977 105.44594,66.743684 104.93987,66.219727 C 104.60677,65.87484 104.06507,65.473984 103.75654,65.326877 C 103.44802,65.179761 102.40679,64.672527 101.41273,64.185514 C 99.147576,63.075769 98.209546,62.737561 96.60813,62.564758 L 95.335289,62.433015 L 96.199947,61.976831 C 97.320096,61.386678 98.117786,61.186873 99.502536,61.355697 z M 113.41658,64.015613 C 113.56687,63.985157 114.59425,64.210969 115.70078,64.540929 C 116.80732,64.870891 118.34289,65.199584 119.11335,65.279331 L 120.48939,65.421756 L 124.19755,69.753725 C 126.2232,72.129989 127.8673,74.129865 127.86209,74.209568 C 127.85685,74.289274 126.80911,74.26201 125.54573,74.130311 L 123.24085,73.891746 L 122.53453,73.112466 C 122.13241,72.679621 121.23202,71.692049 120.544,70.916309 C 119.85599,70.140578 119.31638,69.605276 119.34694,69.733149 C 119.39771,69.945778 120.17585,70.92484 123.92186,75.50298 L 125.2267,77.11458 L 128.04531,77.438418 C 129.57888,77.623351 130.86974,77.833647 130.9235,77.896816 C 131.20562,78.228219 130.87589,79.352817 130.32476,79.953368 C 129.80044,80.524707 129.65693,80.600939 129.21701,80.416487 C 128.39657,80.072495 126.78787,79.999988 126.07272,80.283633 C 125.24712,80.611099 125.36058,80.970103 126.24238,80.814776 C 127.68526,80.560616 128.81139,80.904193 129.56551,81.832808 C 129.76449,82.077861 130.25681,82.57838 130.66166,82.973429 L 131.40697,83.692549 L 130.79001,84.078072 C 130.45062,84.277991 129.45019,84.908001 128.57444,85.485795 L 126.97355,86.539849 L 125.46235,84.425403 C 124.2797,82.813057 123.93183,82.220005 124.04749,81.935745 C 124.23308,81.479691 124.13369,81.475422 123.43056,81.839793 C 122.84825,82.141562 122.89958,81.929698 123.5751,81.276975 C 123.75074,81.10726 123.86217,80.909276 123.81365,80.852282 C 123.76512,80.795285 123.10219,81.319865 122.34348,82.016163 C 121.58474,82.712467 120.34379,83.83087 119.60032,84.492737 L 118.26547,85.702723 L 117.58893,84.990722 C 117.21559,84.604155 115.3636,82.401424 113.50474,80.106255 C 111.64591,77.811085 109.10448,74.721795 107.86574,73.231222 L 105.63443,70.528665 L 107.09088,69.074471 C 110.72007,65.410961 112.05193,64.179427 112.57717,64.121323 C 112.87212,64.088705 113.2663,64.046065 113.41658,64.015613 z M 133.90907,84.079923 L 134.76215,85.227482 C 135.23947,85.852722 135.63599,86.502569 135.62664,86.697209 C 135.62084,86.818069 135.64768,86.902062 135.71149,86.962782 C 135.09914,87.083523 134.34933,87.24026 133.65882,87.552793 C 132.6955,87.988798 131.93446,88.660219 131.50286,89.095075 C 131.4407,89.09851 131.38281,89.086922 131.32856,89.109132 C 130.84903,89.305492 128.80709,86.413571 129.12713,85.992379 C 129.5559,85.428151 131.81641,84.364724 132.85866,84.227991 L 133.90907,84.079923 z M 137.56686,87.186915 C 139.68185,87.434311 140.19903,89.512643 138.7728,92.094462 C 138.73394,92.164805 138.72973,92.239331 138.68794,92.310371 C 137.0095,95.162573 133.58079,97.48755 131.03664,97.48881 C 128.49246,97.490074 127.7899,95.171268 129.46835,92.319069 C 129.867,91.641586 130.3553,91.002415 130.92252,90.415256 C 130.92252,90.415256 131.03136,90.331583 131.03262,90.330356 C 131.23479,90.125468 131.45185,89.91379 131.66564,89.721802 C 131.69631,89.685093 131.71538,89.666977 131.74134,89.633341 C 131.75756,89.612317 131.7661,89.592983 131.78034,89.573178 C 132.63226,88.744248 132.42517,89.0759 134.63347,87.974659 L 136.94305,87.186548 C 137.00051,87.184245 137.06072,87.172519 137.11735,87.17249 C 137.27239,87.17241 137.42585,87.170424 137.56686,87.186915 z "
80 id="path4054"
81 sodipodi:nodetypes="csscccssssssssssssssssssscccccccccccsssscccsssssssccsssssccsssssssccssssssssscccccscccscccsscccssssssccccccssssscccscccscccscscsscccsssscssssscsc" />
82 <path
83 style="opacity:1;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:6.47645712;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
84 d="M 105.35593,3.0334526 C 104.7179,2.4030095 103.85461,2.053202 102.9576,2.0616427 C 102.06058,2.0700844 101.20403,2.4360779 100.57799,3.0784166 L 3.734095,101.74164 C 3.1035166,102.37953 2.7536338,103.24264 2.7620774,104.13946 C 2.770521,105.03628 3.1365923,105.89265 3.7790679,106.51857 L 100.19108,201.11225 C 101.52373,202.40388 103.65054,202.37359 104.94584,201.04453 L 201.78974,102.38131 C 203.09445,101.0615 203.08471,98.934946 201.76795,97.62714 L 105.35593,3.0334526 z M 103.05717,10.198137 L 194.64627,100.05986 L 102.51259,193.92439 L 10.923501,104.06265 L 103.05717,10.198137 z "
85 id="rect4126" />
86 </g>
87 </svg>
0 The documentation here is updated before a release from the wzweb application.
1 I know it's weird but it works for the moment ;)
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>API Stability // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>API Stability</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#backwards-incompatible-changes" id="id1" name="id1">Backwards Incompatible Changes</a></li>
22 </ul>
23 </div>
24
25 <p>Werkzeug has not yet reached 1.0 and as a matter of fact some things might
26 change over time. The following modules will very likely change in one of
27 the next release versions:</p>
28 <dl>
29 <dt><cite>werkzeug.contrib</cite></dt>
30 <dd>The community-contributed modules are yet undocumented and we expect
31 some upcoming changes there.</dd>
32 </dl>
33 <div class="section">
34 <h3 id="backwards-incompatible-changes">Backwards Incompatible Changes</h3>
35 <dl>
36 <dt><cite>0.3</cite></dt>
37 <dd><ul class="first last simple">
38 <li>Werkzeug 0.3 will be the last release with Python 2.3 compatibility.</li>
39 <li>The <cite>environ_property</cite> is now read-only by default. This decision was
40 made because the request in general should be considered read-only.</li>
41 </ul>
42 </dd>
43 <dt><cite>0.2</cite></dt>
44 <dd><ul class="first last simple">
45 <li>The <cite>BaseReporterStream</cite> is now part of the contrib module, the
46 new module is <cite>werkzeug.contrib.reporterstream</cite>. Starting with
47 <cite>0.3</cite>, the old import will not work any longer.</li>
48 <li><cite>RequestRedirect</cite> now uses a 301 status code. Previously a 302
49 status code was used incorrectly. If you want to continue using
50 this 302 code, use <tt class="docutils literal"><span class="pre">response</span> <span class="pre">=</span> <span class="pre">redirect(e.new_url,</span> <span class="pre">302)</span></tt>.</li>
51 <li><cite>lazy_property</cite> is now called <cite>cached_property</cite>. The alias for
52 the old name will disappear in Werkzeug 0.3.</li>
53 <li><cite>match</cite> can now raise <cite>MethodNotAllowed</cite> if configured for
54 methods and there was no method for that request.</li>
55 <li>The <cite>response_body</cite> attribute on the response object is now called
56 <cite>data</cite>. With Werkzeug 0.3 the old name will not work any longer.</li>
57 <li>The file-like methods on the response object are deprecated. If
58 you want to use the response object as file like object use the
59 <cite>Response</cite> class or a subclass of <cite>BaseResponse</cite> and mix the new
60 <cite>ResponseStreamMixin</cite> class and use <cite>response.stream</cite>.</li>
61 </ul>
62 </dd>
63 </dl>
64 </div>
65
66 <div style="clear:both"></div>
67 </div>
68 <div class="footer">
69 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
70 BSD license.
71 </div>
72 </div>
73 </body>
74 </html>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Debugging System // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Debugging System</h2>
18
19 <p>Depending on the WSGI gateway/server, exceptions are handled differently. But
20 most of the time, exceptions go to stderr or the error log.</p>
21 <p>Since this is not the best debugging environment, Werkzeug provides a WSGI
22 middleware that renders nice debugging tracebacks, optionally with an AJAX
23 based debugger (which allows to execute code in the context of the
24 traceback&#8217;s frames).</p>
25 <p>Usage:</p>
26 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">DebuggedApplication</span><span class="p">,</span> <span class="n">run_simple</span>
27 <span class="k">from</span> <span class="nn">myapplication</span> <span class="k">import</span> <span class="n">application</span>
28
29 <span class="n">application</span> <span class="o">=</span> <span class="n">DebuggedApplication</span><span class="p">(</span><span class="n">application</span><span class="p">,</span> <span class="n">evalex</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
30
31 <span class="n">run_simple</span><span class="p">(</span><span class="s">&#39;localhost&#39;</span><span class="p">,</span> <span class="mf">5000</span><span class="p">,</span> <span class="n">application</span><span class="p">)</span>
32 </pre></div>
33 <p>This code spawns a debugging server on <cite>localhost:5000</cite> with the debugger
34 enabled. If you set <cite>evalex</cite> to <cite>False</cite>, the debugger is disabled.</p>
35 <div class="warning">
36 <p class="first admonition-title">Warning</p>
37 <p class="last">Don&#8217;t ever use the debugging middleware in a production environment since it
38 can leak internal information that is part of the variable debug table.
39 Even worse is a debugger with the <cite>evalex</cite> feature enabled, since it can be
40 used to execute code on the server!</p>
41 </div>
42
43 <div style="clear:both"></div>
44 </div>
45 <div class="footer">
46 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
47 BSD license.
48 </div>
49 </div>
50 </body>
51 </html>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Deploying WSGI Applications // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Deploying WSGI Applications</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#generic-gateways" id="id1" name="id1">Generic Gateways</a></li>
22 <li><a class="reference" href="#apache-centric-gateways" id="id2" name="id2">Apache Centric Gateways</a></li>
23 <li><a class="reference" href="#example-configuration" id="id3" name="id3">Example Configuration</a></li>
24 <li><a class="reference" href="#selecting-the-best-gateway" id="id4" name="id4">Selecting the Best Gateway</a></li>
25 </ul>
26 </div>
27
28 <p>The best thing about WSGI is that there are so many gateways for productive
29 usage. If you want to switch from <a class="reference" href="http://httpd.apache.org/">Apache</a> 2 with <a class="reference" href="http://code.google.com/p/modwsgi/">mod_wsgi</a> to <a class="reference" href="http://www.lighttpd.net/">lighttpd</a>
30 with <a class="reference" href="http://www.fastcgi.com/">FastCGI</a>, it&#8217;s a matter of a few minutes. If you want to distribute
31 your application on an USB stick for presentation, you can bundle everything
32 into an executable using <a class="reference" href="http://www.py2exe.org/">py2exe</a> and the built-in <cite>wsgiref</cite> module.</p>
33 <p>The following list shows the most often used server configurations and how
34 you can serve your WSGI applications.</p>
35 <div class="section">
36 <h3 id="generic-gateways">Generic Gateways</h3>
37 <p>The following gateways are webserver agnostic and will work with any webserver
38 out there that suppors the underlaying interface:</p>
39 <dl>
40 <dt><cite>FastCGI</cite></dt>
41 <dd>For FastCGI, two well known python libraries exist. One is implemented in
42 Python and handles pretty every FastCGI configuration. It&#8217;s called <a class="reference" href="http://cheeseshop.python.org/pypi/flup">flup</a>
43 and supports also some other interfaces besides FastCGI. The other one is
44 called <a class="reference" href="http://cheeseshop.python.org/pypi/python-fastcgi">python-fastcgi</a> and is implemented in C. However, the latter has
45 a much smaller feature set.</dd>
46 <dt><cite>SCGI</cite></dt>
47 <dd><a class="reference" href="http://www.mems-exchange.org/software/scgi/">SCGI</a> is a simple protocol with the same benefits of FastCGI (persistent
48 interpreters). Like <cite>FastCGI</cite>, this is supported by <a class="reference" href="http://cheeseshop.python.org/pypi/flup">flup</a>.</dd>
49 <dt><cite>CGI</cite></dt>
50 <dd>CGI is known to work on every major webserver out there, but has the
51 disadvantage of being slow. With Python 2.5 and onwards, you can use
52 <cite>wsgiref</cite> as CGI gateway. In older Python versions, you can either install
53 <cite>wsgiref</cite> yourself or use the CGI wrapper code from <a class="reference" href="http://www.python.org/dev/peps/pep-0333/">PEP 333</a>.</dd>
54 </dl>
55 </div>
56 <div class="section">
57 <h3 id="apache-centric-gateways">Apache Centric Gateways</h3>
58 <p>The following gateways are either written especially for the Apache webserver
59 or work best with it.</p>
60 <dl>
61 <dt><cite>mod_wsgi</cite></dt>
62 <dd>Without doubt the best deployment platform for the Apache webserver is
63 <a class="reference" href="http://code.google.com/p/modwsgi/">mod_wsgi</a>. Even though it&#8217;s an Apache module, there are also ways to
64 switch the underlaying interpreter into another user context. This allows
65 you to mass host Python applications like you would do with
66 <cite>suexec</cite>/<cite>suphp</cite>.</dd>
67 <dt><cite>mod_python</cite></dt>
68 <dd><p class="first">For a long time, <a class="reference" href="http://www.modpython.org/">mod_python</a> was the preferred way to deploy Python
69 applications on the Apache webserver, and frameworks like <a class="reference" href="http://www.djangoproject.com/">Django</a> still
70 recommend this setup. However, some bugs in mod_python make it hard to
71 deploy WSGI applications, especially the undefined behavior of
72 <cite>SCRIPT_NAME</cite> makes routing hard.</p>
73 <p class="last">If you want to use mod_python or have to use it, you can try the
74 mod_python WSGI wrapper from this <a class="reference" href="http://www.aminus.org/blogs/index.php/fumanchu/2005/11/06/wsgi_wrapper_for_mod_python">blog post about mod_python and WSGI</a>.</p>
75 </dd>
76 <dt><cite>AJP</cite></dt>
77 <dd><a class="reference" href="http://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html">AJP</a> is the Apache JServ Protocol and is used by Tomcat. <a class="reference" href="http://cheeseshop.python.org/pypi/flup">flup</a>
78 provides ways to talk it.</dd>
79 </dl>
80 </div>
81 <div class="section">
82 <h3 id="example-configuration">Example Configuration</h3>
83 <p>Here is a small example configuration for Apache and mod_wsgi:</p>
84 <div class="syntax"><pre><span class="k">from</span> <span class="nn">yourapplicaiton</span> <span class="k">import</span> <span class="n">YourWSGIApplication</span>
85
86 <span class="c"># mod_wsgi just wants an object called `application` it will use</span>
87 <span class="c"># for dispatching. Pretty simple, huh?</span>
88 <span class="n">application</span> <span class="o">=</span> <span class="n">YourWSGIApplication</span><span class="p">(</span><span class="n">configuration</span><span class="o">=</span><span class="s">&#39;probably&#39;</span><span class="p">,</span> <span class="n">goes</span><span class="o">=</span><span class="s">&#39;here&#39;</span><span class="p">)</span>
89 </pre></div>
90 <p>Save it as <cite>yourapplication.wsgi</cite> and add this to your virtual host config:</p>
91 <div class="syntax"><pre><span class="nb">WSGIScriptAlias</span> / <span class="sx">/path/to/yourapplication.wsgi/</span>
92 </pre></div>
93 <p>(Note the trailing slash). Or, if you want to have the application in a
94 subfolder:</p>
95 <div class="syntax"><pre><span class="nb">WSGIScriptAlias</span> <span class="sx">/foo</span> <span class="sx">/path/to/yourapplication.wsgi</span>
96 </pre></div>
97 <p>(Without the trailing slash). Detailed usage examples for the WSGI gateways
98 usually come with the gateways or can be found in their wikis.</p>
99 </div>
100 <div class="section">
101 <h3 id="selecting-the-best-gateway">Selecting the Best Gateway</h3>
102 <p>Selecting the best gateway is mainly a matter of what you have available on
103 your server and how your application is written. Some applications might not
104 be thread safe, others work better with threads etc.</p>
105 <p>The following IRC channels on <a class="reference" href="http://freenode.net/">freenode</a> deal a lot with WSGI and you might
106 be able to find some help there:</p>
107 <ul class="simple">
108 <li><tt class="docutils literal"><span class="pre">#wsgi</span></tt></li>
109 <li><tt class="docutils literal"><span class="pre">#pylons</span></tt></li>
110 <li><tt class="docutils literal"><span class="pre">#pocoo</span></tt></li>
111 <li><tt class="docutils literal"><span class="pre">#pythonpaste</span></tt></li>
112 </ul>
113 </div>
114
115 <div style="clear:both"></div>
116 </div>
117 <div class="footer">
118 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
119 BSD license.
120 </div>
121 </div>
122 </body>
123 </html>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>HTTP Exceptions // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>HTTP Exceptions</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#error-classes" id="id1" name="id1">Error Classes</a></li>
22 <li><a class="reference" href="#baseclass" id="id2" name="id2">Baseclass</a></li>
23 <li><a class="reference" href="#special-http-exceptions" id="id3" name="id3">Special HTTP Exceptions</a></li>
24 <li><a class="reference" href="#simple-aborting" id="id4" name="id4">Simple Aborting</a></li>
25 <li><a class="reference" href="#custom-errors" id="id5" name="id5">Custom Errors</a></li>
26 </ul>
27 </div>
28
29 <p>This module implements a number of Python exceptions you can raise from
30 within your views to trigger a standard non 200 response.</p>
31 <div class="section">
32 <h3 id="usage-example">Usage Example</h3>
33 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">BaseRequest</span><span class="p">,</span> <span class="n">responder</span>
34 <span class="k">from</span> <span class="nn">werkzeug.exceptions</span> <span class="k">import</span> <span class="n">HTTPException</span><span class="p">,</span> <span class="n">NotFound</span>
35
36 <span class="k">def</span> <span class="nf">view</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
37 <span class="k">raise</span> <span class="n">NotFound</span><span class="p">()</span>
38
39 <span class="nd">@responder</span>
40 <span class="k">def</span> <span class="nf">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
41 <span class="n">request</span> <span class="o">=</span> <span class="n">BaseRequest</span><span class="p">(</span><span class="n">environ</span><span class="p">)</span>
42 <span class="k">try</span><span class="p">:</span>
43 <span class="k">return</span> <span class="n">view</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
44 <span class="k">except</span> <span class="n">HTTPException</span><span class="p">,</span> <span class="n">e</span><span class="p">:</span>
45 <span class="k">return</span> <span class="n">e</span>
46 </pre></div>
47 <p>As you can see from this example those exceptions are callable WSGI
48 applications. Because of Python 2.3 / 2.4 compatibility those do not
49 extend from the response objects but only from the python exception
50 class.</p>
51 <p>As a matter of fact they are not Werkzeug response objects. However you
52 can get a response object by calling <tt class="docutils literal"><span class="pre">get_response()</span></tt> on a HTTP
53 exception.</p>
54 <p>Keep in mind that you have to pass an environment to <tt class="docutils literal"><span class="pre">get_response()</span></tt>
55 because some errors fetch additional information from the WSGI
56 environment.</p>
57 <p>If you want to hook in a different exception page to say, an 404 status
58 code, you can add a second except for a specific subclass of an error:</p>
59 <div class="syntax"><pre><span class="nd">@responder</span>
60 <span class="k">def</span> <span class="nf">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
61 <span class="n">request</span> <span class="o">=</span> <span class="n">BaseRequest</span><span class="p">(</span><span class="n">environ</span><span class="p">)</span>
62 <span class="k">try</span><span class="p">:</span>
63 <span class="k">return</span> <span class="n">view</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
64 <span class="k">except</span> <span class="n">NotFound</span><span class="p">,</span> <span class="n">e</span><span class="p">:</span>
65 <span class="k">return</span> <span class="n">not_found</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
66 <span class="k">except</span> <span class="n">HTTPException</span><span class="p">,</span> <span class="n">e</span><span class="p">:</span>
67 <span class="k">return</span> <span class="n">e</span>
68 </pre></div>
69 </div>
70 <p>All the exceptions must be imported from the <cite>werkzeug.exceptions</cite> module
71 unless you are using the <cite>abort</cite> callable which is available in <cite>werkzeug</cite>
72 too.</p>
73 <div class="section">
74 <h3 id="error-classes">Error Classes</h3>
75 <p>The following error classes exist in Werkzeug:</p>
76 <dl>
77 <dt><strong>class</strong> <cite id="werkzeug.exceptions.BadRequest">BadRequest</cite></dt>
78 <dd><p class="first"><em>400</em> <cite>Bad Request</cite></p>
79 <p class="last">Raise if the browser send something to the application the application
80 or server cannot handle.</p>
81 </dd>
82 </dl>
83 <dl>
84 <dt><strong>class</strong> <cite id="werkzeug.exceptions.Unauthorized">Unauthorized</cite></dt>
85 <dd><p class="first"><em>401</em> <cite>Unauthorized</cite></p>
86 <p class="last">Raise if the user is not authorized. Also used if you want to use HTTP
87 basic auth.</p>
88 </dd>
89 </dl>
90 <dl>
91 <dt><strong>class</strong> <cite id="werkzeug.exceptions.Forbidden">Forbidden</cite></dt>
92 <dd><p class="first"><em>403</em> <cite>Forbidden</cite></p>
93 <p class="last">Raise if the user doesn&#8217;t have the permission for the requested resource
94 but was authenticated.</p>
95 </dd>
96 </dl>
97 <dl>
98 <dt><strong>class</strong> <cite id="werkzeug.exceptions.NotFound">NotFound</cite></dt>
99 <dd><p class="first"><em>404</em> <cite>Not Found</cite></p>
100 <p class="last">Raise if a resource does not exist and never existed.</p>
101 </dd>
102 </dl>
103 <dl>
104 <dt><strong>class</strong> <cite id="werkzeug.exceptions.MethodNotAllowed">MethodNotAllowed</cite></dt>
105 <dd><p class="first"><em>405</em> <cite>Method Not Allowed</cite></p>
106 <p>Raise if the server used a method the resource does not handle. For
107 example <cite>POST</cite> if the resource is view only. Especially useful for REST.</p>
108 <p class="last">The first argument for this exception should be a list of allowed methods.
109 Strictly speaking the response would be invalid if you don&#8217;t provide valid
110 methods in the header which you can do with that list.</p>
111 </dd>
112 </dl>
113 <dl>
114 <dt><strong>class</strong> <cite id="werkzeug.exceptions.NotAcceptable">NotAcceptable</cite></dt>
115 <dd><p class="first"><em>406</em> <cite>Not Acceptable</cite></p>
116 <p class="last">Raise if the server cant return any content conforming to the
117 <cite>Accept</cite> headers of the client.</p>
118 </dd>
119 </dl>
120 <dl>
121 <dt><strong>class</strong> <cite id="werkzeug.exceptions.RequestTimeout">RequestTimeout</cite></dt>
122 <dd><p class="first"><em>408</em> <cite>Request Timeout</cite></p>
123 <p class="last">Raise to signalize a timeout.</p>
124 </dd>
125 </dl>
126 <dl>
127 <dt><strong>class</strong> <cite id="werkzeug.exceptions.Gone">Gone</cite></dt>
128 <dd><p class="first"><em>410</em> <cite>Gone</cite></p>
129 <p class="last">Raise if a resource existed previously and went away without new location.</p>
130 </dd>
131 </dl>
132 <dl>
133 <dt><strong>class</strong> <cite id="werkzeug.exceptions.LengthRequired">LengthRequired</cite></dt>
134 <dd><p class="first"><em>411</em> <cite>Length Required</cite></p>
135 <p class="last">Raise if the browser submitted data but no <tt class="docutils literal"><span class="pre">Content-Length</span></tt> header which
136 is required for the kind of processing the server does.</p>
137 </dd>
138 </dl>
139 <dl>
140 <dt><strong>class</strong> <cite id="werkzeug.exceptions.PreconditionFailed">PreconditionFailed</cite></dt>
141 <dd><p class="first"><em>412</em> <cite>Precondition Failed</cite></p>
142 <p class="last">Status code used in combination with <tt class="docutils literal"><span class="pre">If-Match</span></tt>, <tt class="docutils literal"><span class="pre">If-None-Match</span></tt>, or
143 <tt class="docutils literal"><span class="pre">If-Unmodified-Since</span></tt>.</p>
144 </dd>
145 </dl>
146 <dl>
147 <dt><strong>class</strong> <cite id="werkzeug.exceptions.RequestEntityTooLarge">RequestEntityTooLarge</cite></dt>
148 <dd><p class="first"><em>413</em> <cite>Request Entity Too Large</cite></p>
149 <p class="last">The status code one should return if the data submitted exceeded a given
150 limit.</p>
151 </dd>
152 </dl>
153 <dl>
154 <dt><strong>class</strong> <cite id="werkzeug.exceptions.RequestURITooLarge">RequestURITooLarge</cite></dt>
155 <dd><p class="first"><em>414</em> <cite>Request URI Too Large</cite></p>
156 <p class="last">Like <em>413</em> but for too long URLs.</p>
157 </dd>
158 </dl>
159 <dl>
160 <dt><strong>class</strong> <cite id="werkzeug.exceptions.UnsupportedMediaType">UnsupportedMediaType</cite></dt>
161 <dd><p class="first"><em>415</em> <cite>Unsupported Media Type</cite></p>
162 <p class="last">The status code returned if the server is unable to handle the media type
163 the client transmitted.</p>
164 </dd>
165 </dl>
166 <dl>
167 <dt><strong>class</strong> <cite id="werkzeug.exceptions.InternalServerError">InternalServerError</cite></dt>
168 <dd><p class="first"><em>500</em> <cite>Internal Server Error</cite></p>
169 <p class="last">Raise if an internal server error occoured. This is a good fallback if an
170 unknown error occoured in the dispatcher.</p>
171 </dd>
172 </dl>
173 <dl>
174 <dt><strong>class</strong> <cite id="werkzeug.exceptions.NotImplemented">NotImplemented</cite></dt>
175 <dd><p class="first"><em>501</em> <cite>Not Implemented</cite></p>
176 <p class="last">Raise if the application does not support the action requested by the
177 browser.</p>
178 </dd>
179 </dl>
180 <dl>
181 <dt><strong>class</strong> <cite id="werkzeug.exceptions.BadGateway">BadGateway</cite></dt>
182 <dd><p class="first"><em>502</em> <cite>Bad Gateway</cite></p>
183 <p class="last">If you do proxing in your application you should return this status code
184 if you received an invalid response from the upstream server it accessed
185 in attempting to fulfill the request.</p>
186 </dd>
187 </dl>
188 <dl>
189 <dt><strong>class</strong> <cite id="werkzeug.exceptions.ServiceUnavailable">ServiceUnavailable</cite></dt>
190 <dd><p class="first"><em>503</em> <cite>Service Unavailable</cite></p>
191 <p class="last">Status code you should return if a service is temporarily unavailable.</p>
192 </dd>
193 </dl>
194 </div>
195 <div class="section">
196 <h3 id="baseclass">Baseclass</h3>
197 <p>All the exceptions implement this common interface:</p>
198 <dl>
199 <dt><strong>class</strong> <cite id="werkzeug.exceptions.HTTPException">HTTPException</cite></dt>
200 <dd><p class="first">Baseclass for all HTTP exceptions. This exception can be called as WSGI
201 application to render a default error page or you can catch the subclasses
202 of it independently and render nicer error messages.</p>
203 <dl>
204 <dt><cite id="werkzeug.exceptions.HTTPException.get_response">get_response</cite> <tt class="func-signature docutils literal"><span class="pre">(environ)</span></tt></dt>
205 <dd>Get a response object.</dd>
206 </dl>
207 <dl class="last">
208 <dt><cite id="werkzeug.exceptions.HTTPException.__call__">__call__</cite> <tt class="func-signature docutils literal"><span class="pre">(environ,</span> <span class="pre">start_response)</span></tt></dt>
209 <dd>Call the exception as WSGI application.</dd>
210 </dl>
211 </dd>
212 </dl>
213 </div>
214 <div class="section">
215 <h3 id="special-http-exceptions">Special HTTP Exceptions</h3>
216 <p>Starting with Werkzeug 0.3 some of the builtin classes raise exceptions that
217 look like regular python exceptions (eg <cite>KeyError</cite>) but are <cite>BadRequest</cite>
218 HTTP exceptions at the same time. This decision was made to simplify a
219 common pattern where you want to abort if the client tampered with the
220 submitted form data in a way that the application can&#8217;t recover properly and
221 should abort with <tt class="docutils literal"><span class="pre">400</span> <span class="pre">BAD</span> <span class="pre">REQUEST</span></tt>.</p>
222 <p>Assuming the application catches all HTTP exceptions and reacts to them
223 properly a view function could do the following savely and doesn&#8217;t have to
224 check if the keys exist:</p>
225 <div class="syntax"><pre><span class="k">def</span> <span class="nf">new_post</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
226 <span class="n">post</span> <span class="o">=</span> <span class="n">Post</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="p">[</span><span class="s">&#39;title&#39;</span><span class="p">],</span> <span class="n">body</span><span class="o">=</span><span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="p">[</span><span class="s">&#39;body&#39;</span><span class="p">])</span>
227 <span class="n">post</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
228 <span class="k">return</span> <span class="n">redirect</span><span class="p">(</span><span class="n">post</span><span class="o">.</span><span class="n">url</span><span class="p">)</span>
229 </pre></div>
230 <p>If <cite>title</cite> or <cite>body</cite> are missing in the form a <cite>MultiDict.KeyError</cite> will be
231 raised which behaves like a <cite>KeyError</cite> but also a <cite>BadRequest</cite> exception.</p>
232 </div>
233 <div class="section">
234 <h3 id="simple-aborting">Simple Aborting</h3>
235 <p>Sometimes it&#8217;s convenient to just raise an exception by the error code,
236 without importing the exception and looking up the name etc. For this
237 purpose there is the <cite>abort</cite> function.</p>
238 <p>It can be passed a WSGI application or a status code. If a status code
239 is given it&#8217;s looked up in the list of exceptions from above and will
240 raise that exception, if passed a WSGI application it will wrap it in
241 a proxy WSGI exception and raise that:</p>
242 <div class="syntax"><pre><span class="n">abort</span><span class="p">(</span><span class="mf">404</span><span class="p">)</span>
243 <span class="n">abort</span><span class="p">(</span><span class="n">Response</span><span class="p">(</span><span class="s">&#39;Hello World&#39;</span><span class="p">))</span>
244 </pre></div>
245 <p>If you want to use this functionality with custom excetions you can
246 create an instance of the aborter class:</p>
247 <dl>
248 <dt><strong>class</strong> <cite id="werkzeug.exceptions.Aborter">Aborter</cite></dt>
249 <dd><p class="first">When passed a dict of code -&gt; exception items it can be used as
250 callable that raises exceptions. If the first argument to the
251 callable is a integer it will be looked up in the mapping, if it&#8217;s
252 a WSGI application it will be raised in a proxy exception.</p>
253 <p class="last">The rest of the arguments are forwarded to the exception constructor.</p>
254 </dd>
255 </dl>
256 </div>
257 <div class="section">
258 <h3 id="custom-errors">Custom Errors</h3>
259 <p>As you can see from the list above not all status codes are available as
260 errors. Especially redirects and ather non 200 status codes that
261 represent do not represent errors are missing. For redirects you can use
262 the <cite>redirect</cite> function from the utilities.</p>
263 <p>If you want to add an error yourself you can subclass <cite>HTTPException</cite>:</p>
264 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug.exceptions</span> <span class="k">import</span> <span class="n">HTTPException</span>
265
266 <span class="k">class</span> <span class="nc">PaymentRequred</span><span class="p">(</span><span class="n">HTTPException</span><span class="p">):</span>
267 <span class="n">code</span> <span class="o">=</span> <span class="mf">402</span>
268 <span class="n">description</span> <span class="o">=</span> <span class="s">&#39;&lt;p&gt;Payment required.&lt;/p&gt;&#39;</span>
269 </pre></div>
270 <p>This is the minimal code you need for your own exception. If you want to
271 add more logic to the errors you can override the <cite>get_description()</cite>,
272 <cite>get_body()</cite>, <cite>get_headers()</cite> and <cite>get_response()</cite> methods. In any case
273 you should have a look at the sourcecode of the exceptions module.</p>
274 <p>You can override the default description in the constructor with the
275 <cite>description</cite> parameter (it&#8217;s the first argument for all exceptions
276 except of the <cite>MethodNotAllowed</cite> which accepts a list of allowed methods
277 as first argument):</p>
278 <div class="syntax"><pre><span class="k">raise</span> <span class="n">BadRequest</span><span class="p">(</span><span class="s">&#39;Request failed because X was not present&#39;</span><span class="p">)</span>
279 </pre></div>
280 </div>
281
282 <div style="clear:both"></div>
283 </div>
284 <div class="footer">
285 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
286 BSD license.
287 </div>
288 </div>
289 </body>
290 </html>
Binary diff not shown
Binary diff not shown
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Documentation Overview // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Documentation Overview</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#reference" id="id1" name="id1">Reference</a></li>
22 <li><a class="reference" href="#general-development-information" id="id2" name="id2">General Development Information</a></li>
23 </ul>
24 </div>
25
26 <p>Welcome to the Werkzeug documentation.</p>
27 <div class="section">
28 <h3 id="reference">Reference</h3>
29 <p>Documentation regarding the Werkzeug modules:</p>
30 <ul class="simple">
31 <li><a class="reference" href="installation.html">Installing Werkzeug</a></li>
32 <li><a class="reference" href="terms.html">Important Terms</a> &#8212; read this before reading the
33 documentation</li>
34 <li><a class="reference" href="tutorial.html">Tutorial</a> &#8212; getting started with Werkzeug</li>
35 <li><a class="reference" href="unicode.html">Unicode</a> &#8212; short introduction into unicode in python
36 and how Werkzeug encodes and decodes data.</li>
37 <li><a class="reference" href="wrappers.html">Wrappers</a> &#8212; request and response objects</li>
38 <li><a class="reference" href="routing.html">Routing System</a> &#8212; a powerful URL dispatcher
39 and builder</li>
40 <li><a class="reference" href="templates.html">Mini Templates</a> &#8212; a minimal templating system</li>
41 <li><a class="reference" href="script.html">Management Script Utilities</a> &#8212; tools to write simple
42 management scripts</li>
43 <li><a class="reference" href="test.html">Test Utilities</a> &#8212; unittest support tools</li>
44 <li><a class="reference" href="exceptions.html">HTTP Exceptions</a> &#8212; exceptions for HTTP</li>
45 <li><a class="reference" href="utils.html">Utilities</a> &#8212; useful classes and functions</li>
46 <li><a class="reference" href="local.html">Context Locals</a> &#8212; a WSGI centric version of <cite>thread.local</cite>.</li>
47 <li><a class="reference" href="debug.html">Debugging System</a> &#8212; an interactive debugger.</li>
48 <li><a class="reference" href="serving.html">Serving Applications</a> &#8212; serving WSGI applications.</li>
49 <li><a class="reference" href="api_stability.html">API Stability</a> &#8212; API stability</li>
50 </ul>
51 </div>
52 <div class="section">
53 <h3 id="general-development-information">General Development Information</h3>
54 <p>This part of the documentation mainly explains how to develop WSGI
55 applications. This is also interesting if you don&#8217;t want to use
56 Werkzeug but other WSGI utilities; the ideas are the same.</p>
57 <ul class="simple">
58 <li><a class="reference" href="wsgihowto.html">How WSGI Works</a> &#8212; short introduction to WSGI and
59 Werkzeug.</li>
60 <li><a class="reference" href="organizing.html">Organizing Code</a> &#8212; gives you an idea how you can
61 organize your code when using Werkzeug.</li>
62 <li><a class="reference" href="libraries.html">Other Libraries</a> &#8212; links to other libraries you
63 can use with Werkzeug.</li>
64 <li><a class="reference" href="deploying.html">Deploying WSGI Applications</a> &#8212; ready for production?
65 This page covers all the details you have to know to deploy your
66 application on various webservers.</li>
67 </ul>
68 </div>
69
70 <div style="clear:both"></div>
71 </div>
72 <div class="footer">
73 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
74 BSD license.
75 </div>
76 </div>
77 </body>
78 </html>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Installation // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Installation</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><p class="first"><a class="reference" href="#installing-a-released-version" id="id1" name="id1">Installing a released version</a></p>
22 <ul class="simple">
23 <li><a class="reference" href="#as-a-python-egg-via-easy-install" id="id2" name="id2">As a Python egg (via easy_install)</a></li>
24 <li><a class="reference" href="#from-the-tarball-release" id="id3" name="id3">From the tarball release</a></li>
25 </ul>
26 </li>
27 <li><p class="first"><a class="reference" href="#installing-the-development-version" id="id4" name="id4">Installing the development version</a></p>
28 <ul class="simple">
29 <li><a class="reference" href="#if-you-want-to-play-around-with-the-code" id="id5" name="id5">If you want to play around with the code</a></li>
30 <li><a class="reference" href="#if-you-just-want-the-latest-features-and-use-them" id="id6" name="id6">If you just want the latest features and use them</a></li>
31 </ul>
32 </li>
33 <li><a class="reference" href="#documentation" id="id7" name="id7">Documentation</a></li>
34 </ul>
35 </div>
36
37 <p>Werkzeug requires at least Python 2.3 to work correctly.</p>
38 <div class="section">
39 <h3 id="installing-a-released-version">Installing a released version</h3>
40 <div class="section">
41 <h4 id="as-a-python-egg-via-easy-install">As a Python egg (via easy_install)</h4>
42 <p>You can install the most recent Werkzeug version using <a class="reference" href="http://peak.telecommunity.com/DevCenter/EasyInstall">easy_install</a>:</p>
43 <div class="syntax"><pre><span class="n">sudo</span> <span class="n">easy_install</span> <span class="n">Werkzeug</span>
44 </pre></div>
45 <p>This will install a Werkzeug egg in your Python installation&#8217;s <cite>site-packages</cite>
46 directory.</p>
47 </div>
48 <div class="section">
49 <h4 id="from-the-tarball-release">From the tarball release</h4>
50 <ol class="arabic simple">
51 <li>Download the most recent tarball from the <a class="reference" href="http://werkzeug.pocoo.org/download.html">download page</a>.</li>
52 <li>Unpack the tarball.</li>
53 <li><tt class="docutils literal"><span class="pre">sudo</span> <span class="pre">python</span> <span class="pre">setup.py</span> <span class="pre">install</span></tt></li>
54 </ol>
55 <p>Note that the last command will automatically download and install
56 <a class="reference" href="http://peak.telecommunity.com/DevCenter/setuptools">setuptools</a> if you don&#8217;t already have it installed. This requires a working
57 Internet connection.</p>
58 <p>This will install Werkzeug into your Python installation&#8217;s <cite>site-packages</cite>
59 directory.</p>
60 </div>
61 </div>
62 <div class="section">
63 <h3 id="installing-the-development-version">Installing the development version</h3>
64 <div class="section">
65 <h4 id="if-you-want-to-play-around-with-the-code">If you want to play around with the code</h4>
66 <ol class="arabic simple">
67 <li>Install <a class="reference" href="http://selenic.com/mercurial/">Mercurial</a></li>
68 <li><tt class="docutils literal"><span class="pre">hg</span> <span class="pre">clone</span> <span class="pre">http://dev.pocoo.org/hg/werkzeug-main</span> <span class="pre">werkzeug</span></tt></li>
69 <li><tt class="docutils literal"><span class="pre">cd</span> <span class="pre">werkzeug</span></tt></li>
70 <li><tt class="docutils literal"><span class="pre">ln</span> <span class="pre">-s</span> <span class="pre">werkzeug</span> <span class="pre">/usr/lib/python2.X/site-packages</span></tt></li>
71 </ol>
72 <p>As an alternative to step 4 you can also do <tt class="docutils literal"><span class="pre">python</span> <span class="pre">setup.py</span> <span class="pre">develop</span></tt> which
73 will install the package via setuptools in development mode.</p>
74 </div>
75 <div class="section">
76 <h4 id="if-you-just-want-the-latest-features-and-use-them">If you just want the latest features and use them</h4>
77 <div class="syntax"><pre><span class="n">sudo</span> <span class="n">easy_install</span> <span class="n">Werkzeug</span><span class="o">==</span><span class="n">dev</span>
78 </pre></div>
79 <p>This will install a Werkzeug egg containing the latest mercurial tip in
80 your Python installation&#8217;s <cite>site-packages</cite> directory. Every time the
81 command is run, the sources are updated from the mercurial repository.</p>
82 </div>
83 </div>
84 <div class="section">
85 <h3 id="documentation">Documentation</h3>
86 <p>The egg builds include a documentation which is available in the <cite>docs</cite> folder
87 of the egg. If you&#8217;re running Linux you will find the documentation here:</p>
88 <div class="syntax"><pre><span class="nb">file</span><span class="p">:</span><span class="o">///</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">python2</span><span class="o">.</span><span class="n">X</span><span class="o">/</span><span class="n">site</span><span class="o">-</span><span class="n">packages</span><span class="o">/</span><span class="n">Werkzeug</span><span class="o">-</span><span class="n">Y</span><span class="o">.</span><span class="n">Z</span><span class="o">-</span><span class="n">py2</span><span class="o">.</span><span class="n">X</span><span class="o">.</span><span class="n">egg</span><span class="o">/</span><span class="n">docs</span><span class="o">/</span><span class="n">index</span><span class="o">.</span><span class="n">html</span>
89 </pre></div>
90 <p>where <cite>X</cite>, <cite>Y</cite> and <cite>Z</cite> must be replaced by the Python/Werkzeug version number.</p>
91 </div>
92
93 <div style="clear:both"></div>
94 </div>
95 <div class="footer">
96 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
97 BSD license.
98 </div>
99 </div>
100 </body>
101 </html>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Other Libraries // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Other Libraries</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#database-layers" id="id1" name="id1">Database Layers</a></li>
22 <li><a class="reference" href="#template-engines" id="id2" name="id2">Template Engines</a></li>
23 <li><a class="reference" href="#form-validation" id="id3" name="id3">Form Validation</a></li>
24 <li><a class="reference" href="#tools-and-utilities" id="id4" name="id4">Tools and Utilities</a></li>
25 </ul>
26 </div>
27
28 <p>Because Werkzeug is just a thin layer over WSGI, it&#8217;s very easy to use WSGI
29 middlewares together with Werkzeug-powered applications. It&#8217;s also possible
30 to just use small parts of Werkzeug like the URL mapper etc. with complete
31 different implementations or frameworks.</p>
32 <p>Here a small list of libraries you may want to try out.</p>
33 <div class="section">
34 <h3 id="database-layers">Database Layers</h3>
35 <p>If you want to use relational databases in your application.</p>
36 <dl>
37 <dt><a class="reference" href="http://www.sqlalchemy.org/">SQLAlchemy</a></dt>
38 <dd>SQLAlchemy is a great database layer and object relational mapper that
39 lets you construct SQL queries using Python expressions. It also provides
40 connection pools and plays nicely with the WSGI standard.</dd>
41 <dt><a class="reference" href="http://elixir.ematia.de/">Elixir</a></dt>
42 <dd>Elixir is a declarative layer on top of the SQLAlchemy library. It is a
43 fairly thin wrapper, which provides the ability to create simple Python
44 classes that map directly to relational database tables (this pattern is
45 often referred to as the Active Record design pattern), providing many of
46 the benefits of traditional databases without losing the convenience of
47 Python objects.</dd>
48 <dt><a class="reference" href="https://storm.canonical.com/">Storm</a></dt>
49 <dd>Storm is an object-relational mapper (ORM) for Python developed at
50 Canonical. It has been in development for more than a year for use in
51 Canonical projects such as Launchpad, and has been released as an
52 open-source product.</dd>
53 </dl>
54 </div>
55 <div class="section">
56 <h3 id="template-engines">Template Engines</h3>
57 <p>Bigger applications deserve something better than minitmpl :)</p>
58 <dl>
59 <dt><a class="reference" href="http://genshi.edgewall.org/">Genshi</a></dt>
60 <dd>If you like XML template engines, check out Genshi. Ass-kicking
61 template engine, but unfortunately not the fastest.</dd>
62 <dt><a class="reference" href="http://www.makotemplates.org/">Mako</a></dt>
63 <dd>The fastest designer friendly template engine for Python. Similar
64 to ERB <a class="reference" href="http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/">http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/</a> in terms of
65 syntax, but with a powerful template inheritance system and multiple
66 namespaces.</dd>
67 <dt><a class="reference" href="http://jinja.pocoo.org/">Jinja</a></dt>
68 <dd>Sandboxed, Django-/Smarty-like template engine, but with inline
69 expressions that let you execute a subset of Python expressions in
70 your templates.</dd>
71 </dl>
72 </div>
73 <div class="section">
74 <h3 id="form-validation">Form Validation</h3>
75 <p>Here some form validation packages for WSGI applications:</p>
76 <dl>
77 <dt><a class="reference" href="http://dev.simplecodes.com/projects/wtforms">What The Forms</a></dt>
78 <dd>WTForms is a HTTP/HTML forms handling library, written in Python. It
79 handles definition, validation and rendering in a flexible and i18n
80 friendly way. It heavily reduces boilerplate and is completely unicode
81 aware.</dd>
82 <dt><a class="reference" href="http://code.google.com/p/newforms-extracted/">Newforms Extracted</a></dt>
83 <dd>This is a project to extract Django&#8217;s newforms and make that package
84 usable by other projects, since Django doesn&#8217;t seem interested in making
85 this code framework independent.</dd>
86 <dt><a class="reference" href="http://formencode.org/">FormEncode</a></dt>
87 <dd>FormEncode is a validation and form generation package. The validation
88 can be used separately from the form generation. The validation works on
89 compound data structures, with all parts being nestable. It is separate
90 from HTTP or any other input mechanism.</dd>
91 </dl>
92 </div>
93 <div class="section">
94 <h3 id="tools-and-utilities">Tools and Utilities</h3>
95 <p>Something&#8217;s missing? Check here first:</p>
96 <dl>
97 <dt><a class="reference" href="http://subdivi.de/~helmut/wsgitools/">wsgitools</a></dt>
98 <dd>Various small WSGI utilities like a minimal traceback or auth
99 middleware. It also includes an SCGI server.</dd>
100 <dt><a class="reference" href="http://pythonpaste.org/">Paste</a></dt>
101 <dd>Many tools for use with WSGI: dispatching, composition, simple
102 applications (e.g., file serving), and more.</dd>
103 <dt><a class="reference" href="http://routes.groovie.org/">Routes</a></dt>
104 <dd>A port of the Rails URL mapping system.</dd>
105 <dt><a class="reference" href="http://openidenabled.com/python-openid/">Python OpenID</a></dt>
106 <dd>The OpenID library with batteries included.</dd>
107 <dt><a class="reference" href="http://authkit.org/">AuthKit</a></dt>
108 <dd>WSGI Authentication and Authorization Tools. Built in support for
109 HTTP basic, HTTP digest, form, cookie and OpenID authentication methods
110 plus others.</dd>
111 </dl>
112 <p>You can find a more complete list on the <a class="reference" href="http://wsgi.org/wsgi/Middleware_and_Utilities">wsgi.org</a> webpage.</p>
113 </div>
114
115 <div style="clear:both"></div>
116 </div>
117 <div class="footer">
118 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
119 BSD license.
120 </div>
121 </div>
122 </body>
123 </html>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Context Locals // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Context Locals</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#nutshell" id="id1" name="id1">Nutshell</a></li>
22 <li><a class="reference" href="#objects" id="id2" name="id2">Objects</a></li>
23 </ul>
24 </div>
25
26 <p>Sooner or later you have some things you want to have in every single view
27 or helper function or whatever. In PHP the way to go are global
28 variables. However that is not possible in WSGI applications without a
29 major drawback: As soon as you operate on the global namespace your
30 application is not thread safe any longer.</p>
31 <p>The python standard library comes with a utility called &#8220;thread locals&#8221;.
32 A thread local is a global object where you can put stuff on and get back
33 later in a thread safe way. That means whenever you set or get an object
34 to / from a thread local object the thread local object checks in which
35 thread you are and delivers the correct value.</p>
36 <p>This however has a few disadvantages. For example beside threads there
37 are other ways to handle concurrency in Python. A very popular approach
38 are greenlets. Also, whether every request gets its own thread is not
39 guaranteed in WSGI. It could be that a request is reusing a thread from
40 before and data is left in the thread local object.</p>
41 <div class="section">
42 <h3 id="nutshell">Nutshell</h3>
43 <p>Here a simple example how you can use werkzeug.local:</p>
44 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">Local</span><span class="p">,</span> <span class="n">LocalManager</span>
45
46 <span class="n">local</span> <span class="o">=</span> <span class="n">Local</span><span class="p">()</span>
47 <span class="n">local_manager</span> <span class="o">=</span> <span class="n">LocalManager</span><span class="p">([</span><span class="n">local</span><span class="p">])</span>
48
49 <span class="k">def</span> <span class="nf">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
50 <span class="n">local</span><span class="o">.</span><span class="n">request</span> <span class="o">=</span> <span class="n">request</span> <span class="o">=</span> <span class="n">Request</span><span class="p">(</span><span class="n">environ</span><span class="p">)</span>
51 <span class="o">...</span>
52
53 <span class="n">application</span> <span class="o">=</span> <span class="n">local_manager</span><span class="o">.</span><span class="n">make_middleware</span><span class="p">(</span><span class="n">application</span><span class="p">)</span>
54 </pre></div>
55 <p>Now what this code does is binding request to <cite>local.request</cite>. Every
56 other piece of code executed after this assignment in the same context can
57 safely access local.request and will get the same request object. The
58 <cite>make_middleware</cite> method on the local manager ensures that everything is
59 cleaned up after the request.</p>
60 <p>The same context means the same greenlet (if you&#8217;re using greenlets) in
61 the same thread and same process.</p>
62 <p>If a request object is not yet set on the local object and you try to
63 access it you will get an <cite>AttributeError</cite>. You can use <cite>getattr</cite> to avoid
64 that:</p>
65 <div class="syntax"><pre><span class="k">def</span> <span class="nf">get_request</span><span class="p">():</span>
66 <span class="k">return</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">local</span><span class="p">,</span> <span class="s">&#39;request&#39;</span><span class="p">,</span> <span class="bp">None</span><span class="p">)</span>
67 </pre></div>
68 <p>This will try to get the request or return <cite>None</cite> if the request is not
69 (yet?) available.</p>
70 <p>Note that local objects cannot manage themselves, for that you need a local
71 manager. You can pass a local manager multiple locals or add additionals
72 later by appending them to <cite>manager.locals</cite> and everytime the manager
73 cleans up it will clean up all the data left in the locals for this
74 context.</p>
75 </div>
76 <div class="section">
77 <h3 id="objects">Objects</h3>
78 <dl>
79 <dt><strong>class</strong> <cite id="werkzeug.local.LocalManager">LocalManager</cite></dt>
80 <dd><p class="first">Local objects cannot manage themselves. For that you need a local
81 manager. You can pass a local manager multiple locals or add them later
82 by appending them to <cite>manager.locals</cite>. Everytime the manager cleans up
83 it, will clean up all the data left in the locals for this context.</p>
84 <dl>
85 <dt><cite id="werkzeug.local.LocalManager.cleanup">cleanup</cite> <tt class="func-signature docutils literal"><span class="pre">()</span></tt></dt>
86 <dd>Manually clean up the data in the locals for this context. Call
87 this at the end of the request or use <cite>make_middleware()</cite>.</dd>
88 </dl>
89 <dl>
90 <dt><cite id="werkzeug.local.LocalManager.make_middleware">make_middleware</cite> <tt class="func-signature docutils literal"><span class="pre">(app)</span></tt></dt>
91 <dd>Wrap a WSGI application so that cleaning up happens after
92 request end.</dd>
93 </dl>
94 <dl>
95 <dt><cite id="werkzeug.local.LocalManager.middleware">middleware</cite> <tt class="func-signature docutils literal"><span class="pre">(func)</span></tt></dt>
96 <dd><p class="first">Like <cite>make_middleware</cite> but for decorating functions.</p>
97 <p>Example usage:</p>
98 <div class="syntax"><pre><span class="nd">@manager</span><span class="o">.</span><span class="n">middleware</span>
99 <span class="k">def</span> <span class="nf">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
100 <span class="o">...</span>
101 </pre></div>
102 <p class="last">The difference to <cite>make_middleware</cite> is that the function passed
103 will have all the arguments copied from the inner application
104 (name, docstring, module).</p>
105 </dd>
106 </dl>
107 <dl class="last">
108 <dt><cite id="werkzeug.local.LocalManager.get_ident">get_ident</cite> <tt class="func-signature docutils literal"><span class="pre">()</span></tt></dt>
109 <dd>Return the context identifier the local objects use internally for
110 this context. You cannot override this method to change the behavior
111 but use it to link other context local objects (such as SQLAlchemy&#8217;s
112 scoped sessions) to the Werkzeug locals.</dd>
113 </dl>
114 </dd>
115 </dl>
116 <dl>
117 <dt><strong>class</strong> <cite id="werkzeug.local.LocalProxy">LocalProxy</cite></dt>
118 <dd><p class="first">Acts as a proxy for a werkzeug local. Forwards all operations to
119 a proxied object. The only operations not supported for forwarding
120 are right handed operands and any kind of assignment.</p>
121 <p>Example usage:</p>
122 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">Local</span>
123 <span class="n">l</span> <span class="o">=</span> <span class="n">Local</span><span class="p">()</span>
124 <span class="n">request</span> <span class="o">=</span> <span class="n">l</span><span class="p">(</span><span class="s">&#39;request&#39;</span><span class="p">)</span>
125 <span class="n">user</span> <span class="o">=</span> <span class="n">l</span><span class="p">(</span><span class="s">&#39;user&#39;</span><span class="p">)</span>
126 </pre></div>
127 <p>Whenever something is bound to l.user / l.request the proxy objects
128 will forward all operations. If no object is bound a <cite>RuntimeError</cite>
129 will be raised.</p>
130 <dl>
131 <dt><cite id="werkzeug.local.LocalProxy._get_current_object">_get_current_object</cite> <tt class="func-signature docutils literal"><span class="pre">()</span></tt></dt>
132 <dd>Return the current object. This is useful if you want the real
133 object behind the proxy at a time for performance reasons or because
134 you want to pass the object into a different context.</dd>
135 </dl>
136 <p>Keep in mind that <tt class="docutils literal"><span class="pre">repr()</span></tt> is also forwarded, so if you want to find
137 out if you are dealing with a proxy you can do an <tt class="docutils literal"><span class="pre">isinstance()</span></tt> check:</p>
138 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">LocalProxy</span>
139 <span class="gp">&gt;&gt;&gt; </span><span class="nb">isinstance</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">LocalProxy</span><span class="p">)</span>
140 <span class="go">True</span>
141 </pre></div>
142 <p>You can also create proxy objects by hand:</p>
143 <div class="last"><div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">Local</span><span class="p">,</span> <span class="n">LocalProxy</span>
144 <span class="n">local</span> <span class="o">=</span> <span class="n">Local</span><span class="p">()</span>
145 <span class="n">request</span> <span class="o">=</span> <span class="n">LocalProxy</span><span class="p">(</span><span class="n">local</span><span class="p">,</span> <span class="s">&#39;request&#39;</span><span class="p">)</span>
146 </pre></div>
147 </div></dd>
148 </dl>
149 </div>
150
151 <div style="clear:both"></div>
152 </div>
153 <div class="footer">
154 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
155 BSD license.
156 </div>
157 </div>
158 </body>
159 </html>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Organizing Code // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Organizing Code</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#entry-point" id="id1" name="id1">Entry Point</a></li>
22 <li><a class="reference" href="#utils" id="id2" name="id2">Utils</a></li>
23 <li><a class="reference" href="#urls" id="id3" name="id3">URLs</a></li>
24 <li><a class="reference" href="#view" id="id4" name="id4">View</a></li>
25 <li><a class="reference" href="#models-and-templates" id="id5" name="id5">Models and Templates</a></li>
26 </ul>
27 </div>
28
29 <p>Werkzeug doesn&#8217;t limit you in what you do; thus there is no general rule where
30 to locate your modules, what to do with your data etc.</p>
31 <p>However, there is a general pattern which is used in many web applications,
32 the &#8220;Model View Controller&#8221; pattern (<cite>MVC</cite>), which makes sense for many
33 applications.</p>
34 <p>The idea is that you have &#8220;models&#8221; in one file, the &#8220;views&#8221; in another and the
35 &#8220;controllers&#8221; in the third file or folder. The Django framework came up with
36 a new idea of naming those components which is called &#8220;Model Template View&#8221;
37 (<cite>MTV</cite>) which probably makes more sense for web applications although it means
38 nearly the same. We will use the latter naming in this file.</p>
39 <p>So here is what those terms mean:</p>
40 <dl>
41 <dt>Model</dt>
42 <dd>A model is an abstraction of your data. For example if you have a
43 database you can use the excellent <a class="reference" href="http://www.sqlalchemy.org/">SQLAlchemy</a> library that maps
44 database tables to classes or just provides a thin layer that lets you
45 write your own classes.</dd>
46 <dt>Template</dt>
47 <dd><p class="first">The templates contain the actual HTML markup with placeholders for data
48 passed to them. Although there is a <a class="reference" href="minitmpl.html">minimal template language</a> in the Werkzeug package we don&#8217;t recommend using it for
49 larger applications. There are many good and powerful template engines
50 out there that support debugging, template inheritance, XML output etc.</p>
51 <p class="last">The integrated templating language is a good idea if you have one or two
52 templates and don&#8217;t want to install one of the big template engines, but
53 it is not suitable for more complex tasks!</p>
54 </dd>
55 <dt>View</dt>
56 <dd>The view is the code that processes data, connects the model with
57 templates, and then outputs the result. This is the part where Werkzeug
58 helps you.</dd>
59 </dl>
60 <div class="section">
61 <h3 id="entry-point">Entry Point</h3>
62 <p>There should be one central file that provides the WSGI application and
63 dispatches the requests. It&#8217;s also a good idea to assemble the final URL map
64 there if you use the Werkzeug <a class="reference" href="routing.html">routing system</a>.</p>
65 <p>It&#8217;s also a good idea to call the file something like <tt class="docutils literal"><span class="pre">main.py</span></tt> or
66 <tt class="docutils literal"><span class="pre">application.py</span></tt> and locate it in the root of the package. This is what it
67 <em>could</em> look like:</p>
68 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">Request</span><span class="p">,</span> <span class="n">Response</span><span class="p">,</span> <span class="n">import_string</span>
69 <span class="k">from</span> <span class="nn">werkzeug.exceptions</span> <span class="k">import</span> <span class="n">HTTPException</span>
70 <span class="k">from</span> <span class="nn">werkzeug.routing</span> <span class="k">import</span> <span class="n">RequestRedirect</span>
71 <span class="k">from</span> <span class="nn">mypackage.urls</span> <span class="k">import</span> <span class="n">url_map</span>
72
73 <span class="k">def</span> <span class="nf">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
74 <span class="n">url_adapter</span> <span class="o">=</span> <span class="n">url_map</span><span class="o">.</span><span class="n">bind_to_environ</span><span class="p">(</span><span class="n">environ</span><span class="p">)</span>
75 <span class="n">req</span> <span class="o">=</span> <span class="n">Request</span><span class="p">(</span><span class="n">environ</span><span class="p">)</span>
76 <span class="k">try</span><span class="p">:</span>
77 <span class="n">endpoint</span><span class="p">,</span> <span class="n">values</span> <span class="o">=</span> <span class="n">url_adapter</span><span class="o">.</span><span class="n">match</span><span class="p">()</span>
78 <span class="n">view</span> <span class="o">=</span> <span class="n">import_string</span><span class="p">(</span><span class="s">&#39;mypackage.views.&#39;</span> <span class="o">+</span> <span class="n">endpoint</span><span class="p">)</span>
79 <span class="n">resp</span> <span class="o">=</span> <span class="n">view</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="o">**</span><span class="n">values</span><span class="p">)</span>
80 <span class="k">except</span> <span class="p">(</span><span class="n">RequestRedirect</span><span class="p">,</span> <span class="n">HTTPException</span><span class="p">),</span> <span class="n">e</span><span class="p">:</span>
81 <span class="n">resp</span> <span class="o">=</span> <span class="n">e</span>
82 <span class="k">return</span> <span class="n">resp</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">)</span>
83 </pre></div>
84 <p>You can even further simplify the dispatching by using <tt class="docutils literal"><span class="pre">urls.dispatch</span></tt> as
85 explained in the routing documentation.</p>
86 <p>This will look for the controller functions in <cite>mypackage.views.module</cite>.
87 If the URL is configured with an endpoint of <tt class="docutils literal"><span class="pre">'static.index'</span></tt>, the module
88 <cite>mypackage.views.static</cite> is loaded and <cite>index(req)</cite> is called.</p>
89 <p>The URL rule parameters are passed to the function as keyword arguments then.</p>
90 <p><strong>Note</strong>: This is just one idea of how things can look like. This doesn&#8217;t
91 necessarily have to represent your application. You can, for example, save
92 the request object in a thread-local storage or have your views as methods
93 of a controller class you instantiate with the request as argument for each
94 incoming request. You can also use other request objects or no request object
95 at all etc.</p>
96 </div>
97 <div class="section">
98 <h3 id="utils">Utils</h3>
99 <p>If we continue the example above we need an utils module with the request and
100 response objects. This would also contain other utilities and a bridge to a
101 template engine. In this example we will use the <a class="reference" href="http://jinja.pocoo.org/">Jinja</a> template engine -
102 basically because it&#8217;s something we use, too - but you can of course use any
103 template engine:</p>
104 <div class="syntax"><pre><span class="k">from</span> <span class="nn">jinja</span> <span class="k">import</span> <span class="n">Environment</span><span class="p">,</span> <span class="n">PackageLoader</span>
105 <span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">Response</span>
106
107 <span class="n">env</span> <span class="o">=</span> <span class="n">Environment</span><span class="p">(</span><span class="n">loader</span><span class="o">=</span><span class="n">PackageLoader</span><span class="p">(</span><span class="s">&#39;mypackage&#39;</span><span class="p">,</span> <span class="s">&#39;templates&#39;</span><span class="p">))</span>
108
109 <span class="k">class</span> <span class="nc">TemplateResponse</span><span class="p">(</span><span class="n">Response</span><span class="p">):</span>
110
111 <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">template_name</span><span class="p">,</span> <span class="o">**</span><span class="n">values</span><span class="p">):</span>
112 <span class="n">tmpl</span> <span class="o">=</span> <span class="n">env</span><span class="o">.</span><span class="n">get_template</span><span class="p">(</span><span class="n">template_name</span><span class="p">)</span>
113 <span class="n">output</span> <span class="o">=</span> <span class="n">tmpl</span><span class="o">.</span><span class="n">render</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
114 <span class="n">Response</span><span class="o">.</span><span class="n">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">mimetype</span><span class="o">=</span><span class="s">&#39;text/html&#39;</span><span class="p">)</span>
115 </pre></div>
116 <p><strong>Note</strong>: Templates in this example are saved in the <cite>templates</cite> folder inside
117 the <cite>mypackage</cite> package. The way template loading and rendering works depends
118 on the template engine, so have a look at it&#8217;s documentation regarding that.</p>
119 <p>We just subclass request and response classes for our needs and provide a
120 second response subclass that is used to render templates. It&#8217;s used in the
121 example view below.</p>
122 </div>
123 <div class="section">
124 <h3 id="urls">URLs</h3>
125 <p>Because we use the Werkzeug URL routing system in this example and the URLs
126 are stored in <cite>urls.py</cite>, we need that file:</p>
127 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug.routing</span> <span class="k">import</span> <span class="n">Map</span><span class="p">,</span> <span class="n">Rule</span>
128
129 <span class="n">url_map</span> <span class="o">=</span> <span class="n">Map</span><span class="p">([</span>
130 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/&#39;</span><span class="p">,</span> <span class="s">&#39;static.index&#39;</span><span class="p">)</span>
131 <span class="p">])</span>
132 </pre></div>
133 <p>This is just one small URL rule for one view.</p>
134 </div>
135 <div class="section">
136 <h3 id="view">View</h3>
137 <p>Here is the view defined above. It must be saved in
138 <cite>myprojects/views/static.py</cite> as defined above:</p>
139 <div class="syntax"><pre><span class="k">from</span> <span class="nn">myproject.utils</span> <span class="k">import</span> <span class="n">TemplateResponse</span>
140
141 <span class="k">def</span> <span class="nf">index</span><span class="p">(</span><span class="n">req</span><span class="p">):</span>
142 <span class="k">return</span> <span class="n">TemplateResponse</span><span class="p">(</span><span class="s">&#39;index.html&#39;</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">&#39;Welcome&#39;</span><span class="p">)</span>
143 </pre></div>
144 </div>
145 <div class="section">
146 <h3 id="models-and-templates">Models and Templates</h3>
147 <p>Models and templates are out of the scope for this documentation, but you
148 should have gotten an idea on how you can organize your WSGI application built
149 with the help of Werkzeug.</p>
150 </div>
151
152 <div style="clear:both"></div>
153 </div>
154 <div class="footer">
155 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
156 BSD license.
157 </div>
158 </div>
159 </body>
160 </html>
0 div.header, ul.navigation, div.aside, a.underthehood {
1 display: none;
2 }
3
4 div.page, div.withaside, div.contents {
5 margin: 0;
6 padding: 0;
7 width: auto;
8 float: none;
9 border: none;
10 }
0 .syntax { background: #f0f3f3; }
1 .syntax .c { color: #0099FF; font-style: italic } /* Comment */
2 .syntax .err { color: #AA0000; background-color: #FFAAAA } /* Error */
3 .syntax .k { color: #006699; font-weight: bold } /* Keyword */
4 .syntax .o { color: #555555 } /* Operator */
5 .syntax .cm { color: #0099FF; font-style: italic } /* Comment.Multiline */
6 .syntax .cp { color: #009999 } /* Comment.Preproc */
7 .syntax .c1 { color: #0099FF; font-style: italic } /* Comment.Single */
8 .syntax .cs { color: #0099FF; font-weight: bold; font-style: italic } /* Comment.Special */
9 .syntax .gd { background-color: #FFCCCC; border: 1px solid #CC0000 } /* Generic.Deleted */
10 .syntax .ge { font-style: italic } /* Generic.Emph */
11 .syntax .gr { color: #FF0000 } /* Generic.Error */
12 .syntax .gh { color: #003300; font-weight: bold } /* Generic.Heading */
13 .syntax .gi { background-color: #CCFFCC; border: 1px solid #00CC00 } /* Generic.Inserted */
14 .syntax .go { color: #AAAAAA } /* Generic.Output */
15 .syntax .gp { color: #000099; font-weight: bold } /* Generic.Prompt */
16 .syntax .gs { font-weight: bold } /* Generic.Strong */
17 .syntax .gu { color: #003300; font-weight: bold } /* Generic.Subheading */
18 .syntax .gt { color: #99CC66 } /* Generic.Traceback */
19 .syntax .kc { color: #006699; font-weight: bold } /* Keyword.Constant */
20 .syntax .kd { color: #006699; font-weight: bold } /* Keyword.Declaration */
21 .syntax .kp { color: #006699 } /* Keyword.Pseudo */
22 .syntax .kr { color: #006699; font-weight: bold } /* Keyword.Reserved */
23 .syntax .kt { color: #007788; font-weight: bold } /* Keyword.Type */
24 .syntax .m { color: #FF6600 } /* Literal.Number */
25 .syntax .s { color: #CC3300 } /* Literal.String */
26 .syntax .na { color: #330099 } /* Name.Attribute */
27 .syntax .nb { color: #336666 } /* Name.Builtin */
28 .syntax .nc { color: #00AA88; font-weight: bold } /* Name.Class */
29 .syntax .no { color: #336600 } /* Name.Constant */
30 .syntax .nd { color: #9999FF } /* Name.Decorator */
31 .syntax .ni { color: #999999; font-weight: bold } /* Name.Entity */
32 .syntax .ne { color: #CC0000; font-weight: bold } /* Name.Exception */
33 .syntax .nf { color: #CC00FF } /* Name.Function */
34 .syntax .nl { color: #9999FF } /* Name.Label */
35 .syntax .nn { color: #00CCFF; font-weight: bold } /* Name.Namespace */
36 .syntax .nt { color: #330099; font-weight: bold } /* Name.Tag */
37 .syntax .nv { color: #003333 } /* Name.Variable */
38 .syntax .ow { color: #000000; font-weight: bold } /* Operator.Word */
39 .syntax .w { color: #bbbbbb } /* Text.Whitespace */
40 .syntax .mf { color: #FF6600 } /* Literal.Number.Float */
41 .syntax .mh { color: #FF6600 } /* Literal.Number.Hex */
42 .syntax .mi { color: #FF6600 } /* Literal.Number.Integer */
43 .syntax .mo { color: #FF6600 } /* Literal.Number.Oct */
44 .syntax .sb { color: #CC3300 } /* Literal.String.Backtick */
45 .syntax .sc { color: #CC3300 } /* Literal.String.Char */
46 .syntax .sd { color: #CC3300; font-style: italic } /* Literal.String.Doc */
47 .syntax .s2 { color: #CC3300 } /* Literal.String.Double */
48 .syntax .se { color: #CC3300; font-weight: bold } /* Literal.String.Escape */
49 .syntax .sh { color: #CC3300 } /* Literal.String.Heredoc */
50 .syntax .si { color: #AA0000 } /* Literal.String.Interpol */
51 .syntax .sx { color: #CC3300 } /* Literal.String.Other */
52 .syntax .sr { color: #33AAAA } /* Literal.String.Regex */
53 .syntax .s1 { color: #CC3300 } /* Literal.String.Single */
54 .syntax .ss { color: #FFCC33 } /* Literal.String.Symbol */
55 .syntax .bp { color: #336666 } /* Name.Builtin.Pseudo */
56 .syntax .vc { color: #003333 } /* Name.Variable.Class */
57 .syntax .vg { color: #003333 } /* Name.Variable.Global */
58 .syntax .vi { color: #003333 } /* Name.Variable.Instance */
59 .syntax .il { color: #FF6600 } /* Literal.Number.Integer.Long */
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Routing System // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Routing System</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#quickstart" id="id1" name="id1">Quickstart</a></li>
22 <li><a class="reference" href="#rule-format" id="id2" name="id2">Rule Format</a></li>
23 <li><a class="reference" href="#builtin-converters" id="id3" name="id3">Builtin Converters</a></li>
24 <li><a class="reference" href="#maps-rules-and-adapters" id="id4" name="id4">Maps, Rules and Adapters</a></li>
25 <li><a class="reference" href="#rule-factories" id="id5" name="id5">Rule Factories</a></li>
26 <li><a class="reference" href="#rule-templates" id="id6" name="id6">Rule Templates</a></li>
27 <li><a class="reference" href="#custom-converters" id="id7" name="id7">Custom Converters</a></li>
28 </ul>
29 </div>
30
31 <p>When it comes to combining multiple controller or view functions (however
32 you want to call them), you need a dispatcher. A simple way would be
33 applying regular expression tests on <tt class="docutils literal"><span class="pre">PATH_INFO</span></tt> and call registered
34 callback functions that return the value.</p>
35 <p>Werkzeug provides a much more powerful system, similar to <a class="reference" href="http://routes.groovie.org/">Routes</a>. All the
36 objects mentioned on this page must be imported from <cite>werkzeug.routing</cite>, not
37 from <cite>werkzeug</cite>!</p>
38 <div class="section">
39 <h3 id="quickstart">Quickstart</h3>
40 <p>Here a simple example which could be the URL definition for a blog:</p>
41 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug.routing</span> <span class="k">import</span> <span class="n">Map</span><span class="p">,</span> <span class="n">Rule</span><span class="p">,</span> <span class="n">NotFound</span><span class="p">,</span> <span class="n">RequestRedirect</span>
42
43 <span class="n">url_map</span> <span class="o">=</span> <span class="n">Map</span><span class="p">([</span>
44 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;blog/index&#39;</span><span class="p">),</span>
45 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/&lt;int:year&gt;/&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;blog/archive&#39;</span><span class="p">),</span>
46 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/&lt;int:year&gt;/&lt;int:month&gt;/&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;blog/archive&#39;</span><span class="p">),</span>
47 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/&lt;int:year&gt;/&lt;int:month&gt;/&lt;int:day&gt;/&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;blog/archive&#39;</span><span class="p">),</span>
48 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/&lt;int:year&gt;/&lt;int:month&gt;/&lt;int:day&gt;/&lt;slug&gt;&#39;</span><span class="p">,</span>
49 <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;blog/show_post&#39;</span><span class="p">),</span>
50 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/about&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;blog/about_me&#39;</span><span class="p">),</span>
51 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/feeds/&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;blog/feeds&#39;</span><span class="p">),</span>
52 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/feeds/&lt;feed_name&gt;.rss&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;blog/show_feed&#39;</span><span class="p">)</span>
53 <span class="p">])</span>
54
55 <span class="k">def</span> <span class="nf">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
56 <span class="n">urls</span> <span class="o">=</span> <span class="n">url_map</span><span class="o">.</span><span class="n">bind_to_environ</span><span class="p">(</span><span class="n">environ</span><span class="p">)</span>
57 <span class="k">try</span><span class="p">:</span>
58 <span class="n">endpoint</span><span class="p">,</span> <span class="n">args</span> <span class="o">=</span> <span class="n">urls</span><span class="o">.</span><span class="n">match</span><span class="p">()</span>
59 <span class="k">except</span> <span class="n">HTTPException</span><span class="p">,</span> <span class="n">e</span><span class="p">:</span>
60 <span class="k">return</span> <span class="n">e</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">)</span>
61 <span class="n">start_response</span><span class="p">(</span><span class="s">&#39;200 OK&#39;</span><span class="p">,</span> <span class="p">[(</span><span class="s">&#39;Content-Type&#39;</span><span class="p">,</span> <span class="s">&#39;text/plain&#39;</span><span class="p">)])</span>
62 <span class="k">return</span> <span class="p">[</span><span class="s">&#39;Rule points to </span><span class="si">%r</span><span class="s"> with arguments </span><span class="si">%r</span><span class="s">&#39;</span> <span class="o">%</span> <span class="p">(</span><span class="n">endpoint</span><span class="p">,</span> <span class="n">args</span><span class="p">)]</span>
63 </pre></div>
64 <p>So what does that do? First of all we create a new <cite>Map</cite> which stores a
65 bunch of URL rules. Then we pass it a list of <cite>Rule</cite> objects.</p>
66 <p>Each <cite>Rule</cite> object is instantiated with a string that represents a rule and
67 an endpoint which will be the alias for what view the rule represents.
68 Multiple rules can have the same endpoint but should have different arguments
69 to allow URL construction.</p>
70 <p>The format for the URL rules is straightforward, but explained in detail below.</p>
71 <p>Inside the WSGI application we bind the url_map to the current request which will
72 return a new <cite>MapAdapter</cite>. This url_map adapter can then be used to match or
73 build domains for the current request.</p>
74 <p>The <cite>match</cite> method can then either return a tuple in the form
75 <tt class="docutils literal"><span class="pre">(endpoint,</span> <span class="pre">args)</span></tt> or raise one of the three exceptions <cite>NotFound</cite>,
76 <cite>MethodNotAllowed</cite>, or <cite>RequestRedirect</cite>. For more details about those
77 exceptions have a look at the documentation of the <cite>match</cite> method.</p>
78 </div>
79 <div class="section">
80 <h3 id="rule-format">Rule Format</h3>
81 <p>Rule strings basically are just normal URL paths with placeholders in the
82 format <tt class="docutils literal"><span class="pre">&lt;converter(arguments):name&gt;</span></tt>, where converter and the arguments
83 are optional. If no converter is defined, the <cite>default</cite> converter is used
84 (which means <cite>string</cite> in the normal configuration).</p>
85 <p>URL rules that end with a slash are branch URLs, others are leaves. If you
86 have <cite>strict_slashes</cite> enabled (which is the default), all branch URLs that are
87 visited without a trailing slash will trigger a redirect to the same URL with
88 that slash appended.</p>
89 <p>The list of converters can be extended, the default converters are explained
90 below.</p>
91 </div>
92 <div class="section">
93 <h3 id="builtin-converters">Builtin Converters</h3>
94 <p>Here a list of converters that come with Werkzeug:</p>
95 <dl>
96 <dt><cite>string</cite></dt>
97 <dd><p class="first">This converter is the default converter and accepts any string but
98 only one one path segment. Thus the string can not include a slash.</p>
99 <p>Supported arguments:</p>
100 <ul class="last simple">
101 <li><cite>minlength</cite> - the minimum length of the string. must be greater
102 than 1.</li>
103 <li><cite>maxlength</cite> - the maximum length of the string.</li>
104 <li><cite>length</cite> - the exact length of that string.</li>
105 </ul>
106 </dd>
107 <dt><cite>path</cite></dt>
108 <dd>Like the default string converter, but it also matches slashes.</dd>
109 <dt><cite>any</cite></dt>
110 <dd><p class="first">Matches one of the items provided. Items can either be Python
111 identifiers or unicode strings:</p>
112 <div class="syntax"><pre><span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/&lt;any(about, help, imprint, u&quot;class&quot;):page_name&gt;&#39;</span><span class="p">)</span>
113 </pre></div>
114 </dd>
115 <dt><cite>int</cite></dt>
116 <dd><p class="first">This converter only accepts integer values:</p>
117 <div class="syntax"><pre><span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/page/&lt;int:page&gt;&#39;</span><span class="p">)</span>
118 </pre></div>
119 <p>Supported arguments:</p>
120 <ul class="last simple">
121 <li><cite>fixed_digits</cite> - the number of fixed digits in the URL. If you
122 set this to <tt class="docutils literal"><span class="pre">4</span></tt> for example, the application will only match
123 if the url looks like <tt class="docutils literal"><span class="pre">/0001/</span></tt>. The default is
124 variable length.</li>
125 <li><cite>min</cite> - the minimal value.</li>
126 <li><cite>max</cite> - the maximal value.</li>
127 </ul>
128 </dd>
129 <dt><cite>float</cite></dt>
130 <dd><p class="first">This converter only accepts floating point values:</p>
131 <div class="syntax"><pre><span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/probability/&lt;float:probability&gt;&#39;</span><span class="p">)</span>
132 </pre></div>
133 <p>Supported arguments:</p>
134 <ul class="last simple">
135 <li><cite>min</cite> - the minimal value.</li>
136 <li><cite>max</cite> - the maximal value.</li>
137 </ul>
138 </dd>
139 </dl>
140 <div class="admonition-important admonition">
141 <p class="first admonition-title">Important</p>
142 <p>Werkzeug evaluates converter arguments as if they are Python method calls.
143 Thus, you should <strong>never</strong> create rules from user submitted data since
144 they could insert arbitrary Python code in the parameters part.</p>
145 <p>As a matter of fact this is a legal definition and sets fixed_digits to 2:</p>
146 <div class="syntax"><pre><span class="n">url_map</span> <span class="o">=</span> <span class="n">Map</span><span class="p">([</span>
147 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/picture/&lt;int(fixed_digits=1 + 1):id&gt;.png&#39;</span><span class="p">,</span>
148 <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;view_image&#39;</span><span class="p">)</span>
149 <span class="p">])</span>
150 </pre></div>
151 <p class="last">However, evaluating Python expressions is currently an implementation
152 detail and might be unavailable in the future.</p>
153 </div>
154 </div>
155 <div class="section">
156 <h3 id="maps-rules-and-adapters">Maps, Rules and Adapters</h3>
157 <dl>
158 <dt><strong>class</strong> <cite id="werkzeug.routing.Map">Map</cite></dt>
159 <dd><p class="first">The map class stores all the URL rules and some configuration
160 parameters. Some of the configuration values are only stored on the
161 <cite>Map</cite> instance since those affect all rules, others are just defaults
162 and can be overridden for each rule. Note that you have to specify all
163 arguments beside the <cite>rules</cite> as keywords arguments!</p>
164 <dl>
165 <dt><cite id="werkzeug.routing.Map.__init__">__init__</cite> <tt class="func-signature docutils literal"><span class="pre">(rules=None,</span> <span class="pre">default_subdomain='',</span> <span class="pre">charset='utf-8',</span> <span class="pre">strict_slashes=True,</span> <span class="pre">redirect_defaults=True,</span> <span class="pre">converters=None)</span></tt></dt>
166 <dd><p class="first">Initializes the new URL map.</p>
167 <dl class="last">
168 <dt>Parameters</dt>
169 <dd><p class="first"><strong>rules</strong>: sequence of url rules for this map.</p>
170 <p><strong>default_subdomain</strong>: The default subdomain for rules without a
171 subdomain defined.</p>
172 <p><strong>charset</strong>: charset of the url. defaults to <tt class="docutils literal"><span class="pre">&quot;utf-8&quot;</span></tt></p>
173 <p><strong>strict_slashes</strong>: Take care of trailing slashes.</p>
174 <p><strong>redirect_defaults</strong>: This will redirect to the default rule if it
175 wasn&#8217;t visited that way. This helps creating
176 unique URLs.</p>
177 <p class="last"><strong>converters</strong>: A dict of converters that adds additional converters
178 to the list of converters. If you redefine one
179 converter this will override the original one.</p>
180 </dd>
181 </dl>
182 </dd>
183 </dl>
184 <dl>
185 <dt><cite id="werkzeug.routing.Map.is_endpoint_expecting">is_endpoint_expecting</cite> <tt class="func-signature docutils literal"><span class="pre">(endpoint,</span> <span class="pre">*arguments)</span></tt></dt>
186 <dd>Iterate over all rules and check if the endpoint expects
187 the arguments provided. This is for example useful if you have
188 some URLs that expect a language code and others that do not and
189 you want to wrap the builder a bit so that the current language
190 code is automatically added if not provided but endpoints expect
191 it.</dd>
192 </dl>
193 <dl>
194 <dt><cite id="werkzeug.routing.Map.iter_rules">iter_rules</cite> <tt class="func-signature docutils literal"><span class="pre">(endpoint=None)</span></tt></dt>
195 <dd>Iterate over all rules or the rules of an endpoint.</dd>
196 </dl>
197 <dl>
198 <dt><cite id="werkzeug.routing.Map.add">add</cite> <tt class="func-signature docutils literal"><span class="pre">(rulefactory)</span></tt></dt>
199 <dd>Add a new rule or factory to the map and bind it. Requires that the
200 rule is not bound to another map.</dd>
201 </dl>
202 <dl>
203 <dt><cite id="werkzeug.routing.Map.bind">bind</cite> <tt class="func-signature docutils literal"><span class="pre">(server_name,</span> <span class="pre">script_name=None,</span> <span class="pre">subdomain=None,</span> <span class="pre">url_scheme='http',</span> <span class="pre">default_method='GET',</span> <span class="pre">path_info=None)</span></tt></dt>
204 <dd><p class="first">Return a new <cite>MapAdapter</cite> with the details specified to the call.
205 Note that <cite>script_name</cite> will default to <tt class="docutils literal"><span class="pre">'/'</span></tt> if not further
206 specified or <cite>None</cite>. The <cite>server_name</cite> at least is a requirement
207 because the HTTP RFC requires absolute URLs for redirects and so all
208 redirect exceptions raised by Werkzeug will contain the full canonical
209 URL.</p>
210 <p>If no path_info is passed to match() it will use the default path
211 info passed to bind. While this doesn&#8217;t really make sense for
212 manual bind calls, it&#8217;s useful if you bind a map to a WSGI
213 environment which already contains the path info.</p>
214 <p class="last"><cite>subdomain</cite> will default to the <cite>default_subdomain</cite> for this map if
215 no defined. If there is no <cite>default_subdomain</cite> you cannot use the
216 subdomain feature.</p>
217 </dd>
218 </dl>
219 <dl class="last">
220 <dt><cite id="werkzeug.routing.Map.bind_to_environ">bind_to_environ</cite> <tt class="func-signature docutils literal"><span class="pre">(environ,</span> <span class="pre">server_name=None,</span> <span class="pre">subdomain=None,</span> <span class="pre">calculate_subdomain=False)</span></tt></dt>
221 <dd><p class="first">Like <cite>bind</cite> but you can pass it an WSGI environment and it will
222 fetch the information from that directory. Note that because of
223 limitations in the protocol there is no way to get the current
224 subdomain and real <cite>server_name</cite> from the environment. If you don&#8217;t
225 provide it, Werkzeug will use <cite>SERVER_NAME</cite> and <cite>SERVER_PORT</cite> (or
226 <cite>HTTP_HOST</cite> if provided) as used <cite>server_name</cite> with disabled subdomain
227 feature.</p>
228 <p>If <cite>subdomain</cite> is <cite>None</cite> but an environment and a server name is
229 provided it will calculate the current subdomain automatically.
230 Example: <cite>server_name</cite> is <tt class="docutils literal"><span class="pre">'example.com'</span></tt> and the <cite>SERVER_NAME</cite>
231 in the wsgi <cite>environ</cite> is <tt class="docutils literal"><span class="pre">'staging.dev.example.com'</span></tt> the calculated
232 subdomain will be <tt class="docutils literal"><span class="pre">'staging.dev'</span></tt>.</p>
233 <p class="last">If the object passed as environ as an environ attribute, the value of
234 this attribute is used instead. This allows you to pass request
235 objects. Additionally <cite>PATH_INFO</cite> added as a default ot the
236 <cite>MapAdapter</cite> so that you don&#8217;t have to pass the path info to the
237 match method.</p>
238 </dd>
239 </dl>
240 </dd>
241 </dl>
242 <dl>
243 <dt><strong>class</strong> <cite id="werkzeug.routing.MapAdapter">MapAdapter</cite></dt>
244 <dd><p class="first">Retured by <cite>Map.bind</cite> or <cite>Map.bind_to_environ</cite> and does the
245 URL matching and building based on runtime information.</p>
246 <dl>
247 <dt><cite id="werkzeug.routing.MapAdapter.match">match</cite> <tt class="func-signature docutils literal"><span class="pre">(path_info=None,</span> <span class="pre">method=None)</span></tt></dt>
248 <dd><p class="first">The usage is simple: you just pass the match method the current
249 path info as well as the method (which defaults to <cite>GET</cite>). The
250 following things can then happen:</p>
251 <ul class="simple">
252 <li>you receive a <cite>NotFound</cite> exception that indicates that no URL is
253 matching. A <cite>NotFound</cite> exception is also a WSGI application you
254 can call to get a default page not found page (happens to be the
255 same object as <cite>werkzeug.exceptions.NotFound</cite>)</li>
256 <li>you receive a <cite>MethodNotAllowed</cite> exception that indicates that there
257 is a match for this URL but non for the current request method.
258 This is useful for RESTful applications.</li>
259 <li>you receive a <cite>RequestRedirect</cite> exception with a <cite>new_url</cite>
260 attribute. This exception is used to notify you about a request
261 Werkzeug requests by your WSGI application. This is for example the
262 case if you request <tt class="docutils literal"><span class="pre">/foo</span></tt> although the correct URL is <tt class="docutils literal"><span class="pre">/foo/</span></tt>
263 You can use the <cite>RequestRedirect</cite> instance as response-like object
264 similar to all other subclasses of <cite>HTTPException</cite>.</li>
265 <li>you get a tuple in the form <tt class="docutils literal"><span class="pre">(endpoint,</span> <span class="pre">arguments)</span></tt> when there is
266 a match.</li>
267 </ul>
268 <p>If the path info is not passed to the match method the default path
269 info of the map is used (defaults to the root URL if not defined
270 explicitly).</p>
271 <p>All of the exceptions raised are subclasses of <cite>HTTPException</cite> so they
272 can be used as WSGI responses. The will all render generic error or
273 redirect pages.</p>
274 <p>Here is a small example for matching:</p>
275 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="k">from</span> <span class="nn">werkzeug.routing</span> <span class="k">import</span> <span class="n">Map</span><span class="p">,</span> <span class="n">Rule</span>
276 <span class="gp">&gt;&gt;&gt; </span><span class="n">m</span> <span class="o">=</span> <span class="n">Map</span><span class="p">([</span>
277 <span class="gp">... </span> <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;index&#39;</span><span class="p">),</span>
278 <span class="gp">... </span> <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/downloads/&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;downloads/index&#39;</span><span class="p">),</span>
279 <span class="gp">... </span> <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/downloads/&lt;int:id&gt;&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;downloads/show&#39;</span><span class="p">)</span>
280 <span class="gp">... </span><span class="p">])</span>
281 <span class="gp">&gt;&gt;&gt; </span><span class="n">urls</span> <span class="o">=</span> <span class="n">m</span><span class="o">.</span><span class="n">bind</span><span class="p">(</span><span class="s">&quot;example.com&quot;</span><span class="p">,</span> <span class="s">&quot;/&quot;</span><span class="p">)</span>
282 <span class="gp">&gt;&gt;&gt; </span><span class="n">urls</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="s">&quot;/&quot;</span><span class="p">,</span> <span class="s">&quot;GET&quot;</span><span class="p">)</span>
283 <span class="go">(&#39;index&#39;, {})</span>
284 <span class="gp">&gt;&gt;&gt; </span><span class="n">urls</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="s">&quot;/downloads/42&quot;</span><span class="p">)</span>
285 <span class="go">(&#39;downloads/show&#39;, {&#39;id&#39;: 42})</span>
286 </pre></div>
287 <p>And here is what happens on redirect and missing URLs:</p>
288 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">urls</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="s">&quot;/downloads&quot;</span><span class="p">)</span>
289 <span class="gt">Traceback (most recent call last):</span>
290 <span class="c">...</span>
291 <span class="nc">werkzeug.routing.RequestRedirect</span>: <span class="n-Identifier">http://example.com/downloads/</span>
292 <span class="gp">&gt;&gt;&gt; </span><span class="n">urls</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="s">&quot;/missing&quot;</span><span class="p">)</span>
293 <span class="gt">Traceback (most recent call last):</span>
294 <span class="c">...</span>
295 <span class="nc">werkzeug.routing.NotFound</span>: <span class="n-Identifier">/missing</span>
296 </pre></div>
297 </dd>
298 </dl>
299 <dl>
300 <dt><cite id="werkzeug.routing.MapAdapter.test">test</cite> <tt class="func-signature docutils literal"><span class="pre">(path_info=None,</span> <span class="pre">method=None)</span></tt></dt>
301 <dd>Test if a rule would match. Works like <cite>match</cite> but returns <cite>True</cite>
302 if the URL matches, or <cite>False</cite> if it does not exist.</dd>
303 </dl>
304 <dl>
305 <dt><cite id="werkzeug.routing.MapAdapter.dispatch">dispatch</cite> <tt class="func-signature docutils literal"><span class="pre">(view_func,</span> <span class="pre">path_info=None,</span> <span class="pre">method=None,</span> <span class="pre">catch_http_exceptions=False)</span></tt></dt>
306 <dd><p class="first">Does the complete dispatching process. <cite>view_func</cite> is called with
307 the endpoint and a dict with the values for the view. It should
308 look up the view function, call it, and return a response object
309 or WSGI application. http exceptions are not catched by default
310 so that applications can display nicer error messages by just
311 catching them by hand. If you want to stick with the default
312 error messages you can pass it <tt class="docutils literal"><span class="pre">catch_http_exceptions=True</span></tt> and
313 it will catch the http exceptions.</p>
314 <p>Here a small example for the dispatch usage:</p>
315 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">Request</span><span class="p">,</span> <span class="n">Response</span><span class="p">,</span> <span class="n">responder</span>
316 <span class="k">from</span> <span class="nn">werkzeug.routing</span> <span class="k">import</span> <span class="n">Map</span><span class="p">,</span> <span class="n">Rule</span>
317
318 <span class="k">def</span> <span class="nf">on_index</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
319 <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="s">&#39;Hello from the index&#39;</span><span class="p">)</span>
320
321 <span class="n">url_map</span> <span class="o">=</span> <span class="n">Map</span><span class="p">([</span><span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;index&#39;</span><span class="p">)])</span>
322 <span class="n">views</span> <span class="o">=</span> <span class="p">{</span><span class="s">&#39;index&#39;</span><span class="p">:</span> <span class="n">on_index</span><span class="p">}</span>
323
324 <span class="nd">@responder</span>
325 <span class="k">def</span> <span class="nf">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
326 <span class="n">request</span> <span class="o">=</span> <span class="n">Request</span><span class="p">(</span><span class="n">environ</span><span class="p">)</span>
327 <span class="n">urls</span> <span class="o">=</span> <span class="n">url_map</span><span class="o">.</span><span class="n">bind_to_environ</span><span class="p">(</span><span class="n">environ</span><span class="p">)</span>
328 <span class="k">return</span> <span class="n">urls</span><span class="o">.</span><span class="n">dispatch</span><span class="p">(</span><span class="k">lambda</span> <span class="n">e</span><span class="p">,</span> <span class="n">v</span><span class="p">:</span> <span class="n">views</span><span class="p">[</span><span class="n">e</span><span class="p">](</span><span class="n">request</span><span class="p">,</span> <span class="o">**</span><span class="n">v</span><span class="p">),</span>
329 <span class="n">catch_http_exceptions</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
330 </pre></div>
331 <p class="last">Keep in mind that this method might return exception objects too, so
332 use <cite>Response.force_type</cite> to get a response object.</p>
333 </dd>
334 </dl>
335 <dl class="last">
336 <dt><cite id="werkzeug.routing.MapAdapter.build">build</cite> <tt class="func-signature docutils literal"><span class="pre">(endpoint,</span> <span class="pre">values=None,</span> <span class="pre">method=None,</span> <span class="pre">force_external=False)</span></tt></dt>
337 <dd><p class="first">Building URLs works pretty much the other way round. Instead of
338 <cite>match</cite> you call <cite>build</cite> and pass it the endpoint and a dict of
339 arguments for the placeholders.</p>
340 <p>The <cite>build</cite> function also accepts an argument called <cite>force_external</cite>
341 which, if you set it to <cite>True</cite> will force external URLs. Per default
342 external URLs (include the server name) will only be used if the
343 target URL is on a
344 different subdomain.</p>
345 <p>With the same map as in the example above this code generates some
346 target URLs:</p>
347 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">urls</span><span class="o">.</span><span class="n">build</span><span class="p">(</span><span class="s">&quot;index&quot;</span><span class="p">,</span> <span class="p">{})</span>
348 <span class="go">&#39;/&#39;</span>
349 <span class="gp">&gt;&gt;&gt; </span><span class="n">urls</span><span class="o">.</span><span class="n">build</span><span class="p">(</span><span class="s">&quot;downloads/show&quot;</span><span class="p">,</span> <span class="p">{</span><span class="s">&#39;id&#39;</span><span class="p">:</span> <span class="mf">42</span><span class="p">})</span>
350 <span class="go">&#39;/downloads/42&#39;</span>
351 <span class="gp">&gt;&gt;&gt; </span><span class="n">urls</span><span class="o">.</span><span class="n">build</span><span class="p">(</span><span class="s">&quot;downloads/show&quot;</span><span class="p">,</span> <span class="p">{</span><span class="s">&#39;id&#39;</span><span class="p">:</span> <span class="mf">42</span><span class="p">},</span> <span class="n">force_external</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
352 <span class="go">&#39;http://example.com/downloads/42&#39;</span>
353 </pre></div>
354 <p>Because URLs cannot contain non ASCII data you will always get
355 bytestrings back. Non ASCII characters are urlencoded with the
356 charset defined on the map instance.</p>
357 <p>Additional values are converted to unicode and appended to the URL as
358 URL querystring parameters:</p>
359 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">urls</span><span class="o">.</span><span class="n">build</span><span class="p">(</span><span class="s">&quot;index&quot;</span><span class="p">,</span> <span class="p">{</span><span class="s">&#39;q&#39;</span><span class="p">:</span> <span class="s">&#39;My Searchstring&#39;</span><span class="p">})</span>
360 <span class="go">&#39;/?q=My+Searchstring&#39;</span>
361 </pre></div>
362 <p>If a rule does not exist when building a <cite>BuildError</cite> exception is
363 raised.</p>
364 <p class="last">The build method accepts an argument called <cite>method</cite> which allows you
365 to specify the method you want to have an URL builded for if you have
366 different methods for the same endpoint specified.</p>
367 </dd>
368 </dl>
369 </dd>
370 </dl>
371 <dl>
372 <dt><strong>class</strong> <cite id="werkzeug.routing.Rule">Rule</cite></dt>
373 <dd><p class="first">A Rule represents one URL pattern. There are some options for <cite>Rule</cite>
374 that change the way it behaves and are passed to the <cite>Rule</cite> constructor.
375 Note that beside the rule-string all arguments <em>must</em> be keyword arguments
376 in order to not break the application on Werkzeug upgrades.</p>
377 <dl>
378 <dt><cite>string</cite></dt>
379 <dd><p class="first">Rule strings basically are just normal URL paths with placeholders in
380 the format <tt class="docutils literal"><span class="pre">&lt;converter(arguments):name&gt;</span></tt> where the converter and the
381 arguments are optional. If no converter is defined the <cite>default</cite>
382 converter is used which means <cite>string</cite> in the normal configuration.</p>
383 <p>URL rules that end with a slash are branch URLs, others are leaves.
384 If you have <cite>strict_slashes</cite> enabled (which is the default), all
385 branch URLs that are matched without a trailing slash will trigger a
386 redirect to the same URL with the missing slash appended.</p>
387 <p class="last">The converters are defined on the <cite>Map</cite>.</p>
388 </dd>
389 <dt><cite>endpoint</cite></dt>
390 <dd>The endpoint for this rule. This can be anything. A reference to a
391 function, a string, a number etc. The preferred way is using a string
392 as because the endpoint is used for URL generation.</dd>
393 <dt><cite>defaults</cite></dt>
394 <dd><p class="first">An optional dict with defaults for other rules with the same endpoint.
395 This is a bit tricky but useful if you want to have unique URLs:</p>
396 <div class="syntax"><pre><span class="n">url_map</span> <span class="o">=</span> <span class="n">Map</span><span class="p">([</span>
397 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/all/&#39;</span><span class="p">,</span> <span class="n">defaults</span><span class="o">=</span><span class="p">{</span><span class="s">&#39;page&#39;</span><span class="p">:</span> <span class="mf">1</span><span class="p">},</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;all_entries&#39;</span><span class="p">),</span>
398 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/all/page/&lt;int:page&gt;&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;all_entries&#39;</span><span class="p">)</span>
399 <span class="p">])</span>
400 </pre></div>
401 <p class="last">If a user now visits <tt class="docutils literal"><span class="pre">http://example.com/all/page/1</span></tt> he will be
402 redirected to <tt class="docutils literal"><span class="pre">http://example.com/all/</span></tt>. If <cite>redirect_defaults</cite> is
403 disabled on the <cite>Map</cite> instance this will only affect the URL
404 generation.</p>
405 </dd>
406 <dt><cite>subdomain</cite></dt>
407 <dd><p class="first">The subdomain rule string for this rule. If not specified the rule
408 only matches for the <cite>default_subdomain</cite> of the map. If the map is
409 not bound to a subdomain this feature is disabled.</p>
410 <p>Can be useful if you want to have user profiles on different subdomains
411 and all subdomains are forwarded to your application:</p>
412 <div class="syntax"><pre><span class="n">url_map</span> <span class="o">=</span> <span class="n">Map</span><span class="p">([</span>
413 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/&#39;</span><span class="p">,</span> <span class="n">subdomain</span><span class="o">=</span><span class="s">&#39;&lt;username&gt;&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;user/homepage&#39;</span><span class="p">),</span>
414 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/stats&#39;</span><span class="p">,</span> <span class="n">subdomain</span><span class="o">=</span><span class="s">&#39;&lt;username&gt;&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;user/stats&#39;</span><span class="p">)</span>
415 <span class="p">])</span>
416 </pre></div>
417 </dd>
418 <dt><cite>methods</cite></dt>
419 <dd>A sequence of http methods this rule applies to. If not specified, all
420 methods are allowed. For example this can be useful if you want different
421 endpoints for <cite>POST</cite> and <cite>GET</cite>. If methods are defined and the path
422 matches but the method matched against is not in this list or in the
423 list of another rule for that path the error raised is of the type
424 <cite>MethodNotAllowed</cite> rather than <cite>NotFound</cite>.</dd>
425 <dt><cite>strict_slashes</cite></dt>
426 <dd>Override the <cite>Map</cite> setting for <cite>strict_slashes</cite> only for this rule. If
427 not specified the <cite>Map</cite> setting is used.</dd>
428 <dt><cite>build_only</cite></dt>
429 <dd>Set this to true and the rule will never match but will create a URL
430 that can be build. This is useful if you have resources on a subdomain
431 or folder that are not handled by the WSGI application (like static data)</dd>
432 <dt><cite>redirect_to</cite></dt>
433 <dd><p class="first">If given this must be either a string or callable. In case of a
434 callable it&#8217;s called with the url adapter that triggered the match and
435 the values of the URL as keyword arguments and has to return the target
436 for the redirect, otherwise it has to be a string with placeholders in
437 rule syntax:</p>
438 <div class="syntax"><pre><span class="k">def</span> <span class="nf">foo_with_slug</span><span class="p">(</span><span class="n">adapter</span><span class="p">,</span> <span class="nb">id</span><span class="p">):</span>
439 <span class="c"># ask the database for the slug for the old id. this of</span>
440 <span class="c"># course has nothing to do with werkzeug.</span>
441 <span class="k">return</span> <span class="s">&#39;foo/&#39;</span> <span class="o">+</span> <span class="n">Foo</span><span class="o">.</span><span class="n">get_slug_for_id</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
442
443 <span class="n">url_map</span> <span class="o">=</span> <span class="n">Map</span><span class="p">([</span>
444 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/foo/&lt;slug&gt;&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;foo&#39;</span><span class="p">),</span>
445 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/some/old/url/&lt;slug&gt;&#39;</span><span class="p">,</span> <span class="n">redirect_to</span><span class="o">=</span><span class="s">&#39;foo/&lt;slug&gt;&#39;</span><span class="p">),</span>
446 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/other/old/url/&lt;int:id&gt;&#39;</span><span class="p">,</span> <span class="n">redirect_to</span><span class="o">=</span><span class="n">foo_with_slug</span><span class="p">)</span>
447 <span class="p">])</span>
448 </pre></div>
449 <p>When the rule is matched the routing system will raise a
450 <cite>RequestRedirect</cite> exception with the target for the redirect.</p>
451 <p class="last">Keep in mind that the URL will be joined against the URL root of the
452 script so don&#8217;t use a leading slash on the target URL unless you
453 really mean root of that domain.</p>
454 </dd>
455 </dl>
456 <dl>
457 <dt><cite id="werkzeug.routing.Rule.empty">empty</cite> <tt class="func-signature docutils literal"><span class="pre">()</span></tt></dt>
458 <dd>Return an unbound copy of this rule. This can be useful if you
459 want to reuse an already bound URL for another map.</dd>
460 </dl>
461 <dl class="last">
462 <dt><cite id="werkzeug.routing.Rule.bind">bind</cite> <tt class="func-signature docutils literal"><span class="pre">(map)</span></tt></dt>
463 <dd>Bind the url to a map and create a regular expression based on
464 the information from the rule itself and the defaults from the map.</dd>
465 </dl>
466 </dd>
467 </dl>
468 </div>
469 <div class="section">
470 <h3 id="rule-factories">Rule Factories</h3>
471 <dl>
472 <dt><strong>class</strong> <cite id="werkzeug.routing.RuleFactory">RuleFactory</cite></dt>
473 <dd><p class="first">As soon as you have more complex URL setups it&#8217;s a good idea to use rule
474 factories to avoid repetitive tasks. Some of them are builtin, others can
475 be added by subclassing <cite>RuleFactory</cite> and overriding <cite>get_rules</cite>.</p>
476 <dl class="last">
477 <dt><cite id="werkzeug.routing.RuleFactory.get_rules">get_rules</cite> <tt class="func-signature docutils literal"><span class="pre">(map)</span></tt></dt>
478 <dd>Subclasses of <cite>RuleFactory</cite> have to override this method and return
479 an iterable of rules.</dd>
480 </dl>
481 </dd>
482 </dl>
483 <dl>
484 <dt><strong>class</strong> <cite id="werkzeug.routing.Subdomain">Subdomain</cite></dt>
485 <dd><p class="first">All URLs provided by this factory have the subdomain set to a
486 specific domain. For example if you want to use the subdomain for
487 the current language this can be a good setup:</p>
488 <div class="syntax"><pre><span class="n">url_map</span> <span class="o">=</span> <span class="n">Map</span><span class="p">([</span>
489 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;#select_language&#39;</span><span class="p">),</span>
490 <span class="n">Subdomain</span><span class="p">(</span><span class="s">&#39;&lt;string(length=2):lang_code&gt;&#39;</span><span class="p">,</span> <span class="p">[</span>
491 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;index&#39;</span><span class="p">),</span>
492 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/about&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;about&#39;</span><span class="p">),</span>
493 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/help&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;help&#39;</span><span class="p">)</span>
494 <span class="p">])</span>
495 <span class="p">])</span>
496 </pre></div>
497 <p class="last">All the rules except of the <tt class="docutils literal"><span class="pre">'#select_language'</span></tt> endpoint will now
498 listen on a two letter long subdomain that helds the language code
499 for the current request.</p>
500 </dd>
501 </dl>
502 <dl>
503 <dt><strong>class</strong> <cite id="werkzeug.routing.Submount">Submount</cite></dt>
504 <dd><p class="first">Like <cite>Subdomain</cite> but prefixes the URL rule with a given string:</p>
505 <div class="syntax"><pre><span class="n">url_map</span> <span class="o">=</span> <span class="n">Map</span><span class="p">([</span>
506 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;index&#39;</span><span class="p">),</span>
507 <span class="n">Submount</span><span class="p">(</span><span class="s">&#39;/blog&#39;</span><span class="p">,</span> <span class="p">[</span>
508 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;blog/index&#39;</span><span class="p">),</span>
509 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/entry/&lt;entry_slug&gt;&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;blog/show&#39;</span><span class="p">)</span>
510 <span class="p">])</span>
511 <span class="p">])</span>
512 </pre></div>
513 <p class="last">Now the rule <tt class="docutils literal"><span class="pre">'blog/show'</span></tt> matches <tt class="docutils literal"><span class="pre">/blog/entry/&lt;entry_slug&gt;</span></tt>.</p>
514 </dd>
515 </dl>
516 <dl>
517 <dt><strong>class</strong> <cite id="werkzeug.routing.EndpointPrefix">EndpointPrefix</cite></dt>
518 <dd><p class="first">Prefixes all endpoints (which must be strings for this factory) with
519 another string. This can be useful for sub applications:</p>
520 <div class="syntax"><pre><span class="n">url_map</span> <span class="o">=</span> <span class="n">Map</span><span class="p">([</span>
521 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;index&#39;</span><span class="p">),</span>
522 <span class="n">EndpointPrefix</span><span class="p">(</span><span class="s">&#39;blog/&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Submount</span><span class="p">(</span><span class="s">&#39;/blog&#39;</span><span class="p">,</span> <span class="p">[</span>
523 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;index&#39;</span><span class="p">),</span>
524 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/entry/&lt;entry_slug&gt;&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;show&#39;</span><span class="p">)</span>
525 <span class="p">])])</span>
526 <span class="p">])</span>
527 </pre></div>
528 </dd>
529 </dl>
530 </div>
531 <div class="section">
532 <h3 id="rule-templates">Rule Templates</h3>
533 <dl>
534 <dt><strong>class</strong> <cite id="werkzeug.routing.RuleTemplate">RuleTemplate</cite></dt>
535 <dd><p class="first">Returns copies of the rules wrapped and expands string templates in
536 the endpoint, rule, defaults or subdomain sections.</p>
537 <p>Here a small example for such a rule template:</p>
538 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug.routing</span> <span class="k">import</span> <span class="n">Map</span><span class="p">,</span> <span class="n">Rule</span><span class="p">,</span> <span class="n">RuleTemplate</span>
539
540 <span class="n">resource</span> <span class="o">=</span> <span class="n">RuleTemplate</span><span class="p">([</span>
541 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/$name/&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;$name.list&#39;</span><span class="p">),</span>
542 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/$name/&lt;int:id&gt;&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;$name.show&#39;</span><span class="p">)</span>
543 <span class="p">])</span>
544
545 <span class="n">url_map</span> <span class="o">=</span> <span class="n">Map</span><span class="p">([</span><span class="n">resource</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">&#39;user&#39;</span><span class="p">),</span> <span class="n">resource</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">&#39;page&#39;</span><span class="p">)])</span>
546 </pre></div>
547 <p class="last">When a rule template is called the keyword arguments are used to
548 replace the placeholders in all the string parameters.</p>
549 </dd>
550 </dl>
551 </div>
552 <div class="section">
553 <h3 id="custom-converters">Custom Converters</h3>
554 <p>You can easily add custom converters. The only thing you have to do is to
555 subclass <cite>BaseConverter</cite> and pass that new converter to the url_map. A converter
556 has to provide two public methods: <cite>to_python</cite> and <cite>to_url</cite>, as well as a
557 member that represents a regular expression. Here is a small example:</p>
558 <div class="syntax"><pre><span class="k">from</span> <span class="nn">random</span> <span class="k">import</span> <span class="n">randrange</span>
559 <span class="k">from</span> <span class="nn">werkzeug.routing</span> <span class="k">import</span> <span class="n">Rule</span><span class="p">,</span> <span class="n">Map</span><span class="p">,</span> <span class="n">BaseConverter</span><span class="p">,</span> <span class="n">ValidationError</span>
560
561 <span class="k">class</span> <span class="nc">BooleanConverter</span><span class="p">(</span><span class="n">BaseConverter</span><span class="p">):</span>
562
563 <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">url_map</span><span class="p">,</span> <span class="n">randomify</span><span class="o">=</span><span class="bp">False</span><span class="p">):</span>
564 <span class="nb">super</span><span class="p">(</span><span class="n">BooleanConverter</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">__init__</span><span class="p">(</span><span class="n">url_map</span><span class="p">)</span>
565 <span class="bp">self</span><span class="o">.</span><span class="n">randomify</span> <span class="o">=</span> <span class="n">randomify</span>
566 <span class="bp">self</span><span class="o">.</span><span class="n">regex</span> <span class="o">=</span> <span class="s">&#39;(?:yes|no|maybe)&#39;</span>
567
568 <span class="k">def</span> <span class="nf">to_python</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
569 <span class="k">if</span> <span class="n">value</span> <span class="o">==</span> <span class="s">&#39;maybe&#39;</span><span class="p">:</span>
570 <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">randomify</span><span class="p">:</span>
571 <span class="k">return</span> <span class="ow">not</span> <span class="n">randrange</span><span class="p">(</span><span class="mf">2</span><span class="p">)</span>
572 <span class="k">raise</span> <span class="n">ValidationError</span><span class="p">()</span>
573 <span class="k">return</span> <span class="n">value</span> <span class="o">==</span> <span class="s">&#39;yes&#39;</span>
574
575 <span class="k">def</span> <span class="nf">to_url</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
576 <span class="k">return</span> <span class="n">value</span> <span class="ow">and</span> <span class="s">&#39;yes&#39;</span> <span class="ow">or</span> <span class="s">&#39;no&#39;</span>
577
578 <span class="n">url_map</span> <span class="o">=</span> <span class="n">Map</span><span class="p">([</span>
579 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/vote/&lt;bool:werkzeug_rocks&gt;&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;vote&#39;</span><span class="p">),</span>
580 <span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/vote/&lt;bool(randomify=True):foo&gt;&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;foo&#39;</span><span class="p">)</span>
581 <span class="p">],</span> <span class="n">converters</span><span class="o">=</span><span class="p">{</span><span class="s">&#39;bool&#39;</span><span class="p">:</span> <span class="n">BooleanConverter</span><span class="p">})</span>
582 </pre></div>
583 <p>If you want that converter to be the default converter, name it <tt class="docutils literal"><span class="pre">'default'</span></tt>.</p>
584 </div>
585
586 <div style="clear:both"></div>
587 </div>
588 <div class="footer">
589 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
590 BSD license.
591 </div>
592 </div>
593 </body>
594 </html>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Management Script Utilities // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Management Script Utilities</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#basic-usage" id="id1" name="id1">Basic Usage</a></li>
22 <li><a class="reference" href="#using-the-scripts" id="id2" name="id2">Using The Scripts</a></li>
23 <li><a class="reference" href="#writing-actions" id="id3" name="id3">Writing Actions</a></li>
24 <li><a class="reference" href="#action-discovery" id="id4" name="id4">Action Discovery</a></li>
25 <li><a class="reference" href="#example-scripts" id="id5" name="id5">Example Scripts</a></li>
26 </ul>
27 </div>
28
29 <p>Most of the time you have recurring tasks while writing an application
30 such as starting up an interactive python interpreter with some prefilled
31 imports, starting the development server, initializing the database or
32 something similar.</p>
33 <p>For that purpose werkzeug provides the <cite>werkzeug.script</cite> module which
34 helps you writing such scripts.</p>
35 <div class="section">
36 <h3 id="basic-usage">Basic Usage</h3>
37 <p>The following snippet is roughly the same in every werkzeug script:</p>
38 <div class="syntax"><pre><span class="c">#!/usr/bin/env python</span>
39 <span class="c"># -*- coding: utf-8 -*-</span>
40 <span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">script</span>
41
42 <span class="c"># actions go here</span>
43
44 <span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&#39;__main__&#39;</span><span class="p">:</span>
45 <span class="n">script</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
46 </pre></div>
47 <p>Starting this script now does nothing because no actions are defined.
48 An action is a function in the same module starting with <tt class="docutils literal"><span class="pre">&quot;action_&quot;</span></tt>
49 which takes a number of arguments where every argument has a default. The
50 type of the default value specifies the type of the argument.</p>
51 <p>Arguments can then be passed by position or using <tt class="docutils literal"><span class="pre">--name=value</span></tt> from
52 the shell.</p>
53 <p>Because a runserver and shell command is pretty common there are two
54 factory functions that create such commands:</p>
55 <div class="syntax"><pre><span class="k">def</span> <span class="nf">make_app</span><span class="p">():</span>
56 <span class="k">from</span> <span class="nn">yourapplication</span> <span class="k">import</span> <span class="n">YourApplication</span>
57 <span class="k">return</span> <span class="n">YourApplication</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
58
59 <span class="n">action_runserver</span> <span class="o">=</span> <span class="n">script</span><span class="o">.</span><span class="n">make_runserver</span><span class="p">(</span><span class="n">make_app</span><span class="p">,</span> <span class="n">use_reloader</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
60 <span class="n">action_shell</span> <span class="o">=</span> <span class="n">script</span><span class="o">.</span><span class="n">make_shell</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="p">{</span><span class="s">&#39;app&#39;</span><span class="p">:</span> <span class="n">make_app</span><span class="p">()})</span>
61 </pre></div>
62 </div>
63 <div class="section">
64 <h3 id="using-the-scripts">Using The Scripts</h3>
65 <p>The script from above can be used like this from the shell now:</p>
66 <div class="syntax"><pre>$ ./manage.py --help
67 $ ./manage.py runserver localhost 8080 --debugger --no-reloader
68 $ ./manage.py runserver -p 4000
69 $ ./manage.py shell
70 </pre></div>
71 <p>As you can see it&#8217;s possible to pass parameters as positional arguments
72 or as named parameters, pretty much like Python function calls.</p>
73 </div>
74 <div class="section">
75 <h3 id="writing-actions">Writing Actions</h3>
76 <p>Writing new action functions is pretty straightforward. All you have to do is
77 to name the function <cite>action_COMMAND</cite> and it will be available as
78 <cite>./manage.py COMMAND</cite>. The docstring of the function is used for the help
79 screen and all arguments must have defaults which the <cite>run</cite> function can
80 inspect. As a matter of fact you cannot use <tt class="docutils literal"><span class="pre">*args</span></tt> or <tt class="docutils literal"><span class="pre">**kwargs</span></tt>
81 constructs.</p>
82 <p>An additional feature is the definition of tuples as defaults. The first item
83 in the tuple could be a short name for the command and the second the default
84 value:</p>
85 <div class="syntax"><pre><span class="k">def</span> <span class="nf">action_add_user</span><span class="p">(</span><span class="n">username</span><span class="o">=</span><span class="p">(</span><span class="s">&#39;u&#39;</span><span class="p">,</span> <span class="s">&#39;&#39;</span><span class="p">),</span> <span class="n">password</span><span class="o">=</span><span class="p">(</span><span class="s">&#39;p&#39;</span><span class="p">,</span> <span class="s">&#39;&#39;</span><span class="p">)):</span>
86 <span class="sd">&quot;&quot;&quot;Docstring goes here.&quot;&quot;&quot;</span>
87 <span class="o">...</span>
88 </pre></div>
89 </div>
90 <div class="section">
91 <h3 id="action-discovery">Action Discovery</h3>
92 <p>Per default, the <cite>run</cite> function looks up variables in the current locals.
93 That means if no arguments are provided, it implicitly assumes this call:</p>
94 <div class="syntax"><pre><span class="n">script</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="nb">locals</span><span class="p">(),</span> <span class="s">&#39;action_&#39;</span><span class="p">)</span>
95 </pre></div>
96 <p>If you don&#8217;t want to use an action discovery, you can set the prefix to an
97 empty string and pass a dict with functions:</p>
98 <div class="syntax"><pre><span class="n">script</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="nb">dict</span><span class="p">(</span>
99 <span class="n">runserver</span><span class="o">=</span><span class="n">script</span><span class="o">.</span><span class="n">make_runserver</span><span class="p">(</span><span class="n">make_app</span><span class="p">,</span> <span class="n">use_reloader</span><span class="o">=</span><span class="bp">True</span><span class="p">),</span>
100 <span class="n">shell</span><span class="o">=</span><span class="n">script</span><span class="o">.</span><span class="n">make_shell</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="p">{</span><span class="s">&#39;app&#39;</span><span class="p">:</span> <span class="n">make_app</span><span class="p">()}),</span>
101 <span class="n">initdb</span><span class="o">=</span><span class="n">on_initdb</span>
102 <span class="p">),</span> <span class="s">&#39;&#39;</span><span class="p">)</span>
103 </pre></div>
104 </div>
105 <div class="section">
106 <h3 id="example-scripts">Example Scripts</h3>
107 <p>In the Werkzeug <a class="reference" href="http://dev.pocoo.org/projects/werkzeug/browser/examples">example folder</a> there are some <tt class="docutils literal"><span class="pre">./manage-APP.py</span></tt> scripts
108 using <cite>werkzeug.script</cite>.</p>
109 </div>
110
111 <div style="clear:both"></div>
112 </div>
113 <div class="footer">
114 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
115 BSD license.
116 </div>
117 </div>
118 </body>
119 </html>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Serving WSGI Applications // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Serving WSGI Applications</h2>
18
19 <p>There are many ways to serve a WSGI application. While you&#8217;re developing it,
20 you usually don&#8217;t want to have a full-blown webserver like Apache up and
21 running, but instead a simple standalone one. With Python 2.5 and onwards,
22 there is the <a class="reference" href="http://cheeseshop.python.org/pypi/wsgiref">wsgiref</a> server in the standard library. If you&#8217;re using older
23 versions of Python you can download the package from the <a class="reference" href="http://pypi.python.org/">Cheeseshop</a>.</p>
24 <p>However, there are some caveats. Sourcecode won&#8217;t reload itself when changed,
25 and each time you kill the server using <tt class="docutils literal"><span class="pre">^C</span></tt> you get a <cite>KeyboardInterrupt</cite>
26 error. While the latter is easy to solve, the first one can be a pain in the
27 ass in some situations.</p>
28 <p>Because of that Werkzeug ships a small wrapper over <cite>wsgiref</cite> that spawns the
29 WSGI application in a subprocess and automatically reloads the application if
30 a module was changed.</p>
31 <p>The easiest way is creating a small <tt class="docutils literal"><span class="pre">start-myproject.py</span></tt> file that runs the
32 application:</p>
33 <div class="syntax"><pre><span class="c">#!/usr/bin/env python</span>
34 <span class="c"># -*- coding: utf-8 -*-</span>
35
36 <span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">run_simple</span>
37 <span class="k">from</span> <span class="nn">myproject</span> <span class="k">import</span> <span class="n">make_app</span>
38
39 <span class="n">app</span> <span class="o">=</span> <span class="n">make_app</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
40 <span class="n">run_simple</span><span class="p">(</span><span class="s">&#39;localhost&#39;</span><span class="p">,</span> <span class="mf">8080</span><span class="p">,</span> <span class="n">app</span><span class="p">,</span> <span class="n">use_reloader</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
41 </pre></div>
42 <p>You can also pass it the <cite>extra_files</cite> keyword argument with a list of
43 additional files (like configuration files) you want to observe.</p>
44 <dl>
45 <dt><cite id="werkzeug.serving.run_simple">run_simple</cite> <tt class="func-signature docutils literal"><span class="pre">(hostname,</span> <span class="pre">port,</span> <span class="pre">application,</span> <span class="pre">use_reloader=False,</span> <span class="pre">use_debugger=False,</span> <span class="pre">use_evalex=True,</span> <span class="pre">extra_files=None,</span> <span class="pre">reloader_interval=1,</span> <span class="pre">threaded=False,</span> <span class="pre">processes=1,</span> <span class="pre">request_handler=None)</span></tt></dt>
46 <dd><p class="first">Start an application using wsgiref and with an optional reloader. This
47 wraps <cite>wsgiref</cite> to fix the wrong default reporting of the multithreaded
48 WSGI variable and adds optional multithreading and fork support.</p>
49 <dl class="last">
50 <dt>Parameters</dt>
51 <dd><p class="first"><strong>hostname</strong>: The host for the application. eg: <tt class="docutils literal"><span class="pre">'localhost'</span></tt></p>
52 <p><strong>port</strong>: The port for the server. eg: <tt class="docutils literal"><span class="pre">8080</span></tt></p>
53 <p><strong>application</strong>: the WSGI application to execute</p>
54 <p><strong>use_reloader</strong>: should the server automatically restart the python
55 process if modules were changed?</p>
56 <p><strong>use_debugger</strong>: should the werkzeug debugging system be used?</p>
57 <p><strong>use_evalex</strong>: should the exception evaluation feature be enabled?</p>
58 <p><strong>extra_files</strong>: a list of files the reloader should listen for
59 additionally to the modules. For example configuration
60 files.</p>
61 <p><strong>reloader_interval</strong>: the interval for the reloader in seconds.</p>
62 <p><strong>threaded</strong>: should the process handle each request in a separate
63 thread?</p>
64 <p><strong>processes</strong>: number of processes to spawn.</p>
65 <p class="last"><strong>request_handler</strong>: optional parameter that can be used to replace
66 the default wsgiref request handler. Have a look
67 at the <cite>werkzeug.serving</cite> sourcecode for more
68 details.</p>
69 </dd>
70 </dl>
71 </dd>
72 </dl>
73
74 <div style="clear:both"></div>
75 </div>
76 <div class="footer">
77 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
78 BSD license.
79 </div>
80 </div>
81 </body>
82 </html>
0 body {
1 font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif;
2 font-size: 14px;
3 letter-spacing: -0.01em;
4 line-height: 150%;
5 text-align: center;
6 background-color: #AFC1C4;
7 color: black;
8 margin: 0;
9 padding: 0;
10 }
11
12 a {
13 color: #CA7900;
14 text-decoration: none;
15 }
16
17 a:hover {
18 color: #2491CF;
19 }
20
21 pre {
22 font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
23 font-size: 0.85em;
24 letter-spacing: 0.015em;
25 padding: 0.5em;
26 border: 1px solid #ccc;
27 background-color: #f8f8f8;
28 }
29
30 cite, code, tt {
31 font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
32 font-size: 0.95em;
33 letter-spacing: 0.01em;
34 font-style: normal;
35 }
36
37 tt {
38 background-color: #f2f2f2;
39 border-bottom: 1px solid #ddd;
40 color: #333;
41 }
42
43 tt.func-signature {
44 font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif;
45 font-size: 0.85em;
46 background-color: transparent;
47 border-bottom: none;
48 color: #555;
49 }
50
51 dt {
52 margin-top: 0.8em;
53 }
54
55 dd p.first {
56 margin-top: 0;
57 }
58
59 dd p.last {
60 margin-bottom: 0;
61 }
62
63 pre {
64 line-height: 150%;
65 }
66
67 pre a {
68 color: inherit;
69 text-decoration: underline;
70 }
71
72 div.syntax {
73 background-color: transparent;
74 }
75
76 div.page {
77 background-color: white;
78 border: 1px solid #aaa;
79 width: 740px;
80 margin: 20px auto 20px auto;
81 text-align: left;
82 }
83
84 div.header {
85 background-image: url(header.png);
86 height: 100px;
87 border-bottom: 1px solid #aaa;
88 }
89
90 div.header h1 {
91 float: right;
92 position: absolute;
93 margin: -30px 0 0 585px;
94 height: 180px;
95 width: 180px;
96 }
97
98 div.header h1 a {
99 display: block;
100 background-image: url(werkzeug.png);
101 background-repeat: no-repeat;
102 height: 180px;
103 width: 180px;
104 text-decoration: none;
105 color: white!important;
106 }
107
108 div.header span {
109 display: none;
110 }
111
112 div.header p {
113 background-image: url(header_invert.png);
114 margin: 0;
115 padding: 10px;
116 height: 80px;
117 color: white;
118 display: none;
119 }
120
121 ul.navigation {
122 background-image: url(navigation.png);
123 height: 2em;
124 list-style: none;
125 border-top: 1px solid #ddd;
126 border-bottom: 1px solid #ddd;
127 margin: 0;
128 padding: 0;
129 }
130
131 ul.navigation li {
132 margin: 0;
133 padding: 0;
134 height: 2em;
135 float: left;
136 }
137
138 ul.navigation li a {
139 margin: 0;
140 padding: 0 10px 0 10px;
141 line-height: 1.75em;
142 color: #EE9816;
143 }
144
145 ul.navigation li a:hover {
146 color: #3CA8E7;
147 }
148
149 ul.navigation li.active {
150 background-image: url(navigation_active.png);
151 }
152
153 ul.navigation li.active a {
154 color: black;
155 }
156
157 div.body {
158 background-color: #ffffff;
159 background-image: url(contents.png);
160 background-repeat: repeat-x;
161 margin: 0 20px 0 20px;
162 padding: 0.5em 0 20px 0;
163 }
164
165 p {
166 margin: 0.8em 0 0.5em 0;
167 }
168
169 h2 {
170 margin: 0;
171 padding: 0.7em 0 0.3em 0;
172 font-size: 1.5em;
173 color: #11557C;
174 }
175
176 h3 {
177 margin: 1.3em 0 0.2em 0;
178 font-size: 1.35em;
179 padding: 0;
180 }
181
182 h4 {
183 margin: 1em 0 -0.3em 0;
184 }
185
186 h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
187 color: black!important;
188 }
189
190 h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor {
191 display: none;
192 margin: 0 0 0 0.3em;
193 padding: 0 0.2em 0 0.2em;
194 color: #aaa!important;
195 }
196
197 h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor,
198 h5:hover a.anchor, h6:hover a.anchor {
199 display: inline;
200 }
201
202 h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover,
203 h5 a.anchor:hover, h6 a.anchor:hover {
204 color: #777;
205 background-color: #eee;
206 }
207
208 table {
209 border-collapse: collapse;
210 margin: 0 -0.5em 0 -0.5em;
211 }
212
213 table td, table th {
214 padding: 0.2em 0.5em 0.2em 0.5em;
215 }
216
217 div.footer {
218 background-color: #E3EFF1;
219 color: #86989B;
220 padding: 3px 8px 3px 0;
221 clear: both;
222 font-size: 0.8em;
223 text-align: right;
224 }
225
226 div.footer a {
227 color: #86989B;
228 text-decoration: underline;
229 }
230
231 div.toc {
232 float: right;
233 background-color: white;
234 border: 1px solid #86989B;
235 padding: 0;
236 margin: 0 0 1em 1em;
237 width: 10em;
238 }
239
240 div.toc h4 {
241 margin: 0;
242 font-size: 0.9em;
243 padding: 0.1em 0 0.1em 0.6em;
244 margin: 0;
245 color: white;
246 border-bottom: 1px solid #86989B;
247 background-color: #AFC1C4;
248 }
249
250 div.toc ul {
251 margin: 1em 0 1em 0;
252 padding: 0 0 0 1em;
253 list-style: none;
254 }
255
256 div.toc ul li {
257 margin: 0.5em 0 0.5em 0;
258 font-size: 0.9em;
259 line-height: 130%;
260 }
261
262 div.toc ul li p {
263 margin: 0;
264 padding: 0;
265 }
266
267 div.toc ul ul {
268 margin: 0.2em 0 0.2em 0;
269 padding: 0 0 0 1.8em;
270 }
271
272 div.toc ul ul li {
273 padding: 0;
274 }
275
276 div.admonition, div.warning {
277 font-size: 0.9em;
278 margin: 1em 0 0 0;
279 border: 1px solid #86989B;
280 background-color: #f7f7f7;
281 }
282
283 div.admonition p, div.warning p {
284 margin: 0.5em 1em 0.5em 1em;
285 padding: 0;
286 }
287
288 div.admonition pre, div.warning pre {
289 margin: 0.4em 1em 0.4em 1em;
290 }
291
292 div.admonition p.admonition-title,
293 div.warning p.admonition-title {
294 margin: 0;
295 padding: 0.1em 0 0.1em 0.5em;
296 color: white;
297 border-bottom: 1px solid #86989B;
298 font-weight: bold;
299 background-color: #AFC1C4;
300 }
301
302 div.warning {
303 border: 1px solid #940000;
304 }
305
306 div.warning p.admonition-title {
307 background-color: #CF0000;
308 border-bottom-color: #940000;
309 }
310
311 div.admonition ul, div.admonition ol,
312 div.warning ul, div.warning ol {
313 margin: 0.1em 0.5em 0.5em 3em;
314 padding: 0;
315 }
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Mini Templates // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Mini Templates</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#syntax-elements" id="id1" name="id1">Syntax Elements</a></li>
22 <li><a class="reference" href="#missing-variables" id="id2" name="id2">Missing Variables</a></li>
23 <li><a class="reference" href="#python-2-3-compatibility" id="id3" name="id3">Python 2.3 Compatibility</a></li>
24 <li><a class="reference" href="#the-template-class" id="id4" name="id4">The Template Class</a></li>
25 </ul>
26 </div>
27
28 <p>Werkzeug ships a <strong>minimal</strong> templating system which is useful for small
29 scripts where you just want to generate some HTML and don&#8217;t want another
30 dependency or full blown template engine system.</p>
31 <p>It it however not recommended to use this template system for anything else
32 than simple content generation. The <cite>Template</cite> class can be directly imported
33 from the <cite>werkzeug</cite> module.</p>
34 <p>This template engine recognizes ASP/PHP like blocks and executes the code
35 in them:</p>
36 <div class="syntax"><pre><span class="n">t</span> <span class="o">=</span> <span class="n">Template</span><span class="p">(</span><span class="s">&#39;&lt;</span><span class="si">% f</span><span class="s">or u in users %&gt;${u[&quot;username&quot;]}</span><span class="se">\n</span><span class="s">&lt;</span><span class="si">% e</span><span class="s">ndfor %&gt;&#39;</span><span class="p">)</span>
37 <span class="n">t</span><span class="o">.</span><span class="n">render</span><span class="p">(</span><span class="n">users</span><span class="o">=</span><span class="p">[{</span><span class="s">&#39;username&#39;</span><span class="p">:</span> <span class="s">&#39;John&#39;</span><span class="p">},</span>
38 <span class="p">{</span><span class="s">&#39;username&#39;</span><span class="p">:</span> <span class="s">&#39;Jane&#39;</span><span class="p">}])</span>
39 </pre></div>
40 <p>would result in:</p>
41 <div class="syntax"><pre><span class="n">John</span>
42 <span class="n">Jane</span>
43 </pre></div>
44 <p>You can also create templates from files:</p>
45 <div class="syntax"><pre><span class="n">t</span> <span class="o">=</span> <span class="n">Template</span><span class="o">.</span><span class="n">from_file</span><span class="p">(</span><span class="s">&#39;test.html&#39;</span><span class="p">)</span>
46 </pre></div>
47 <p>The syntax elements are a mixture of django, genshi text and mod_python
48 templates and used internally in werkzeug components.</p>
49 <p>We do not recommend using this template engine in a real environment
50 because is quite slow and does not provide any advanced features. For
51 simple applications (cgi script like) this can however be sufficient.</p>
52 <div class="section">
53 <h3 id="syntax-elements">Syntax Elements</h3>
54 <p>Printing Variables:</p>
55 <div class="syntax"><pre>$variable
56 $variable.attribute[item](some, function)(calls)
57 ${expression} or &lt;%py print expression %&gt;
58 </pre></div>
59 <p>Keep in mind that the print statement adds a newline after the call or
60 a whitespace if it ends with a comma.</p>
61 <p>For Loops:</p>
62 <div class="syntax"><pre>&lt;% for item in seq %&gt;
63 ...
64 &lt;% endfor %&gt;
65 </pre></div>
66 <p>While Loops:</p>
67 <div class="syntax"><pre>&lt;% while expression %&gt;
68 &lt;%py break / continue %&gt;
69 &lt;% endwhile %&gt;
70 </pre></div>
71 <p>If Conditions:</p>
72 <div class="syntax"><pre>&lt;% if expression %&gt;
73 ...
74 &lt;% elif expression %&gt;
75 ...
76 &lt;% else %&gt;
77 ...
78 &lt;% endif %&gt;
79 </pre></div>
80 <p>Python Expressions:</p>
81 <div class="syntax"><pre>&lt;%py
82 ...
83 %&gt;
84
85 &lt;%python
86 ...
87 %&gt;
88 </pre></div>
89 <p>Note on python expressions: You cannot start a loop in a python block
90 and continue it in another one. This example does <em>not</em> work:</p>
91 <div class="syntax"><pre>&lt;%python
92 for item in seq:
93 %&gt;
94 ...
95 </pre></div>
96 <p>Comments:</p>
97 <div class="syntax"><pre>&lt;%#
98 This is a comment
99 %&gt;
100 </pre></div>
101 </div>
102 <div class="section">
103 <h3 id="missing-variables">Missing Variables</h3>
104 <p>If you try to access a missing variable you will get back an <cite>Undefined</cite>
105 object. You can iterate over such an object or print it and it won&#8217;t
106 fail. However every other operation will raise an error. To test if a
107 variable is undefined you can use this expression:</p>
108 <div class="syntax"><pre>&lt;% if variable is Undefined %&gt;
109 ...
110 &lt;% endif %&gt;
111 </pre></div>
112 </div>
113 <div class="section">
114 <h3 id="python-2-3-compatibility">Python 2.3 Compatibility</h3>
115 <p>Because of limitations in Python 2.3 it&#8217;s impossible to achieve the
116 semi-silent variable lookup fallback. If a template relies on undefined
117 variables it won&#8217;t execute under Python 2.3.</p>
118 </div>
119 <div class="section">
120 <h3 id="the-template-class">The Template Class</h3>
121 <dl>
122 <dt><strong>class</strong> <cite id="werkzeug.templates.Template">Template</cite></dt>
123 <dd><p class="first">Represents a simple text based template. It&#8217;s a good idea to load such
124 templates from files on the file system to get better debug output.</p>
125 <dl>
126 <dt><cite id="werkzeug.templates.Template.render">render</cite> <tt class="func-signature docutils literal"><span class="pre">(*args,</span> <span class="pre">**kwargs)</span></tt></dt>
127 <dd>This function accepts either a dict or some keyword arguments which
128 will then be the context the template is evaluated in. The return
129 value will be the rendered template.</dd>
130 </dl>
131 <dl>
132 <dt><cite id="werkzeug.templates.Template.from_file">from_file</cite> <tt class="func-signature docutils literal"><span class="pre">(file,</span> <span class="pre">encoding='utf-8',</span> <span class="pre">errors='strict',</span> <span class="pre">unicode_mode=True)</span></tt></dt>
133 <dd>Load a template from a file.</dd>
134 </dl>
135 <p>Besides the normal global functions and objects, the following functions
136 are added to every namespace: <cite>escape</cite>, <cite>url_encode</cite>, <cite>url_quote</cite>, and
137 <cite>url_quote_plus</cite>. You can change those by subclassing <cite>Template</cite> and
138 overriding the <cite>default_context</cite> dict:</p>
139 <div class="syntax"><pre><span class="k">class</span> <span class="nc">MyTemplate</span><span class="p">(</span><span class="n">Template</span><span class="p">):</span>
140 <span class="n">default_namespace</span> <span class="o">=</span> <span class="p">{</span>
141 <span class="s">&#39;ueber_func&#39;</span><span class="p">:</span> <span class="n">ueber_func</span>
142 <span class="p">}</span>
143 <span class="c"># Now add the old functions, too, because they are useful.</span>
144 <span class="n">default_namespace</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">Template</span><span class="o">.</span><span class="n">default_namespace</span><span class="p">)</span>
145 </pre></div>
146 </dd>
147 </dl>
148 </div>
149
150 <div style="clear:both"></div>
151 </div>
152 <div class="footer">
153 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
154 BSD license.
155 </div>
156 </div>
157 </body>
158 </html>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Important Terms // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Important Terms</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#response-object" id="id1" name="id1">Response Object</a></li>
22 <li><a class="reference" href="#view-function" id="id2" name="id2">View Function</a></li>
23 </ul>
24 </div>
25
26 <p>This page covers important terms used in the documentation and Werkzeug
27 itself.</p>
28 <div class="section">
29 <h3 id="response-object">Response Object</h3>
30 <p>For Werkzeug, a response object is an object that works like a WSGI
31 application but does not do any request processing. Usually you have a view
32 function or controller method that processes the request and assambles a
33 response object.</p>
34 <p>A response object is <em>not</em> necessarily the <cite>BaseResponse</cite> object or a subclass
35 thereof.</p>
36 </div>
37 <div class="section">
38 <h3 id="view-function">View Function</h3>
39 <p>Often people speak of MVC (Model, View, Controller) when developing web
40 applications. However, the Django framework coined MTV (Model, Template,
41 View) which basically means the same but reduces the concept to the data
42 model, a function that processes data from the request and the database and
43 renders a template.</p>
44 <p>Werkzeug itself does not tell you how you should develop applications, but the
45 documentation often speaks of view functions that work roughly the same. The
46 idea of a view function is that it&#8217;s called with a request object (and
47 optionally some parameters from an URL rule) and returns a response object.</p>
48 </div>
49
50 <div style="clear:both"></div>
51 </div>
52 <div class="footer">
53 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
54 BSD license.
55 </div>
56 </div>
57 </body>
58 </html>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Test Utilities // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Test Utilities</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#diving-in" id="id1" name="id1">Diving In</a></li>
22 <li><a class="reference" href="#the-client" id="id2" name="id2">The Client</a></li>
23 </ul>
24 </div>
25
26 <p>Quite often you want to unit-test your application or just check the output
27 from an interactive Python session. In theory that is pretty simple because
28 you can fake a WSGI environment and call the application with a dummy
29 <cite>start_response</cite> and iterate over the application iterator, but there are
30 argumentably better ways to interact with an application.</p>
31 <div class="section">
32 <h3 id="diving-in">Diving In</h3>
33 <p>Werkzeug provides a <cite>Client</cite> object which you can pass a WSGI application (and
34 optionally a response wrapper) which you can use to send virtual requests to
35 the application.</p>
36 <p>A response wrapper is a callable that takes three arguments: the application
37 iterator, the status and finally a list of headers. The default response
38 wrapper returns a tuple. Because response objects have the same signature,
39 you can use them as response wrapper, ideally by subclassing them and hooking
40 in test functionality.</p>
41 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">Client</span><span class="p">,</span> <span class="n">BaseResponse</span><span class="p">,</span> <span class="n">test_app</span>
42 <span class="gp">&gt;&gt;&gt; </span><span class="n">c</span> <span class="o">=</span> <span class="n">Client</span><span class="p">(</span><span class="n">test_app</span><span class="p">,</span> <span class="n">BaseResponse</span><span class="p">)</span>
43 <span class="gp">&gt;&gt;&gt; </span><span class="n">resp</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&#39;/&#39;</span><span class="p">)</span>
44 <span class="gp">&gt;&gt;&gt; </span><span class="n">resp</span><span class="o">.</span><span class="n">status_code</span>
45 <span class="go">200</span>
46 <span class="gp">&gt;&gt;&gt; </span><span class="n">resp</span><span class="o">.</span><span class="n">headers</span>
47 <span class="go">Headers([(&#39;Content-Type&#39;, &#39;text/html; charset=utf-8&#39;)])</span>
48 <span class="gp">&gt;&gt;&gt; </span><span class="n">resp</span><span class="o">.</span><span class="n">response_body</span><span class="o">.</span><span class="n">splitlines</span><span class="p">()[:</span><span class="mf">2</span><span class="p">]</span>
49 <span class="go">[&#39;&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot;&#39;,</span>
50 <span class="go"> &#39; &quot;http://www.w3.org/TR/html4/loose.dtd&quot;&gt;&#39;]</span>
51 </pre></div>
52 <p>Or without a wrapper defined:</p>
53 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">c</span> <span class="o">=</span> <span class="n">Client</span><span class="p">(</span><span class="n">test_app</span><span class="p">)</span>
54 <span class="gp">&gt;&gt;&gt; </span><span class="n">app_iter</span><span class="p">,</span> <span class="n">status</span><span class="p">,</span> <span class="n">headers</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&#39;/&#39;</span><span class="p">)</span>
55 <span class="gp">&gt;&gt;&gt; </span><span class="n">status</span>
56 <span class="go">&#39;200 OK&#39;</span>
57 <span class="gp">&gt;&gt;&gt; </span><span class="n">headers</span>
58 <span class="go">[(&#39;Content-Type&#39;, &#39;text/html; charset=utf-8&#39;)]</span>
59 <span class="gp">&gt;&gt;&gt; </span><span class="s">&#39;&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">app_iter</span><span class="p">)</span><span class="o">.</span><span class="n">splitlines</span><span class="p">()[:</span><span class="mf">2</span><span class="p">]</span>
60 <span class="go">[&#39;&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot;&#39;,</span>
61 <span class="go"> &#39; &quot;http://www.w3.org/TR/html4/loose.dtd&quot;&gt;&#39;]</span>
62 </pre></div>
63 </div>
64 <div class="section">
65 <h3 id="the-client">The Client</h3>
66 <dl>
67 <dt><strong>class</strong> <cite id="werkzeug.test.Client">Client</cite></dt>
68 <dd><p class="first">This class allows to send requests to a wrapped application.</p>
69 <dl>
70 <dt><cite id="werkzeug.test.Client.__init__">__init__</cite> <tt class="func-signature docutils literal"><span class="pre">(application,</span> <span class="pre">response_wrapper=None)</span></tt></dt>
71 <dd><p class="first">The response wrapper can be a class or factory function that takes
72 three arguments: app_iter, status and headers. The default response
73 wrapper just returns a tuple.</p>
74 <p>Example:</p>
75 <div class="syntax"><pre><span class="k">class</span> <span class="nc">ClientResponse</span><span class="p">(</span><span class="n">BaseResponse</span><span class="p">):</span>
76 <span class="o">...</span>
77
78 <span class="n">client</span> <span class="o">=</span> <span class="n">Client</span><span class="p">(</span><span class="n">MyApplication</span><span class="p">(),</span> <span class="n">response_wrapper</span><span class="o">=</span><span class="n">ClientResponse</span><span class="p">)</span>
79 </pre></div>
80 </dd>
81 </dl>
82 <dl>
83 <dt><cite id="werkzeug.test.Client.open">open</cite> <tt class="func-signature docutils literal"><span class="pre">(path='/',</span> <span class="pre">base_url=None,</span> <span class="pre">query_string=None,</span> <span class="pre">method='GET',</span> <span class="pre">data=None,</span> <span class="pre">input_stream=None,</span> <span class="pre">content_type=None,</span> <span class="pre">content_length=0,</span> <span class="pre">errors_stream=None,</span> <span class="pre">multithread=False,</span> <span class="pre">multiprocess=False,</span> <span class="pre">run_once=False,</span> <span class="pre">environ_overrides=None,</span> <span class="pre">as_tuple=False)</span></tt></dt>
84 <dd><p class="first">Takes the same arguments as the <cite>create_environ</cite> function from the
85 utility module with some additions.</p>
86 <p>The first parameter should be the path of the request which defaults to
87 &#8216;/&#8217;. The second one can either be a absolute path (in that case the url
88 host is localhost:80) or a full path to the request with scheme,
89 netloc port and the path to the script.</p>
90 <p>If the <cite>path</cite> contains a query string it will be used, even if the
91 <cite>query_string</cite> parameter was given. If it does not contain one
92 the <cite>query_string</cite> parameter is used as querystring. In that case
93 it can either be a dict, MultiDict or string.</p>
94 <p>The following options exist:</p>
95 <dl class="last">
96 <dt><cite>method</cite></dt>
97 <dd>The request method. Defaults to <cite>GET</cite></dd>
98 <dt><cite>input_stream</cite></dt>
99 <dd>The input stream. Defaults to an empty read only stream.</dd>
100 <dt><cite>data</cite></dt>
101 <dd><p class="first">The data you want to transmit. You can set this to a string and
102 define a content type instead of specifying an input stream.
103 Additionally you can pass a dict with the form data. The values
104 could then be strings (no unicode objects!) which are then url
105 encoded or file objects.</p>
106 <p>A file object for this method is either a file descriptor with
107 an additional <cite>name</cite> attribute (like a file descriptor returned
108 by the <cite>open</cite> / <cite>file</cite> function), a tuple in the form
109 <tt class="docutils literal"><span class="pre">(fd,</span> <span class="pre">filename,</span> <span class="pre">mimetype)</span></tt> (all arguments except fd optional)
110 or as dict with those keys and values.</p>
111 <p class="last">Additionally you can instanciate the <cite>werkzeug.test.File</cite> object
112 (or a subclass of it) and pass it as value.</p>
113 </dd>
114 <dt><cite>content_type</cite></dt>
115 <dd>The content type for this request. Default is an empty content
116 type.</dd>
117 <dt><cite>content_length</cite></dt>
118 <dd>The value for the content length header. Defaults to 0.</dd>
119 <dt><cite>errors_stream</cite></dt>
120 <dd>The wsgi.errors stream. Defaults to <cite>sys.stderr</cite>.</dd>
121 <dt><cite>multithread</cite></dt>
122 <dd>The multithreaded flag for the WSGI Environment. Defaults to
123 <cite>False</cite>.</dd>
124 <dt><cite>multiprocess</cite></dt>
125 <dd>The multiprocess flag for the WSGI Environment. Defaults to
126 <cite>False</cite>.</dd>
127 <dt><cite>run_once</cite></dt>
128 <dd>The run_once flag for the WSGI Environment. Defaults to <cite>False</cite>.</dd>
129 </dl>
130 </dd>
131 </dl>
132 <dl>
133 <dt><cite id="werkzeug.test.Client.get">get</cite> <tt class="func-signature docutils literal"><span class="pre">(*args,</span> <span class="pre">**kw)</span></tt></dt>
134 <dd>Like open but method is enforced to GET</dd>
135 </dl>
136 <dl>
137 <dt><cite id="werkzeug.test.Client.head">head</cite> <tt class="func-signature docutils literal"><span class="pre">(*args,</span> <span class="pre">**kw)</span></tt></dt>
138 <dd>Like open but method is enforced to HEAD</dd>
139 </dl>
140 <dl>
141 <dt><cite id="werkzeug.test.Client.post">post</cite> <tt class="func-signature docutils literal"><span class="pre">(*args,</span> <span class="pre">**kw)</span></tt></dt>
142 <dd>Like open but method is enforced to POST</dd>
143 </dl>
144 <dl>
145 <dt><cite id="werkzeug.test.Client.put">put</cite> <tt class="func-signature docutils literal"><span class="pre">(*args,</span> <span class="pre">**kw)</span></tt></dt>
146 <dd>Like open but method is enforced to PUT</dd>
147 </dl>
148 <dl class="last">
149 <dt><cite id="werkzeug.test.Client.delete">delete</cite> <tt class="func-signature docutils literal"><span class="pre">(*args,</span> <span class="pre">**kw)</span></tt></dt>
150 <dd>Like open but method is enforced to DELETE</dd>
151 </dl>
152 </dd>
153 </dl>
154 </div>
155
156 <div style="clear:both"></div>
157 </div>
158 <div class="footer">
159 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
160 BSD license.
161 </div>
162 </div>
163 </body>
164 </html>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Werkzeug Tutorial // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Werkzeug Tutorial</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#part-0-the-folder-structure" id="id1" name="id1">Part 0: The Folder Structure</a></li>
22 <li><a class="reference" href="#part-1-the-wsgi-application" id="id2" name="id2">Part 1: The WSGI Application</a></li>
23 <li><a class="reference" href="#part-2-the-utilities" id="id3" name="id3">Part 2: The Utilities</a></li>
24 <li><a class="reference" href="#intermission-and-now-for-something-completely-different" id="id4" name="id4">Intermission: And Now For Something Completely Different</a></li>
25 <li><a class="reference" href="#part-3-database-models" id="id5" name="id5">Part 3: Database Models</a></li>
26 <li><a class="reference" href="#part-4-the-view-functions" id="id6" name="id6">Part 4: The View Functions</a></li>
27 <li><a class="reference" href="#part-5-the-templates" id="id7" name="id7">Part 5: The Templates</a></li>
28 <li><a class="reference" href="#intermission-adding-the-design" id="id8" name="id8">Intermission: Adding The Design</a></li>
29 <li><a class="reference" href="#part-6-listing-public-urls" id="id9" name="id9">Part 6: Listing Public URLs</a></li>
30 <li><a class="reference" href="#bonus-styling-404-error-pages" id="id10" name="id10">Bonus: Styling 404 Error Pages</a></li>
31 <li><a class="reference" href="#outro" id="id11" name="id11">Outro</a></li>
32 </ul>
33 </div>
34
35 <div class="admonition-translations admonition">
36 <p class="first admonition-title">Translations</p>
37 <p>This tutorial is available in the following languages:</p>
38 <ul class="last simple">
39 <li><strong>English</strong></li>
40 <li><a class="reference" href="tutorial_de.html">Deutsch</a></li>
41 </ul>
42 </div>
43 <p>Welcome to the Werkzeug 0.2 tutorial in which we will create a <a class="reference" href="http://tinyurl.com/">TinyURL</a> clone
44 that stores URLs in a database. The libraries we will use for this
45 applications are <a class="reference" href="http://jinja.pocoo.org/">Jinja</a> for the templates, <a class="reference" href="http://sqlalchemy.org/">SQLAlchemy</a> for the database
46 layer and, of course, Werkzeug for the WSGI layer.</p>
47 <p>The reasons why we&#8217;ve decided on these libraries for the tutorial application
48 is that we want to stick to some of the design decisions <a class="reference" href="http://www.djangoproject.com/">Django</a> took in the
49 past. One of them is using view functions instead of controller classes with
50 action methods, which is common in <a class="reference" href="http://www.rubyonrails.org/">Rails</a> and <a class="reference" href="http://pylonshq.com/">Pylons</a>, the other one is
51 designer-friendly templates.</p>
52 <p>The Werkzeug <a class="reference" href="http://dev.pocoo.org/projects/werkzeug/browser/examples">example folder</a> contains a couple of applications that use other
53 template engines, too, so you may want to have a look at them. There is also
54 the source code of this application.</p>
55 <p>You can use <a class="reference" href="http://peak.telecommunity.com/DevCenter/EasyInstall">easy_install</a> to install the required libraries:</p>
56 <div class="syntax"><pre><span class="n">sudo</span> <span class="n">easy_install</span> <span class="n">Jinja</span>
57 <span class="n">sudo</span> <span class="n">easy_install</span> <span class="n">SQLAlchemy</span>
58 </pre></div>
59 <p>If you&#8217;re on Windows, omit the &#8220;sudo&#8221; (and make sure, <a class="reference" href="http://pypi.python.org/pypi/setuptools">setuptools</a> is
60 installed); if you&#8217;re on OS X, you can check if the libraries are also
61 available in port; or on Linux, you can check out your package manager for
62 packages called <tt class="docutils literal"><span class="pre">python-jinja</span></tt> and <tt class="docutils literal"><span class="pre">python-sqlalchemy</span></tt>.</p>
63 <p>If you&#8217;re curious, check out the <a class="reference" href="http://werkzeug.pocoo.org/e/shorty/">online demo</a> of the application.</p>
64 <p>Small disclaimer: This tutorial requires Python 2.4 or later.</p>
65 <div class="section">
66 <h3 id="part-0-the-folder-structure">Part 0: The Folder Structure</h3>
67 <p>Before we can get started we have to create a Python package for our Werkzeug
68 application and the folders for the templates and static files.</p>
69 <p>This tutorial application is called <cite>shorty</cite> and the initial directory layout
70 we will use looks like this:</p>
71 <div class="syntax"><pre><span class="n">manage</span><span class="o">.</span><span class="n">py</span>
72 <span class="n">shorty</span><span class="o">/</span>
73 <span class="n">__init__</span><span class="o">.</span><span class="n">py</span>
74 <span class="n">templates</span><span class="o">/</span>
75 <span class="n">static</span><span class="o">/</span>
76 </pre></div>
77 <p>The <tt class="docutils literal"><span class="pre">__init__.py</span></tt> and <tt class="docutils literal"><span class="pre">manage.py</span></tt> files should be empty for the time
78 being. The first one makes <tt class="docutils literal"><span class="pre">shorty</span></tt> a Python package, the second one will
79 hold our management utilities later.</p>
80 </div>
81 <div class="section">
82 <h3 id="part-1-the-wsgi-application">Part 1: The WSGI Application</h3>
83 <p>Unlike Django or other frameworks, Werkzeug operates directly on the WSGI
84 layer. There is no fancy magic that implements the central WSGI application
85 for you. As a result of that the first thing you will do every time you write
86 a Werkzeug application is implementing this basic WSGI application object.
87 This can now either be a function or, even better, a callable class.</p>
88 <p>A callable class has huge advantages over a function. For one you can pass
89 it some configuration parameters and furthermore you can use inline WSGI
90 middlewares. Inline WSGI middlewares are basically middlewares applied
91 &#8220;inside&#8221; of our application object. This is a good idea for middlewares that
92 are essential for the application (session middlewares, serving of media
93 files etc.).</p>
94 <p>Here the initial code for our <tt class="docutils literal"><span class="pre">shorty/application.py</span></tt> file which implements
95 the WSGI application:</p>
96 <div class="syntax"><pre><span class="k">from</span> <span class="nn">sqlalchemy</span> <span class="k">import</span> <span class="n">create_engine</span>
97 <span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">Request</span><span class="p">,</span> <span class="n">ClosingIterator</span>
98 <span class="k">from</span> <span class="nn">werkzeug.exceptions</span> <span class="k">import</span> <span class="n">HTTPException</span>
99
100 <span class="k">from</span> <span class="nn">shorty.utils</span> <span class="k">import</span> <span class="n">session</span><span class="p">,</span> <span class="n">metadata</span><span class="p">,</span> <span class="n">local</span><span class="p">,</span> <span class="n">local_manager</span><span class="p">,</span> <span class="n">url_map</span>
101 <span class="k">from</span> <span class="nn">shorty</span> <span class="k">import</span> <span class="n">views</span>
102 <span class="k">import</span> <span class="nn">shorty.models</span>
103
104
105 <span class="k">class</span> <span class="nc">Shorty</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
106
107 <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">db_uri</span><span class="p">):</span>
108 <span class="n">local</span><span class="o">.</span><span class="n">application</span> <span class="o">=</span> <span class="bp">self</span>
109 <span class="bp">self</span><span class="o">.</span><span class="n">database_engine</span> <span class="o">=</span> <span class="n">create_engine</span><span class="p">(</span><span class="n">db_uri</span><span class="p">,</span> <span class="n">convert_unicode</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
110
111 <span class="k">def</span> <span class="nf">init_database</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
112 <span class="n">metadata</span><span class="o">.</span><span class="n">create_all</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">database_engine</span><span class="p">)</span>
113
114 <span class="k">def</span> <span class="nf">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
115 <span class="n">local</span><span class="o">.</span><span class="n">application</span> <span class="o">=</span> <span class="bp">self</span>
116 <span class="n">request</span> <span class="o">=</span> <span class="n">Request</span><span class="p">(</span><span class="n">environ</span><span class="p">)</span>
117 <span class="n">local</span><span class="o">.</span><span class="n">url_adapter</span> <span class="o">=</span> <span class="n">adapter</span> <span class="o">=</span> <span class="n">url_map</span><span class="o">.</span><span class="n">bind_to_environ</span><span class="p">(</span><span class="n">environ</span><span class="p">)</span>
118 <span class="k">try</span><span class="p">:</span>
119 <span class="n">endpoint</span><span class="p">,</span> <span class="n">values</span> <span class="o">=</span> <span class="n">adapter</span><span class="o">.</span><span class="n">match</span><span class="p">()</span>
120 <span class="n">handler</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">views</span><span class="p">,</span> <span class="n">endpoint</span><span class="p">)</span>
121 <span class="n">response</span> <span class="o">=</span> <span class="n">handler</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">**</span><span class="n">values</span><span class="p">)</span>
122 <span class="k">except</span> <span class="n">HTTPException</span><span class="p">,</span> <span class="n">e</span><span class="p">:</span>
123 <span class="n">response</span> <span class="o">=</span> <span class="n">e</span>
124 <span class="k">return</span> <span class="n">ClosingIterator</span><span class="p">(</span><span class="n">response</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">),</span>
125 <span class="p">[</span><span class="n">session</span><span class="o">.</span><span class="n">remove</span><span class="p">,</span> <span class="n">local_manager</span><span class="o">.</span><span class="n">cleanup</span><span class="p">])</span>
126 </pre></div>
127 <p>That&#8217;s a lot of code for the beginning! Let&#8217;s go through it step by step.
128 First we have a couple of imports: From SQLAlchemy we import a factory
129 function that creates a new database engine for us. A database engine holds
130 a pool of connections for us and manages them. The next few imports pull some
131 objects into the namespace Werkzeug provides: a request object, a special
132 iterator class that helps us cleaning up stuff at the request end and finally
133 the base class for all HTTP exceptions.</p>
134 <p>The next five imports are not working because we don&#8217;t have the utils module
135 written yet. However we should cover some of the objects already. The
136 <cite>session</cite> object pulled from there is not a PHP-like session object but a
137 SQLAlchemy database session object. Basically a database session object keeps
138 track of yet uncommited objects for the database. Unlike Django, an
139 instantiated SQLAlchemy model is already tracked by the session! The metadata
140 object is also an SQLAlchemy thing which is used to keep track of tables. We
141 can use the metadata object to easily create all tables for the database and
142 SQLAlchemy uses it to look up foreign keys and similar stuff.</p>
143 <p>The <cite>local</cite> object is basically a thread local object created in the utility
144 module for us. Every attribute on this object is bound to the current request
145 and we can use this to implicitly pass objects around in a thread-safe way.</p>
146 <p>The <cite>local_manager</cite> object ensures that all local objects it keeps track of
147 are properly deleted at the end of the request.</p>
148 <p>The last thing we import from there is the URL map which holds the URL routing
149 information. If you know Django you can compare that to the url patterns you
150 specify in the <tt class="docutils literal"><span class="pre">urls.py</span></tt> module, if you have used PHP so far it&#8217;s comparable
151 with some sort of built-in &#8220;mod_rewrite&#8221;.</p>
152 <p>We import our views module which holds the view functions and then we import
153 the models module which holds all of our models. Even if it looks like we
154 don&#8217;t use that import it&#8217;s there so that all the tables are registered on the
155 metadata properly.</p>
156 <p>So let&#8217;s have a look at the application class. The constructor of this class
157 takes a database URI which is basically the type of the database and the login
158 credentials or location of the database. For SQLite this is for example
159 <tt class="docutils literal"><span class="pre">'sqlite:////tmp/shorty.db'</span></tt> (note that these are <strong>four</strong> slashes).</p>
160 <p>In the constructor we create a database engine for that database URI and use
161 the <cite>convert_unicode</cite> parameter to tell SQLAlchemy that our strings are all
162 unicode objects.</p>
163 <p>Another thing we do here is binding the application to the local object. This
164 is not really required but useful if we want to play with the application in
165 a python shell. On application instanciation we have it bound to the current
166 thread and all the database functions will work as expected. If we don&#8217;t do
167 that Werkzeug will complain that it&#8217;s unable to find the database when it&#8217;s
168 creating a session for SQLAlchemy.</p>
169 <p>The <cite>init_database</cite> function defined below can be used to create all the
170 tables we use.</p>
171 <p>And then comes the request dispatching function. In there we create a new
172 request object by passing the environment to the <cite>Request</cite> constructor.
173 Once again we bind the application to the local object, this time, however,
174 we have to do this, otherwise things will break soon.</p>
175 <p>Then we create a new URL map adapter by binding the URL map to the current
176 WSGI environment. This basically looks at the environment of the incoming
177 request information and fetches the information from the environment it
178 requires. This is for example the name of the server for external URLs, the
179 location of the script so that it can generate absolute paths if we use the
180 URL builder. We also bind the adapter to the local object so that we can
181 use it for URL generation in the utils module.</p>
182 <p>After that we have a <cite>try</cite>/<cite>except</cite> that catches HTTP exceptions that could
183 occur while matching or in the view function. When the adapter does not find
184 a valid endpoint for our current request it will raise a <cite>NotFound</cite> exception
185 which we can use like a response object. An endpoint is basically the name
186 of the function we want to handle our request with. We just get the
187 function with the name of the endpoint and pass it the request and the URL
188 values.</p>
189 <p>At the end of the function we call the response object as WSGI application
190 and pass the return value of this function (which will be an iterable) to
191 the closing iterator class along with our cleanup callbacks (which remove
192 the current SQLAlchemy session and clean up the data left in the local
193 objects).</p>
194 <p>As next step create two empty files <tt class="docutils literal"><span class="pre">shorty/views.py</span></tt> and
195 <tt class="docutils literal"><span class="pre">shorty/models.py</span></tt> so that our imports work. We will fill the modules with
196 useful code later.</p>
197 </div>
198 <div class="section">
199 <h3 id="part-2-the-utilities">Part 2: The Utilities</h3>
200 <p>Now we have basically finished the WSGI application itself but we have to add
201 some more code into our utiliy module so that the imports work. For the time
202 being we just add the objects which we need for the application to work. All
203 the following code goes into the <tt class="docutils literal"><span class="pre">shorty/utils.py</span></tt> file:</p>
204 <div class="syntax"><pre><span class="k">from</span> <span class="nn">sqlalchemy</span> <span class="k">import</span> <span class="n">MetaData</span>
205 <span class="k">from</span> <span class="nn">sqlalchemy.orm</span> <span class="k">import</span> <span class="n">create_session</span><span class="p">,</span> <span class="n">scoped_session</span>
206 <span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">Local</span><span class="p">,</span> <span class="n">LocalManager</span>
207 <span class="k">from</span> <span class="nn">werkzeug.routing</span> <span class="k">import</span> <span class="n">Map</span><span class="p">,</span> <span class="n">Rule</span>
208
209 <span class="n">local</span> <span class="o">=</span> <span class="n">Local</span><span class="p">()</span>
210 <span class="n">local_manager</span> <span class="o">=</span> <span class="n">LocalManager</span><span class="p">([</span><span class="n">local</span><span class="p">])</span>
211 <span class="n">application</span> <span class="o">=</span> <span class="n">local</span><span class="p">(</span><span class="s">&#39;application&#39;</span><span class="p">)</span>
212
213 <span class="n">metadata</span> <span class="o">=</span> <span class="n">MetaData</span><span class="p">()</span>
214 <span class="n">session</span> <span class="o">=</span> <span class="n">scoped_session</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="n">create_session</span><span class="p">(</span><span class="n">application</span><span class="o">.</span><span class="n">database_engine</span><span class="p">,</span>
215 <span class="n">transactional</span><span class="o">=</span><span class="bp">True</span><span class="p">),</span> <span class="n">local_manager</span><span class="o">.</span><span class="n">get_ident</span><span class="p">)</span>
216
217 <span class="n">url_map</span> <span class="o">=</span> <span class="n">Map</span><span class="p">()</span>
218 <span class="k">def</span> <span class="nf">expose</span><span class="p">(</span><span class="n">rule</span><span class="p">,</span> <span class="o">**</span><span class="n">kw</span><span class="p">):</span>
219 <span class="k">def</span> <span class="nf">decorate</span><span class="p">(</span><span class="n">f</span><span class="p">):</span>
220 <span class="n">kw</span><span class="p">[</span><span class="s">&#39;endpoint&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">__name__</span>
221 <span class="n">url_map</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Rule</span><span class="p">(</span><span class="n">rule</span><span class="p">,</span> <span class="o">**</span><span class="n">kw</span><span class="p">))</span>
222 <span class="k">return</span> <span class="n">f</span>
223 <span class="k">return</span> <span class="n">decorate</span>
224
225 <span class="k">def</span> <span class="nf">url_for</span><span class="p">(</span><span class="n">endpoint</span><span class="p">,</span> <span class="n">_external</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="o">**</span><span class="n">values</span><span class="p">):</span>
226 <span class="k">return</span> <span class="n">local</span><span class="o">.</span><span class="n">url_adapter</span><span class="o">.</span><span class="n">build</span><span class="p">(</span><span class="n">endpoint</span><span class="p">,</span> <span class="n">values</span><span class="p">,</span> <span class="n">force_external</span><span class="o">=</span><span class="n">_external</span><span class="p">)</span>
227 </pre></div>
228 <p>First we again import a bunch of stuff, then we create the local objects and
229 the local manager we already discussed in the section above. The new thing
230 here is that calling a local object with a string returns a proxy object. This
231 returned proxy object always points to the attribute with that name on the
232 local object. For example <cite>application</cite> now points to <cite>local.application</cite>
233 all the time. If you, however, try to do something with it and there is
234 no object bound to <cite>local.application</cite> you will get a <cite>RuntimeError</cite>.</p>
235 <p>The next three lines are basically everything we need to get SQLAlchemy 0.4
236 or higher running in a Werkzeug application. We create a new metadata for all
237 of our tables and then a new scoped session using the <cite>scoped_session</cite> factory
238 function. This basically tells SQLAlchemy to use the same algorithm to
239 determine the current context as werkzeug local does and use the database
240 engine of the current application.</p>
241 <p>If we don&#8217;t plan to add support for multiple instances of the application in
242 the same python interpreter we can also simplify that code by not looking up
243 the application on the current local object but somewhere else. This approach
244 is for example used by Django but makes it impossible to combine multiple such
245 applications.</p>
246 <p>The rest of the module is code we will use in our views. Basically the idea
247 there is to use decorators to specify the URL dispatching rule for a view
248 function rather than a central <tt class="docutils literal"><span class="pre">urls.py</span></tt> file like you could do in Django
249 or a <tt class="docutils literal"><span class="pre">.htaccess</span></tt> for URL rewrites like you would do in PHP. This is
250 <strong>one</strong> way to do it and there are countless of other ways to handle rule
251 definitions.</p>
252 <p>The <cite>url_for</cite> function, which we have there too, provides a simple way to
253 generate URLs by endpoint. We will use it in the views and our model later.</p>
254 </div>
255 <div class="section">
256 <h3 id="intermission-and-now-for-something-completely-different">Intermission: And Now For Something Completely Different</h3>
257 <p>Now that we have finished the foundation for the application we could relax
258 and do something completely different: management scripts. Most of the time
259 you do similar tasks while developing. One of them is firing up a
260 development server (If you&#8217;re used to PHP: Werkzeug does not rely on Apache
261 for development, it&#8217;s perfectly fine and also recommended to use the wsgiref
262 server that comes with python for development purposes), starting a python
263 interpreter to play with the database models, initializing the database etc.</p>
264 <p>Werkzeug makes it incredible easy to write such management scripts. The
265 following piece of code implements a fully featured management script. Put
266 it into the <cite>manage.py</cite> file you have created in the beginning:</p>
267 <div class="syntax"><pre><span class="c">#!/usr/bin/env python</span>
268 <span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">script</span>
269
270 <span class="k">def</span> <span class="nf">make_app</span><span class="p">():</span>
271 <span class="k">from</span> <span class="nn">shorty.application</span> <span class="k">import</span> <span class="n">Shorty</span>
272 <span class="k">return</span> <span class="n">Shorty</span><span class="p">(</span><span class="s">&#39;sqlite:////tmp/shorty.db&#39;</span><span class="p">)</span>
273
274 <span class="k">def</span> <span class="nf">make_shell</span><span class="p">():</span>
275 <span class="k">from</span> <span class="nn">shorty</span> <span class="k">import</span> <span class="n">models</span><span class="p">,</span> <span class="n">utils</span>
276 <span class="n">application</span> <span class="o">=</span> <span class="n">make_app</span><span class="p">()</span>
277 <span class="k">return</span> <span class="nb">locals</span><span class="p">()</span>
278
279 <span class="n">action_runserver</span> <span class="o">=</span> <span class="n">script</span><span class="o">.</span><span class="n">make_runserver</span><span class="p">(</span><span class="n">make_app</span><span class="p">,</span> <span class="n">use_reloader</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
280 <span class="n">action_shell</span> <span class="o">=</span> <span class="n">script</span><span class="o">.</span><span class="n">make_shell</span><span class="p">(</span><span class="n">make_shell</span><span class="p">)</span>
281 <span class="n">action_initdb</span> <span class="o">=</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">make_app</span><span class="p">()</span><span class="o">.</span><span class="n">init_database</span><span class="p">()</span>
282
283 <span class="n">script</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
284 </pre></div>
285 <p><cite>werkzeug.script</cite> is explained in detail in the <a class="reference" href="script.html">script documentation</a> and
286 we won&#8217;t cover it here, most of the code should be self explaining anyway.</p>
287 <p>What&#8217;s important is that you should be able to run <tt class="docutils literal"><span class="pre">python</span> <span class="pre">manage.py</span> <span class="pre">shell</span></tt>
288 to get an interactive Python interpreter without traceback. If you get an
289 exception check the line number and compare your code with the code we have
290 in the code boxes above.</p>
291 <p>Now that the script system is running we can start writing our database models.</p>
292 </div>
293 <div class="section">
294 <h3 id="part-3-database-models">Part 3: Database Models</h3>
295 <p>Now we can create the models. Because the application is pretty simple we
296 just have one model and table:</p>
297 <div class="syntax"><pre><span class="k">from</span> <span class="nn">datetime</span> <span class="k">import</span> <span class="n">datetime</span>
298 <span class="k">from</span> <span class="nn">sqlalchemy</span> <span class="k">import</span> <span class="n">Table</span><span class="p">,</span> <span class="n">Column</span><span class="p">,</span> <span class="n">String</span><span class="p">,</span> <span class="n">Boolean</span><span class="p">,</span> <span class="n">DateTime</span>
299 <span class="k">from</span> <span class="nn">shorty.utils</span> <span class="k">import</span> <span class="n">session</span><span class="p">,</span> <span class="n">metadata</span><span class="p">,</span> <span class="n">url_for</span><span class="p">,</span> <span class="n">get_random_uid</span>
300
301 <span class="n">url_table</span> <span class="o">=</span> <span class="n">Table</span><span class="p">(</span><span class="s">&#39;urls&#39;</span><span class="p">,</span> <span class="n">metadata</span><span class="p">,</span>
302 <span class="n">Column</span><span class="p">(</span><span class="s">&#39;uid&#39;</span><span class="p">,</span> <span class="n">String</span><span class="p">(</span><span class="mf">140</span><span class="p">),</span> <span class="n">primary_key</span><span class="o">=</span><span class="bp">True</span><span class="p">),</span>
303 <span class="n">Column</span><span class="p">(</span><span class="s">&#39;target&#39;</span><span class="p">,</span> <span class="n">String</span><span class="p">(</span><span class="mf">500</span><span class="p">)),</span>
304 <span class="n">Column</span><span class="p">(</span><span class="s">&#39;added&#39;</span><span class="p">,</span> <span class="n">DateTime</span><span class="p">),</span>
305 <span class="n">Column</span><span class="p">(</span><span class="s">&#39;public&#39;</span><span class="p">,</span> <span class="n">Boolean</span><span class="p">)</span>
306 <span class="p">)</span>
307
308 <span class="k">class</span> <span class="nc">URL</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
309
310 <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="n">public</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">uid</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">added</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
311 <span class="bp">self</span><span class="o">.</span><span class="n">target</span> <span class="o">=</span> <span class="n">target</span>
312 <span class="bp">self</span><span class="o">.</span><span class="n">public</span> <span class="o">=</span> <span class="n">public</span>
313 <span class="bp">self</span><span class="o">.</span><span class="n">added</span> <span class="o">=</span> <span class="n">added</span> <span class="ow">or</span> <span class="n">datetime</span><span class="o">.</span><span class="n">utcnow</span><span class="p">()</span>
314 <span class="k">if</span> <span class="ow">not</span> <span class="n">uid</span><span class="p">:</span>
315 <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
316 <span class="n">uid</span> <span class="o">=</span> <span class="n">get_random_uid</span><span class="p">()</span>
317 <span class="k">if</span> <span class="ow">not</span> <span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">uid</span><span class="p">):</span>
318 <span class="k">break</span>
319 <span class="bp">self</span><span class="o">.</span><span class="n">uid</span> <span class="o">=</span> <span class="n">uid</span>
320
321 <span class="nd">@property</span>
322 <span class="k">def</span> <span class="nf">short_url</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
323 <span class="k">return</span> <span class="n">url_for</span><span class="p">(</span><span class="s">&#39;link&#39;</span><span class="p">,</span> <span class="n">uid</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">uid</span><span class="p">,</span> <span class="n">_external</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
324
325 <span class="k">def</span> <span class="nf">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
326 <span class="k">return</span> <span class="s">&#39;&lt;URL </span><span class="si">%r</span><span class="s">&gt;&#39;</span> <span class="o">%</span> <span class="bp">self</span><span class="o">.</span><span class="n">uid</span>
327
328 <span class="n">session</span><span class="o">.</span><span class="n">mapper</span><span class="p">(</span><span class="n">URL</span><span class="p">,</span> <span class="n">url_table</span><span class="p">)</span>
329 </pre></div>
330 <p>This module is pretty straightforward. We import all the stuff we need from
331 SQLAlchemy and create a table. Then we add a class for this table and we map
332 them both together. For detailed explanations regarding SQLAlchemy you should
333 have a look at the <a class="reference" href="http://www.sqlalchemy.org/docs/04/ormtutorial.html">excellent tutorial</a>.</p>
334 <p>In the constructor we generate a unique ID until we find an id which is still
335 free to use.
336 What&#8217;s missing is the <cite>get_random_uid</cite> function we have to add to the utils
337 module:</p>
338 <div class="syntax"><pre><span class="k">from</span> <span class="nn">random</span> <span class="k">import</span> <span class="n">sample</span><span class="p">,</span> <span class="n">randrange</span>
339
340 <span class="n">URL_CHARS</span> <span class="o">=</span> <span class="s">&#39;abcdefghijkmpqrstuvwxyzABCDEFGHIJKLMNPQRST23456789&#39;</span>
341
342 <span class="k">def</span> <span class="nf">get_random_uid</span><span class="p">():</span>
343 <span class="k">return</span> <span class="s">&#39;&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">sample</span><span class="p">(</span><span class="n">URL_CHARS</span><span class="p">,</span> <span class="n">randrange</span><span class="p">(</span><span class="mf">3</span><span class="p">,</span> <span class="mf">9</span><span class="p">)))</span>
344 </pre></div>
345 <p>Once that is done we can use <tt class="docutils literal"><span class="pre">python</span> <span class="pre">manage.py</span> <span class="pre">initdb</span></tt> to initialize the
346 database and play around with the stuff using <tt class="docutils literal"><span class="pre">python</span> <span class="pre">manage.py</span> <span class="pre">shell</span></tt>:</p>
347 <div class="syntax"><pre><span class="go">Interactive Werkzeug Shell</span>
348 <span class="gp">&gt;&gt;&gt; </span><span class="k">from</span> <span class="nn">shorty.models</span> <span class="k">import</span> <span class="n">session</span><span class="p">,</span> <span class="n">URL</span>
349 </pre></div>
350 <p>Now we can add some URLs to the database:</p>
351 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">urls</span> <span class="o">=</span> <span class="p">[</span><span class="n">URL</span><span class="p">(</span><span class="s">&#39;http://example.org/&#39;</span><span class="p">),</span> <span class="n">URL</span><span class="p">(</span><span class="s">&#39;http://localhost:5000/&#39;</span><span class="p">)]</span>
352 <span class="gp">&gt;&gt;&gt; </span><span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
353 <span class="go">[]</span>
354 <span class="gp">&gt;&gt;&gt; </span><span class="n">session</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
355 <span class="gp">&gt;&gt;&gt; </span><span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
356 <span class="go">[&lt;URL &#39;5cFbsk&#39;&gt;, &lt;URL &#39;mpugsT&#39;&gt;]</span>
357 </pre></div>
358 <p>As you can see we have to commit in order to send the urls to the database.
359 Let&#8217;s create a private item with a custom uid:</p>
360 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">URL</span><span class="p">(</span><span class="s">&#39;http://werkzeug.pocoo.org/&#39;</span><span class="p">,</span> <span class="bp">False</span><span class="p">,</span> <span class="s">&#39;werkzeug-webpage&#39;</span><span class="p">)</span>
361 <span class="gp">&gt;&gt;&gt; </span><span class="n">session</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
362 </pre></div>
363 <p>And query them all:</p>
364 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">filter_by</span><span class="p">(</span><span class="n">public</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
365 <span class="go">[&lt;URL &#39;werkzeug-webpage&#39;&gt;]</span>
366 <span class="gp">&gt;&gt;&gt; </span><span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">filter_by</span><span class="p">(</span><span class="n">public</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
367 <span class="go">[&lt;URL &#39;5cFbsk&#39;&gt;, &lt;URL &#39;mpugsT&#39;&gt;]</span>
368 <span class="gp">&gt;&gt;&gt; </span><span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&#39;werkzeug-webpage&#39;</span><span class="p">)</span>
369 <span class="go">&lt;URL &#39;werkzeug-webpage&#39;&gt;</span>
370 </pre></div>
371 <p>Now that we have some data in the database and we are somewhat familiar with
372 the way SQLAlchemy works, it&#8217;s time to create our views.</p>
373 </div>
374 <div class="section">
375 <h3 id="part-4-the-view-functions">Part 4: The View Functions</h3>
376 <p>Now after some playing with SQLAlchemy we can go back to Werkzeug and start
377 creating our view functions. The term &#8220;view function&#8221; is derived from Django
378 which also calls the functions that render templates &#8220;view functions&#8221;. So
379 our example is MTV (Model, View, Template) and not MVC (Model, View,
380 Controller). They are probably the same but it&#8217;s a lot easier to use the
381 Django way of naming those things.</p>
382 <p>For the beginning we just create a view function for new URLs and a function
383 that displays a message about a new link. All that code goes into our still
384 empty <tt class="docutils literal"><span class="pre">views.py</span></tt> file:</p>
385 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">redirect</span>
386 <span class="k">from</span> <span class="nn">werkzeug.exceptions</span> <span class="k">import</span> <span class="n">NotFound</span>
387 <span class="k">from</span> <span class="nn">shorty.utils</span> <span class="k">import</span> <span class="n">session</span><span class="p">,</span> <span class="n">render_template</span><span class="p">,</span> <span class="n">expose</span><span class="p">,</span> <span class="n">validate_url</span><span class="p">,</span> \
388 <span class="n">url_for</span>
389 <span class="k">from</span> <span class="nn">shorty.models</span> <span class="k">import</span> <span class="n">URL</span>
390
391 <span class="nd">@expose</span><span class="p">(</span><span class="s">&#39;/&#39;</span><span class="p">)</span>
392 <span class="k">def</span> <span class="nf">new</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
393 <span class="n">error</span> <span class="o">=</span> <span class="n">url</span> <span class="o">=</span> <span class="s">&#39;&#39;</span>
394 <span class="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n">method</span> <span class="o">==</span> <span class="s">&#39;POST&#39;</span><span class="p">:</span>
395 <span class="n">url</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&#39;url&#39;</span><span class="p">)</span>
396 <span class="n">alias</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&#39;alias&#39;</span><span class="p">)</span>
397 <span class="k">if</span> <span class="ow">not</span> <span class="n">validate_url</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
398 <span class="n">error</span> <span class="o">=</span> <span class="s">&quot;I&#39;m sorry but you cannot shorten this URL.&quot;</span>
399 <span class="k">elif</span> <span class="n">alias</span><span class="p">:</span>
400 <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">alias</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mf">140</span><span class="p">:</span>
401 <span class="n">error</span> <span class="o">=</span> <span class="s">&#39;Your alias is too long&#39;</span>
402 <span class="k">elif</span> <span class="s">&#39;/&#39;</span> <span class="ow">in</span> <span class="n">alias</span><span class="p">:</span>
403 <span class="n">error</span> <span class="o">=</span> <span class="s">&#39;Your alias might not include a slash&#39;</span>
404 <span class="k">elif</span> <span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">alias</span><span class="p">):</span>
405 <span class="n">error</span> <span class="o">=</span> <span class="s">&#39;The alias you have requested exists already&#39;</span>
406 <span class="k">if</span> <span class="ow">not</span> <span class="n">error</span><span class="p">:</span>
407 <span class="n">uid</span> <span class="o">=</span> <span class="n">URL</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="s">&#39;private&#39;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="p">,</span> <span class="n">alias</span><span class="p">)</span><span class="o">.</span><span class="n">uid</span>
408 <span class="n">session</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
409 <span class="k">return</span> <span class="n">redirect</span><span class="p">(</span><span class="n">url_for</span><span class="p">(</span><span class="s">&#39;display&#39;</span><span class="p">,</span> <span class="n">uid</span><span class="o">=</span><span class="n">uid</span><span class="p">))</span>
410 <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">&#39;new.html&#39;</span><span class="p">,</span> <span class="n">error</span><span class="o">=</span><span class="n">error</span><span class="p">,</span> <span class="n">url</span><span class="o">=</span><span class="n">url</span><span class="p">)</span>
411
412 <span class="nd">@expose</span><span class="p">(</span><span class="s">&#39;/display/&lt;uid&gt;&#39;</span><span class="p">)</span>
413 <span class="k">def</span> <span class="nf">display</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">uid</span><span class="p">):</span>
414 <span class="n">url</span> <span class="o">=</span> <span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">uid</span><span class="p">)</span>
415 <span class="k">if</span> <span class="ow">not</span> <span class="n">url</span><span class="p">:</span>
416 <span class="k">raise</span> <span class="n">NotFound</span><span class="p">()</span>
417 <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">&#39;display.html&#39;</span><span class="p">,</span> <span class="n">url</span><span class="o">=</span><span class="n">url</span><span class="p">)</span>
418
419 <span class="nd">@expose</span><span class="p">(</span><span class="s">&#39;/u/&lt;uid&gt;&#39;</span><span class="p">)</span>
420 <span class="k">def</span> <span class="nf">link</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">uid</span><span class="p">):</span>
421 <span class="n">url</span> <span class="o">=</span> <span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">uid</span><span class="p">)</span>
422 <span class="k">if</span> <span class="ow">not</span> <span class="n">url</span><span class="p">:</span>
423 <span class="k">raise</span> <span class="n">NotFound</span><span class="p">()</span>
424 <span class="k">return</span> <span class="n">redirect</span><span class="p">(</span><span class="n">url</span><span class="o">.</span><span class="n">target</span><span class="p">,</span> <span class="mf">301</span><span class="p">)</span>
425
426 <span class="nd">@expose</span><span class="p">(</span><span class="s">&#39;/list/&#39;</span><span class="p">,</span> <span class="n">defaults</span><span class="o">=</span><span class="p">{</span><span class="s">&#39;page&#39;</span><span class="p">:</span> <span class="mf">1</span><span class="p">})</span>
427 <span class="nd">@expose</span><span class="p">(</span><span class="s">&#39;/list/&lt;int:page&gt;&#39;</span><span class="p">)</span>
428 <span class="k">def</span> <span class="nf">list</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">page</span><span class="p">):</span>
429 <span class="k">pass</span>
430 </pre></div>
431 <p>Quite a lot of code again, but most of it is just plain old form validation.
432 Basically we specify two functions: <cite>new</cite> and <cite>display</cite> and
433 decorate them with our <cite>expose</cite> decorator from the utils. This decorator
434 adds a new URL rule to the map by passing all parameters to the constructor
435 of a rule object and setting the endpoint to the name of the function. So we
436 can easily build URLs to those functions by using their name as endpoint.</p>
437 <p>Keep in mind that this is not necessarily a good idea for bigger applications.
438 In such cases it&#8217;s encouraged to use the full import name with a common prefix
439 as endpoint or something similar. Otherwise it becomes pretty confusing.</p>
440 <p>The form validation in the <cite>new</cite> method is pretty straightforward. We check
441 if the current method is <cite>POST</cite>, if yes we get the data from the request and
442 validate it. If there is no error we create a new <cite>URL</cite> object, commit it to
443 the database and redirect to the display page.</p>
444 <p>The <cite>display</cite> function is not much more complex. The URL rule expects a
445 parameter called <cite>uid</cite>, which the function accepts. Then we look up the URL
446 rule with the given uid and render a template by passing the URL object to it.</p>
447 <p>If the URL does not exist we raise a <cite>NotFound</cite> exception which displays a
448 generic &#8220;404 Page Not Found&#8221; page. We can later replace it by a custom error
449 page by catching that exception before the generic <cite>HTTPException</cite> in
450 our WSGI application.</p>
451 <p>The <cite>link</cite> view function is used by our models in the <cite>short_url</cite> property
452 and is the short URL we provide. So if the URL uid is <tt class="docutils literal"><span class="pre">foobar</span></tt> the URL
453 will be available as <tt class="docutils literal"><span class="pre">http://localhost:5000/u/foobar</span></tt>.</p>
454 <p>The <cite>list</cite> view function has not yet been written, we will do that later. But
455 what&#8217;s important is that this function takes a URL parameter which is
456 optional. The first decorator tells Werkzeug that if just <tt class="docutils literal"><span class="pre">/page/</span></tt> is
457 requested it will assume that the page equals 1. Even more important is the
458 fact that Werkzeug also normalizes the URLs. So if you requested <tt class="docutils literal"><span class="pre">/page</span></tt> or
459 <tt class="docutils literal"><span class="pre">/page/1</span></tt>, you will be redirected to <tt class="docutils literal"><span class="pre">/page/</span></tt> in both cases.
460 This makes Google happy and comes for free. If you don&#8217;t like that behavior,
461 you can also disable it.</p>
462 <p>And again we have imported two objects from the utils module that
463 don&#8217;t exist yet. One of those should render a jinja template into a response
464 object, the other one validates a URL. So let&#8217;s add those to <tt class="docutils literal"><span class="pre">utils.py</span></tt>:</p>
465 <div class="syntax"><pre><span class="k">from</span> <span class="nn">os</span> <span class="k">import</span> <span class="n">path</span>
466 <span class="k">from</span> <span class="nn">urlparse</span> <span class="k">import</span> <span class="n">urlparse</span>
467 <span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">Response</span>
468 <span class="k">from</span> <span class="nn">jinja</span> <span class="k">import</span> <span class="n">Environment</span><span class="p">,</span> <span class="n">FileSystemLoader</span>
469
470 <span class="n">ALLOWED_SCHEMES</span> <span class="o">=</span> <span class="n">frozenset</span><span class="p">([</span><span class="s">&#39;http&#39;</span><span class="p">,</span> <span class="s">&#39;https&#39;</span><span class="p">,</span> <span class="s">&#39;ftp&#39;</span><span class="p">,</span> <span class="s">&#39;ftps&#39;</span><span class="p">])</span>
471 <span class="n">TEMPLATE_PATH</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">__file__</span><span class="p">),</span> <span class="s">&#39;templates&#39;</span><span class="p">)</span>
472 <span class="n">jinja_env</span> <span class="o">=</span> <span class="n">Environment</span><span class="p">(</span><span class="n">loader</span><span class="o">=</span><span class="n">FileSystemLoader</span><span class="p">(</span><span class="n">TEMPLATE_PATH</span><span class="p">))</span>
473 <span class="n">jinja_env</span><span class="o">.</span><span class="n">globals</span><span class="p">[</span><span class="s">&#39;url_for&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">url_for</span>
474
475 <span class="k">def</span> <span class="nf">render_template</span><span class="p">(</span><span class="n">template</span><span class="p">,</span> <span class="o">**</span><span class="n">context</span><span class="p">):</span>
476 <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">jinja_env</span><span class="o">.</span><span class="n">get_template</span><span class="p">(</span><span class="n">template</span><span class="p">)</span><span class="o">.</span><span class="n">render</span><span class="p">(</span><span class="o">**</span><span class="n">context</span><span class="p">),</span>
477 <span class="n">mimetype</span><span class="o">=</span><span class="s">&#39;text/html&#39;</span><span class="p">)</span>
478
479 <span class="k">def</span> <span class="nf">validate_url</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
480 <span class="k">return</span> <span class="n">urlparse</span><span class="p">(</span><span class="n">url</span><span class="p">)[</span><span class="mf">0</span><span class="p">]</span> <span class="ow">in</span> <span class="n">ALLOWED_SCHEMES</span>
481 </pre></div>
482 <p>That&#8217;s it, basically. The validation function checks if our URL looks like an
483 HTTP or FTP URL. We do this whitelisting to ensure nobody submits any
484 dangerous JavaScript or similar URLs. The <cite>render_template</cite> function is not
485 much more complicated either, it basically looks up a template on the file
486 system in the <cite>templates</cite> folder and renders it as response.</p>
487 <p>Another thing we do here is passing the <cite>url_for</cite> function into the global
488 template context so that we can build URLs in the templates too.</p>
489 <p>Now that we have our first two view functions it&#8217;s time to add the templates.</p>
490 </div>
491 <div class="section">
492 <h3 id="part-5-the-templates">Part 5: The Templates</h3>
493 <p>We have decided to use Jinja templates in this example. If you are used to
494 Django templates you should feel at home, if you have worked with PHP so far
495 you can compare the Jinja templates with smarty. If you have used PHP as
496 templating language until now you should have a look at <a class="reference" href="http://www.makotemplates.org/">Mako</a> for your next
497 project.</p>
498 <p><strong>Security Warning</strong>: We are using Jinja here which is a text based template
499 engine. As a matter of fact, Jinja has no idea what it is dealing with, so
500 if you want to create HTML template it&#8217;s your responsibility to escape <em>all</em>
501 values that might include, at some point, any of the following characters: <tt class="docutils literal"><span class="pre">&gt;</span></tt>,
502 <tt class="docutils literal"><span class="pre">&lt;</span></tt> or <tt class="docutils literal"><span class="pre">&amp;</span></tt>. Inside attributes you also have to escape double quotes.
503 You can use the jinja <tt class="docutils literal"><span class="pre">|e</span></tt> filter for basic escapes, if you pass it <cite>true</cite>
504 as argument it will also escape quotes (<tt class="docutils literal"><span class="pre">|e(true)</span></tt>). As you can see from
505 the examples below we don&#8217;t escape URLs. The reason is that we won&#8217;t have
506 any ampersands in the URL and as such it&#8217;s safe to omit it.</p>
507 <p>For simplicity we will use HTML 4 in our templates. If you have already
508 some experience with XHTML you can adopt the templates to XHTML. But keep
509 in mind that the example stylesheet from below does not work with XHTML.</p>
510 <p>One of the cool things Jinja inherited from Django is template inheritance.
511 Template inheritance means that we can put often used elements into a base
512 template and fill in placeholders. For example all the doctype and HTML base
513 frame goes into a file called <tt class="docutils literal"><span class="pre">templates/layout.html</span></tt>:</p>
514 <div class="syntax"><pre><span class="cp">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01//EN&quot;</span>
515 <span class="cp"> &quot;http://www.w3.org/TR/html4/strict.dtd&quot;&gt;</span>
516 <span class="nt">&lt;html&gt;</span>
517 <span class="nt">&lt;head&gt;</span>
518 <span class="nt">&lt;title&gt;</span>Shorty<span class="nt">&lt;/title&gt;</span>
519 <span class="nt">&lt;/head&gt;</span>
520 <span class="nt">&lt;body&gt;</span>
521 <span class="nt">&lt;h1&gt;&lt;a</span> <span class="na">href=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">&#39;new&#39;</span><span class="o">)</span> <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;</span>Shorty<span class="nt">&lt;/a&gt;&lt;/h1&gt;</span>
522 <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">&quot;body&quot;</span><span class="nt">&gt;</span><span class="cp">{%</span> <span class="k">block</span> <span class="nv">body</span> <span class="cp">%}{%</span> <span class="k">endblock</span> <span class="cp">%}</span><span class="nt">&lt;/div&gt;</span>
523 <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">&quot;footer&quot;</span><span class="nt">&gt;</span>
524 <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">&#39;new&#39;</span><span class="o">)</span> <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;</span>new<span class="nt">&lt;/a&gt;</span> |
525 <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">&#39;list&#39;</span><span class="o">)</span> <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;</span>list<span class="nt">&lt;/a&gt;</span> |
526 use shorty for good, not for evil
527 <span class="nt">&lt;/div&gt;</span>
528 <span class="nt">&lt;/body&gt;</span>
529 <span class="nt">&lt;/html&gt;</span>
530 </pre></div>
531 <p>And we can inherit from this base template in our <tt class="docutils literal"><span class="pre">templates/new.html</span></tt>:</p>
532 <div class="syntax"><pre><span class="cp">{%</span> <span class="k">extends</span> <span class="s1">&#39;layout.html&#39;</span> <span class="cp">%}</span>
533 <span class="cp">{%</span> <span class="k">block</span> <span class="nv">body</span> <span class="cp">%}</span>
534 <span class="nt">&lt;h2&gt;</span>Create a Shorty-URL!<span class="nt">&lt;/h2&gt;</span>
535 <span class="cp">{%</span> <span class="k">if</span> <span class="nv">error</span> <span class="cp">%}</span><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">&quot;error&quot;</span><span class="nt">&gt;</span><span class="cp">{{</span> <span class="nv">error</span> <span class="cp">}}</span><span class="nt">&lt;/div&gt;</span><span class="cp">{%</span> <span class="k">endif</span> -<span class="cp">%}</span>
536 <span class="nt">&lt;form</span> <span class="na">action=</span><span class="s">&quot;&quot;</span> <span class="na">method=</span><span class="s">&quot;post&quot;</span><span class="nt">&gt;</span>
537 <span class="nt">&lt;p&gt;</span>Enter the URL you want to shorten<span class="nt">&lt;/p&gt;</span>
538 <span class="nt">&lt;p&gt;&lt;input</span> <span class="na">type=</span><span class="s">&quot;text&quot;</span> <span class="na">name=</span><span class="s">&quot;url&quot;</span> <span class="na">id=</span><span class="s">&quot;url&quot;</span> <span class="na">value=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">url</span><span class="o">|</span><span class="nf">e</span><span class="o">(</span><span class="kp">true</span><span class="o">)</span> <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;&lt;/p&gt;</span>
539 <span class="nt">&lt;p&gt;</span>Optionally you can give the URL a memorable name<span class="nt">&lt;/p&gt;</span>
540 <span class="nt">&lt;p&gt;&lt;input</span> <span class="na">type=</span><span class="s">&quot;text&quot;</span> <span class="na">id=</span><span class="s">&quot;alias&quot;</span> <span class="na">name=</span><span class="s">&quot;alias&quot;</span><span class="nt">&gt;</span><span class="c">{#</span>
541 <span class="c"> #}</span><span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">&quot;submit&quot;</span> <span class="na">id=</span><span class="s">&quot;submit&quot;</span> <span class="na">value=</span><span class="s">&quot;Do!&quot;</span><span class="nt">&gt;&lt;/p&gt;</span>
542 <span class="nt">&lt;p&gt;&lt;input</span> <span class="na">type=</span><span class="s">&quot;checkbox&quot;</span> <span class="na">name=</span><span class="s">&quot;private&quot;</span> <span class="na">id=</span><span class="s">&quot;private&quot;</span><span class="nt">&gt;</span>
543 <span class="nt">&lt;label</span> <span class="na">for=</span><span class="s">&quot;private&quot;</span><span class="nt">&gt;</span>make this URL private, so don&#39;t list it<span class="nt">&lt;/label&gt;&lt;/p&gt;</span>
544 <span class="nt">&lt;/form&gt;</span>
545 <span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
546 </pre></div>
547 <p>If you&#8217;re wondering about the comment between the two input elements, this is
548 a neat trick to keep the templates clean but not create whitespace between
549 those two. We&#8217;ve prepared a stylesheet you can use which depends on not having
550 a whitespace there.</p>
551 <p>And then a second template for the display page (<tt class="docutils literal"><span class="pre">templates/display.html</span></tt>):</p>
552 <div class="syntax"><pre><span class="cp">{%</span> <span class="k">extends</span> <span class="s1">&#39;layout.html&#39;</span> <span class="cp">%}</span>
553 <span class="cp">{%</span> <span class="k">block</span> <span class="nv">body</span> <span class="cp">%}</span>
554 <span class="nt">&lt;h2&gt;</span>Shortened URL<span class="nt">&lt;/h2&gt;</span>
555 <span class="nt">&lt;p&gt;</span>
556 The URL <span class="cp">{{</span> <span class="nv">url.target</span><span class="o">|</span><span class="nf">urlize</span><span class="o">(</span><span class="m">40</span><span class="o">,</span> <span class="kp">true</span><span class="o">)</span> <span class="cp">}}</span>
557 was shortened to <span class="cp">{{</span> <span class="nv">url.short_url</span><span class="o">|</span><span class="nf">urlize</span> <span class="cp">}}</span>.
558 <span class="nt">&lt;/p&gt;</span>
559 <span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
560 </pre></div>
561 <p>The <cite>urlize</cite> filter is provided by Jinja and translates a URL(s) in a
562 text into clickable links. If you pass it an integer it will shorten the
563 captions of those links to that number of characters, passing it true as
564 second parameter adds a <cite>nofollow</cite> flag.</p>
565 <p>Now that we have our first two templates it&#8217;s time to fire up the server and
566 look at those part of the application that work already: adding new URLs and
567 getting redirected.</p>
568 </div>
569 <div class="section">
570 <h3 id="intermission-adding-the-design">Intermission: Adding The Design</h3>
571 <p>Now it&#8217;s time to do something different: adding a design. Design elements are
572 usually in static CSS stylesheets so we have to put some static files
573 somewhere. But that&#8217;s a little big tricky. If you have worked with PHP so
574 far you have probably noticed that there is no such thing as translating the
575 URL to filesystem paths and accessing static files right from the URL. You
576 have to explicitly tell the webserver or our development server that some
577 path holds static files.</p>
578 <p>Django even recommends a separate subdomain and standalone server for the
579 static files which is a terribly good idea for heavily loaded environments but
580 somewhat of an overkill for this simple application.</p>
581 <p>So here is the deal: We let our application host the static files, but in
582 production mode you should probably tell the apache to serve those files by
583 using an <cite>Alias</cite> directive in the apache config:</p>
584 <div class="syntax"><pre><span class="nb">Alias</span> <span class="sx">/static</span> <span class="sx">/path/to/static/files</span>
585 </pre></div>
586 <p>This will be a lot faster.</p>
587 <p>But how do we tell our application that we want it to share the static folder
588 from our application package as <tt class="docutils literal"><span class="pre">/static</span></tt>?. Fortunately that&#8217;s pretty
589 simple because Werkzeug provides a WSGI middleware for that. Now there are
590 two ways to hook that middleware in. One way is to wrap the whole application
591 in that middleware (we really don&#8217;t recommend this one) and the other is to
592 just wrap the dispatching function (much better because we don&#8217;t lose the
593 reference to the application object). So head back to <tt class="docutils literal"><span class="pre">application.py</span></tt>
594 and do some code refactoring there.</p>
595 <p>First of all you have to add a new import and calculate the path to the
596 static files:</p>
597 <div class="syntax"><pre><span class="k">from</span> <span class="nn">os</span> <span class="k">import</span> <span class="n">path</span>
598 <span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">SharedDataMiddleware</span>
599
600 <span class="n">STATIC_PATH</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">__file__</span><span class="p">),</span> <span class="s">&#39;static&#39;</span><span class="p">)</span>
601 </pre></div>
602 <p>It may be better to put the path calculation into the <tt class="docutils literal"><span class="pre">utils.py</span></tt> file
603 because we already calculate the path to the templates there. But it doesn&#8217;t
604 really matter and for simplicity we can leave it in the application module.</p>
605 <p>So how do we wrap our dispatching function? In theory we just have to say
606 <tt class="docutils literal"><span class="pre">self.__call__</span> <span class="pre">=</span> <span class="pre">wrap(self.__call__)</span></tt> but unfortunately that doesn&#8217;t work in
607 python. But it&#8217;s not much harder. Just rename <cite>__call__</cite> to <cite>dispatch</cite> and
608 add a new <cite>__call__</cite> method:</p>
609 <div class="syntax"><pre><span class="k">def</span> <span class="nf">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
610 <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">dispatch</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">)</span>
611 </pre></div>
612 <p>Now we can go into our <cite>__init__</cite> function and hook in the middleware by
613 wrapping the <cite>dispatch</cite> method:</p>
614 <div class="syntax"><pre><span class="bp">self</span><span class="o">.</span><span class="n">dispatch</span> <span class="o">=</span> <span class="n">SharedDataMiddleware</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">dispatch</span><span class="p">,</span> <span class="p">{</span>
615 <span class="s">&#39;/static&#39;</span><span class="p">:</span> <span class="n">STATIC_PATH</span>
616 <span class="p">})</span>
617 </pre></div>
618 <p>Now that wasn&#8217;t that hard. This way you can now hook in WSGI middlewares
619 inside the application class!</p>
620 <p>Another good idea now is to tell our <cite>url_map</cite> in the utils module the
621 location of our static files by adding a rule. This way we can generate URLs
622 to the static files in the templates:</p>
623 <div class="syntax"><pre><span class="n">url_map</span> <span class="o">=</span> <span class="n">Map</span><span class="p">([</span><span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/static/&lt;file&gt;&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;static&#39;</span><span class="p">,</span> <span class="n">build_only</span><span class="o">=</span><span class="bp">True</span><span class="p">)])</span>
624 </pre></div>
625 <p>Now we can open our <tt class="docutils literal"><span class="pre">templates/layout.html</span></tt> file again and add a link to the
626 stylesheet <tt class="docutils literal"><span class="pre">style.css</span></tt>, which we are going to create afterwards:</p>
627 <div class="syntax"><pre><span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">&quot;stylesheet&quot;</span> <span class="na">type=</span><span class="s">&quot;text/css&quot;</span> <span class="na">href=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">&#39;static&#39;</span><span class="o">,</span> <span class="nv">file</span><span class="o">=</span><span class="s1">&#39;style.css&#39;</span><span class="o">)</span> <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;</span>
628 </pre></div>
629 <p>This of course goes into the <cite>&lt;head&gt;</cite> tag where currently just the title is.</p>
630 <p>You can now design a nice layout for it or use the <a class="reference" href="http://dev.pocoo.org/projects/werkzeug/browser/examples/shorty/static/style.css">example stylesheet</a> if
631 you want. In both cases the file you have to create is called
632 <tt class="docutils literal"><span class="pre">static/style.css</span></tt></p>
633 </div>
634 <div class="section">
635 <h3 id="part-6-listing-public-urls">Part 6: Listing Public URLs</h3>
636 <p>Now we want to list all of the public URLs on the list page. That shouldn&#8217;t
637 be a big problem but we will have to do some sort of pagination. Because if
638 we print all URLs at once we have sooner or later an endless page that takes
639 minutes to load.</p>
640 <p>So let&#8217;s start by adding a <cite>Pagination</cite> class into our utils module:</p>
641 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">cached_property</span>
642
643 <span class="k">class</span> <span class="nc">Pagination</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
644
645 <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">query</span><span class="p">,</span> <span class="n">per_page</span><span class="p">,</span> <span class="n">page</span><span class="p">,</span> <span class="n">endpoint</span><span class="p">):</span>
646 <span class="bp">self</span><span class="o">.</span><span class="n">query</span> <span class="o">=</span> <span class="n">query</span>
647 <span class="bp">self</span><span class="o">.</span><span class="n">per_page</span> <span class="o">=</span> <span class="n">per_page</span>
648 <span class="bp">self</span><span class="o">.</span><span class="n">page</span> <span class="o">=</span> <span class="n">page</span>
649 <span class="bp">self</span><span class="o">.</span><span class="n">endpoint</span> <span class="o">=</span> <span class="n">endpoint</span>
650
651 <span class="nd">@cached_property</span>
652 <span class="k">def</span> <span class="nf">count</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
653 <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">count</span><span class="p">()</span>
654
655 <span class="nd">@cached_property</span>
656 <span class="k">def</span> <span class="nf">entries</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
657 <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">offset</span><span class="p">((</span><span class="bp">self</span><span class="o">.</span><span class="n">page</span> <span class="o">-</span> <span class="mf">1</span><span class="p">)</span> <span class="o">*</span> <span class="bp">self</span><span class="o">.</span><span class="n">per_page</span><span class="p">)</span> \
658 <span class="o">.</span><span class="n">limit</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">per_page</span><span class="p">)</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
659
660 <span class="n">has_previous</span> <span class="o">=</span> <span class="nb">property</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="o">.</span><span class="n">page</span> <span class="o">&gt;</span> <span class="mf">1</span><span class="p">)</span>
661 <span class="n">has_next</span> <span class="o">=</span> <span class="nb">property</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="o">.</span><span class="n">page</span> <span class="o">&lt;</span> <span class="n">x</span><span class="o">.</span><span class="n">pages</span><span class="p">)</span>
662 <span class="n">previous</span> <span class="o">=</span> <span class="nb">property</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">url_for</span><span class="p">(</span><span class="n">x</span><span class="o">.</span><span class="n">endpoint</span><span class="p">,</span> <span class="n">page</span><span class="o">=</span><span class="n">x</span><span class="o">.</span><span class="n">page</span> <span class="o">-</span> <span class="mf">1</span><span class="p">))</span>
663 <span class="n">next</span> <span class="o">=</span> <span class="nb">property</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">url_for</span><span class="p">(</span><span class="n">x</span><span class="o">.</span><span class="n">endpoint</span><span class="p">,</span> <span class="n">page</span><span class="o">=</span><span class="n">x</span><span class="o">.</span><span class="n">page</span> <span class="o">+</span> <span class="mf">1</span><span class="p">))</span>
664 <span class="n">pages</span> <span class="o">=</span> <span class="nb">property</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="nb">max</span><span class="p">(</span><span class="mf">0</span><span class="p">,</span> <span class="n">x</span><span class="o">.</span><span class="n">count</span> <span class="o">-</span> <span class="mf">1</span><span class="p">)</span> <span class="o">//</span> <span class="n">x</span><span class="o">.</span><span class="n">per_page</span> <span class="o">+</span> <span class="mf">1</span><span class="p">)</span>
665 </pre></div>
666 <p>This is a very simple class that does most of the pagination for us. We
667 can pass at an unexecuted SQLAlchemy query, the number of items per page,
668 the current page and the endpoint, which will be used for URL generation. The
669 <cite>cached_property</cite> decorator you see works pretty much like the normal
670 <cite>property</cite> decorator, just that it memorizes the result. We won&#8217;t cover
671 that class in detail but basically the idea is that accessing
672 <cite>pagination.entries</cite> returns the items for the current page and that the
673 other properties return meaningful values so that we can use them in the
674 template.</p>
675 <p>Now we can import the <cite>Pagination</cite> class into our views module and add some
676 code to the <cite>list</cite> function:</p>
677 <div class="syntax"><pre><span class="k">from</span> <span class="nn">shorty.utils</span> <span class="k">import</span> <span class="n">Pagination</span>
678
679 <span class="nd">@expose</span><span class="p">(</span><span class="s">&#39;/list/&#39;</span><span class="p">,</span> <span class="n">defaults</span><span class="o">=</span><span class="p">{</span><span class="s">&#39;page&#39;</span><span class="p">:</span> <span class="mf">1</span><span class="p">})</span>
680 <span class="nd">@expose</span><span class="p">(</span><span class="s">&#39;/list/&lt;int:page&gt;&#39;</span><span class="p">)</span>
681 <span class="k">def</span> <span class="nf">list</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">page</span><span class="p">):</span>
682 <span class="n">query</span> <span class="o">=</span> <span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">filter_by</span><span class="p">(</span><span class="n">public</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
683 <span class="n">pagination</span> <span class="o">=</span> <span class="n">Pagination</span><span class="p">(</span><span class="n">query</span><span class="p">,</span> <span class="mf">30</span><span class="p">,</span> <span class="n">page</span><span class="p">,</span> <span class="s">&#39;list&#39;</span><span class="p">)</span>
684 <span class="k">if</span> <span class="n">pagination</span><span class="o">.</span><span class="n">page</span> <span class="o">&gt;</span> <span class="mf">1</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">pagination</span><span class="o">.</span><span class="n">entries</span><span class="p">:</span>
685 <span class="k">raise</span> <span class="n">NotFound</span><span class="p">()</span>
686 <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">&#39;list.html&#39;</span><span class="p">,</span> <span class="n">pagination</span><span class="o">=</span><span class="n">pagination</span><span class="p">)</span>
687 </pre></div>
688 <p>The if condition in this function basically ensures that status code 404 is
689 returned if we are not on the first page and there aren&#8217;t any entries to display
690 (Accessing something like <tt class="docutils literal"><span class="pre">/list/42</span></tt> without entries on that page and not
691 returning a 404 status code would be considered bad style.)</p>
692 <p>And finally the template:</p>
693 <div class="syntax"><pre><span class="cp">{%</span> <span class="k">extends</span> <span class="s1">&#39;layout.html&#39;</span> <span class="cp">%}</span>
694 <span class="cp">{%</span> <span class="k">block</span> <span class="nv">body</span> <span class="cp">%}</span>
695 <span class="nt">&lt;h2&gt;</span>List of URLs<span class="nt">&lt;/h2&gt;</span>
696 <span class="nt">&lt;ul&gt;</span>
697 <span class="cp">{%</span>- <span class="k">for</span> <span class="nv">url</span> <span class="k">in</span> <span class="nv">pagination.entries</span> <span class="cp">%}</span>
698 <span class="nt">&lt;li&gt;&lt;a</span> <span class="na">href=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">url.short_url</span><span class="o">|</span><span class="nf">e</span> <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;</span><span class="cp">{{</span> <span class="nv">url.uid</span><span class="o">|</span><span class="nf">e</span> <span class="cp">}}</span><span class="nt">&lt;/a&gt;</span> <span class="ni">&amp;raquo;</span>
699 <span class="nt">&lt;small&gt;</span><span class="cp">{{</span> <span class="nv">url.target</span><span class="o">|</span><span class="nf">urlize</span><span class="o">(</span><span class="m">38</span><span class="o">,</span> <span class="kp">true</span><span class="o">)</span> <span class="cp">}}</span><span class="nt">&lt;/small&gt;&lt;/li&gt;</span>
700 <span class="cp">{%</span>- <span class="k">else</span> <span class="cp">%}</span>
701 <span class="nt">&lt;li&gt;&lt;em&gt;</span>no URls shortened yet<span class="nt">&lt;/em&gt;&lt;/li&gt;</span>
702 <span class="cp">{%</span>- <span class="k">endfor</span> <span class="cp">%}</span>
703 <span class="nt">&lt;/ul&gt;</span>
704 <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">&quot;pagination&quot;</span><span class="nt">&gt;</span>
705 <span class="cp">{%</span>- <span class="k">if</span> <span class="nv">pagination.has_previous</span> <span class="cp">%}</span><span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">pagination.previous</span>
706 <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;</span><span class="ni">&amp;laquo;</span> Previous<span class="nt">&lt;/a&gt;</span>
707 <span class="cp">{%</span>- <span class="k">else</span> <span class="cp">%}</span><span class="nt">&lt;span</span> <span class="na">class=</span><span class="s">&quot;inactive&quot;</span><span class="nt">&gt;</span><span class="ni">&amp;laquo;</span> Previous<span class="nt">&lt;/span&gt;</span><span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span>
708 | <span class="cp">{{</span> <span class="nv">pagination.page</span> <span class="cp">}}</span> |
709 <span class="cp">{%</span> <span class="k">if</span> <span class="nv">pagination.has_next</span> <span class="cp">%}</span><span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">pagination.next</span> <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;</span>Next <span class="ni">&amp;raquo;</span><span class="nt">&lt;/a&gt;</span>
710 <span class="cp">{%</span>- <span class="k">else</span> <span class="cp">%}</span><span class="nt">&lt;span</span> <span class="na">class=</span><span class="s">&quot;inactive&quot;</span><span class="nt">&gt;</span>Next <span class="ni">&amp;raquo;</span><span class="nt">&lt;/span&gt;</span><span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span>
711 <span class="nt">&lt;/div&gt;</span>
712 <span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
713 </pre></div>
714 </div>
715 <div class="section">
716 <h3 id="bonus-styling-404-error-pages">Bonus: Styling 404 Error Pages</h3>
717 <p>Now that we&#8217;ve finished our application we can do some small improvements such
718 as custom 404 error pages. That&#8217;s pretty simple. The first thing we have to
719 do is creating a new function called <cite>not_found</cite> in the view that renders a
720 template:</p>
721 <div class="syntax"><pre><span class="k">def</span> <span class="nf">not_found</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
722 <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">&#39;not_found.html&#39;</span><span class="p">)</span>
723 </pre></div>
724 <p>Then we have to go into our application module and import the <cite>NotFound</cite>
725 exception:</p>
726 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug.exceptions</span> <span class="k">import</span> <span class="n">NotFound</span>
727 </pre></div>
728 <p>Finally we have to catch it and translate it into a response. This except
729 block goes right <strong>before</strong> the except block of the <cite>HTTPException</cite>:</p>
730 <div class="syntax"><pre><span class="k">try</span><span class="p">:</span>
731 <span class="c"># this stays the same</span>
732 <span class="k">except</span> <span class="n">NotFound</span><span class="p">,</span> <span class="n">e</span><span class="p">:</span>
733 <span class="n">response</span> <span class="o">=</span> <span class="n">views</span><span class="o">.</span><span class="n">not_found</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
734 <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mf">404</span>
735 <span class="k">except</span> <span class="n">HTTPException</span><span class="p">,</span> <span class="n">e</span><span class="p">:</span>
736 <span class="c"># this stays the same</span>
737 </pre></div>
738 <p>Now add a template <tt class="docutils literal"><span class="pre">templates/not_found.html</span></tt> and you&#8217;re done:</p>
739 <div class="syntax"><pre><span class="cp">{%</span> <span class="k">extends</span> <span class="s1">&#39;layout.html&#39;</span> <span class="cp">%}</span>
740 <span class="cp">{%</span> <span class="k">block</span> <span class="nv">body</span> <span class="cp">%}</span>
741 <span class="nt">&lt;h2&gt;</span>Page Not Found<span class="nt">&lt;/h2&gt;</span>
742 <span class="nt">&lt;p&gt;</span>
743 The page you have requested does not exist on this server. What about
744 <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">&#39;new&#39;</span><span class="o">)</span> <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;</span>adding a new URL<span class="nt">&lt;/a&gt;</span>?
745 <span class="nt">&lt;/p&gt;</span>
746 <span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
747 </pre></div>
748 </div>
749 <div class="section">
750 <h3 id="outro">Outro</h3>
751 <p>This tutorial covers everything you need to get started with Werkzeug,
752 SQLAlchemy and Jinja and should help you find the best solution for your
753 application. For some more complex examples that also use different setups
754 and ideas for dispatching have a look at the <a class="reference" href="http://dev.pocoo.org/projects/werkzeug/browser/examples">examples folder</a>.</p>
755 <p>Have fun with Werkzeug!</p>
756 </div>
757
758 <div style="clear:both"></div>
759 </div>
760 <div class="footer">
761 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
762 BSD license.
763 </div>
764 </div>
765 </body>
766 </html>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Werkzeug Tutorial // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Werkzeug Tutorial</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#teil-0-die-ordnerstruktur" id="id1" name="id1">Teil 0: Die Ordnerstruktur</a></li>
22 <li><a class="reference" href="#teil-1-die-wsgi-anwendung" id="id2" name="id2">Teil 1: Die WSGI-Anwendung</a></li>
23 <li><a class="reference" href="#teil-2-die-utilities" id="id3" name="id3">Teil 2: Die Utilities</a></li>
24 <li><a class="reference" href="#unterbrechung-und-nun-etwas-komplett-anderes" id="id4" name="id4">Unterbrechung: Und nun etwas komplett anderes</a></li>
25 <li><a class="reference" href="#teil-3-datenbank-models" id="id5" name="id5">Teil 3: Datenbank-Models</a></li>
26 <li><a class="reference" href="#teil-4-die-view-funktionen" id="id6" name="id6">Teil 4: Die View-Funktionen</a></li>
27 <li><a class="reference" href="#teil-5-die-templates" id="id7" name="id7">Teil 5: Die Templates</a></li>
28 <li><a class="reference" href="#zwischenschritt-das-design-hinzuf-gen" id="id8" name="id8">Zwischenschritt: Das Design hinzufügen</a></li>
29 <li><a class="reference" href="#teil-6-ffentliche-urls-auflisten" id="id9" name="id9">Teil 6: Öffentliche URLs auflisten</a></li>
30 <li><a class="reference" href="#bonus-404-fehlerseiten-gestalten" id="id10" name="id10">Bonus: 404-Fehlerseiten gestalten</a></li>
31 <li><a class="reference" href="#abschluss" id="id11" name="id11">Abschluss</a></li>
32 </ul>
33 </div>
34
35 <div class="admonition-hinweis admonition">
36 <p class="first admonition-title">Hinweis</p>
37 <p class="last">Dies ist die deutsche Übersetzung des <a class="reference" href="tutorial.html">Tutorials</a>. Die
38 Entwicklung rund um Werkzeug steht nie still, und Verbesserungen an der
39 Library wirken sich oft auch auf das Tutorial aus &#8211; deshalb ist die
40 Originalversion möglicherweise aktueller.</p>
41 </div>
42 <p>Willkommen zum Tutorial für Werkzeug 0.2. Wir werden einen einfachen
43 <a class="reference" href="http://tinyurl.com/">TinyURL</a>-Klon programmieren, der die URLs in einer Datenbank speichert. Die
44 Die verwendeten Bibliotheken für diese Anwendung sind <a class="reference" href="http://jinja.pocoo.org/">Jinja</a> für die
45 Templates, <a class="reference" href="http://sqlalchemy.org/">SQLAlchemy</a> für die Datenbank-Anbindung und natürlich Werkzeug
46 für WSGI.</p>
47 <p>Wir haben uns hier für diese Komponenten entschieden, weil wir einen
48 <a class="reference" href="http://www.djangoproject.com/">Django</a>-ähnlichen Grundaufbau nachstellen wollen. Dazu zählen wir zum
49 Beispiel View-Funktionen anstelle der in <a class="reference" href="http://www.rubyonrails.org/">Rails</a> und <a class="reference" href="http://pylonshq.com/">Pylons</a> gängigen
50 Controller-Klassen mit Action-Methoden, sowie designerfreundliche Templates.</p>
51 <p>In Werkzeugs <a class="reference" href="http://dev.pocoo.org/projects/werkzeug/browser/examples">Beispiel-Ordner</a> befinden sich einige Anwendungen, die andere
52 Konzepte verfolgen, Template-Engines einsetzen etc. Dort liegt auch der
53 Quellcode der Anwendung, die wir in diesem Tutorial erstellen werden.</p>
54 <p>Du kannst <a class="reference" href="http://peak.telecommunity.com/DevCenter/EasyInstall">easy_install</a> verwenden, um Jinja und SQLAlchemy zu installieren,
55 falls diese nicht bereits installiert sind:</p>
56 <div class="syntax"><pre><span class="n">sudo</span> <span class="n">easy_install</span> <span class="n">Jinja</span>
57 <span class="n">sudo</span> <span class="n">easy_install</span> <span class="n">SQLAlchemy</span>
58 </pre></div>
59 <p>Diese Befehle funktionieren auch auf einem Windows-System (mit
60 Administratorrechten), sofern die <cite>setuptools</cite> installiert sind, allerdings
61 musst du das <cite>sudo</cite> weglassen. Als OS X-Benutzer könntest du die Libraries
62 auch via port installieren, Linux-Benutzer finden diese Pakete möglicherweise
63 auch in ihrem Paketmanager.</p>
64 <p>Wenn du neugierig bist, kannst du dir auch die <a class="reference" href="http://werkzeug.pocoo.org/e/shorty/">Online-Demo</a> der Anwendung
65 ansehen.</p>
66 <p>Noch ein kleiner Hinweis: Dieses Tutorial erfordert Python 2.4.</p>
67 <div class="section">
68 <h3 id="teil-0-die-ordnerstruktur">Teil 0: Die Ordnerstruktur</h3>
69 <p>Bevor wir beginnen können, müssen wir ein Python-Paket für unsere
70 Werkzeug-Anwendung erstellen. Dort werden wir die Anwendung, die Templates
71 und die statischen Dateien ablegen.</p>
72 <p>Die Anwendung dieses Tutorials nennen wir <cite>shorty</cite> und die Struktur für unsere
73 Anwendung sieht etwa so aus:</p>
74 <div class="syntax"><pre><span class="n">manage</span><span class="o">.</span><span class="n">py</span>
75 <span class="n">shorty</span><span class="o">/</span>
76 <span class="n">__init__</span><span class="o">.</span><span class="n">py</span>
77 <span class="n">static</span><span class="o">/</span>
78 <span class="n">templates</span><span class="o">/</span>
79 </pre></div>
80 <p>Die Dateien <tt class="docutils literal"><span class="pre">__init__.py</span></tt> und <tt class="docutils literal"><span class="pre">manage.py</span></tt> lassen wir für den Moment einmal
81 leer. Die erste dieser Dateien macht aus dem Ordner <tt class="docutils literal"><span class="pre">shorty</span></tt> ein
82 Python-Paket, die zweite werden wir später für unsere Verwaltungsfunktionen
83 nutzen.</p>
84 </div>
85 <div class="section">
86 <h3 id="teil-1-die-wsgi-anwendung">Teil 1: Die WSGI-Anwendung</h3>
87 <p>Im Gegensatz zu Django oder ähnlichen Frameworks arbeitet Werkzeug direkt auf
88 der WSGI-Schicht. Es gibt keine schicke Magie, die die zentrale WSGI-Anwendung
89 für uns implementiert. Das bedeutet, dass wir als Erstes eben diese
90 programmieren müssen. Eine WSGI-Anwendung ist eine Funktion oder, noch besser,
91 eine Klasse mit einer Methode <tt class="docutils literal"><span class="pre">__call__</span></tt>.</p>
92 <p>Eine aufrufbare Klasse hat große Vorteile gegenüber einer Funktion: Zum Einen
93 kann man Konfigurationsparameter direkt an den Konstruktor übergeben, zum
94 Anderen können wir WSGI-Middlewares innerhalb der WSGI-Anwendung hinzufügen.
95 Das ist nützlich für Middlewares, die entscheidend für die Funktion der
96 Anwendung sind (z.B. eine Session-Middleware).</p>
97 <p>Hier zunächst einmal der Quellcode für unsere Datei <tt class="docutils literal"><span class="pre">shorty/application.py</span></tt>,
98 in der wir die WSGI-Anwendung ablegen:</p>
99 <div class="syntax"><pre><span class="k">from</span> <span class="nn">sqlalchemy</span> <span class="k">import</span> <span class="n">create_engine</span>
100 <span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">Request</span><span class="p">,</span> <span class="n">ClosingIterator</span>
101 <span class="k">from</span> <span class="nn">werkzeug.exceptions</span> <span class="k">import</span> <span class="n">HTTPException</span>
102
103 <span class="k">from</span> <span class="nn">shorty.utils</span> <span class="k">import</span> <span class="n">session</span><span class="p">,</span> <span class="n">metadata</span><span class="p">,</span> <span class="n">local</span><span class="p">,</span> <span class="n">local_manager</span><span class="p">,</span> <span class="n">url_map</span>
104 <span class="k">from</span> <span class="nn">shorty</span> <span class="k">import</span> <span class="n">views</span>
105 <span class="k">import</span> <span class="nn">shorty.models</span>
106
107
108 <span class="k">class</span> <span class="nc">Shorty</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
109
110 <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">db_uri</span><span class="p">):</span>
111 <span class="n">local</span><span class="o">.</span><span class="n">application</span> <span class="o">=</span> <span class="bp">self</span>
112 <span class="bp">self</span><span class="o">.</span><span class="n">database_engine</span> <span class="o">=</span> <span class="n">create_engine</span><span class="p">(</span><span class="n">db_uri</span><span class="p">,</span> <span class="n">convert_unicode</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
113
114 <span class="k">def</span> <span class="nf">init_database</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
115 <span class="n">metadata</span><span class="o">.</span><span class="n">create_all</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">database_engine</span><span class="p">)</span>
116
117 <span class="k">def</span> <span class="nf">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
118 <span class="n">local</span><span class="o">.</span><span class="n">application</span> <span class="o">=</span> <span class="bp">self</span>
119 <span class="n">request</span> <span class="o">=</span> <span class="n">Request</span><span class="p">(</span><span class="n">environ</span><span class="p">)</span>
120 <span class="n">local</span><span class="o">.</span><span class="n">url_adapter</span> <span class="o">=</span> <span class="n">adapter</span> <span class="o">=</span> <span class="n">url_map</span><span class="o">.</span><span class="n">bind_to_environ</span><span class="p">(</span><span class="n">environ</span><span class="p">)</span>
121 <span class="k">try</span><span class="p">:</span>
122 <span class="n">endpoint</span><span class="p">,</span> <span class="n">values</span> <span class="o">=</span> <span class="n">adapter</span><span class="o">.</span><span class="n">match</span><span class="p">()</span>
123 <span class="n">handler</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">views</span><span class="p">,</span> <span class="n">endpoint</span><span class="p">)</span>
124 <span class="n">response</span> <span class="o">=</span> <span class="n">handler</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">**</span><span class="n">values</span><span class="p">)</span>
125 <span class="k">except</span> <span class="n">HTTPException</span><span class="p">,</span> <span class="n">e</span><span class="p">:</span>
126 <span class="n">response</span> <span class="o">=</span> <span class="n">e</span>
127 <span class="k">return</span> <span class="n">ClosingIterator</span><span class="p">(</span><span class="n">response</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">),</span>
128 <span class="p">[</span><span class="n">session</span><span class="o">.</span><span class="n">remove</span><span class="p">,</span> <span class="n">local_manager</span><span class="o">.</span><span class="n">cleanup</span><span class="p">])</span>
129 </pre></div>
130 <p>Ziemlich viel für Code für den Anfang ... gehen wir ihn mal Schritt für Schritt
131 durch. Zunächst sehen wir einige Imports: Aus dem Paket <cite>sqlalchemy</cite> holen wir
132 uns eine Factory-Funktion, die eine neue Datenbank-Engine für uns erstellt, die
133 wiederum einen Connection-Pool bereithält. Die nächsten Imports holen einige
134 Objekte in den Namensraum, die uns Werkzeug zur Verfügung stellt: ein
135 Request-Objekt; ein spezieller Iterator, der uns hilft am Ende eines Requests
136 einige Dinge aufzuräumen; und schließlich die Basisklasse für alle
137 HTTP-Exceptions.</p>
138 <p>Die nächsten fünf Imports funktionieren noch nicht, weil wir das <cite>utils</cite>-Modul
139 noch nicht erstellt haben. Doch wir werden trotzdem schon ein wenig über diese
140 Objekte sprechen. Das Objekt <cite>session</cite> ist kein aus PHP bekanntes
141 Session-Array, sondern eine Datenbank-Session von SQLAlchemy. Alle
142 Datenbank-Models, die im Kontext eines Requests erstellt werden, sind auf
143 diesem Objekt zwischengespeichert, so dass man Änderungen mit einem Schlag zum
144 Server senden kann. Im Gegensatz zu Django wird ein instantiiertes
145 Datenbank-Model automatisch von der Session verwaltet und ist in dieser Session
146 ein Singleton. Es kann also niemals zwei Instanzen des selben
147 Datenbankeintrags in einer Session geben. Das Objekt <cite>metadata</cite> stammt
148 ebenfalls aus SQLAlchemy und speichert Informationen über die Tabellen der
149 Datenbank. Es stellt zum Beispiel eine Funktion bereit, die alle im Model
150 definierten Tabellen in der Datenbank erstellt.</p>
151 <p>Das Objekt <cite>local</cite> ist ein kontext-lokales Objekt, erstellt vom Modul <cite>utility</cite>.
152 Attributzugriffe auf dieses Objekt sind an den aktuellen Request gebunden,
153 d.h. jeder Request bekommt ein anderes Objekt zurück und kann verschiedene
154 Objekte ablegen, ohne Threadingprobleme zu bekommen. Der <cite>local_manager</cite> wird
155 genutzt, um am Ende des Requests alle auf dem <cite>local</cite>-Objekt gespeicherten Daten
156 wieder freizugeben.</p>
157 <p>Der letzte Import von dort ist die URL-Map, die alle URL-Routen verwaltet.
158 Solltest du bereits mit Django gearbeitet haben, ist dies vergleichbar mit den
159 regulären Ausdrücken in der jeweiligen <tt class="docutils literal"><span class="pre">urls.py</span></tt>. Kennst du PHP, ist die
160 URL-Map ähnlich einem eingebauten <cite>mod_rewrite</cite>.</p>
161 <p>Zusätzlich importieren wir hier unser <cite>views</cite>-Modul, das die View-Funktionen
162 enthält, sowie das <cite>models</cite>-Modul, in welchem unsere Models definiert sind.
163 Auch wenn es so aussieht, als ob wir diesen Import nicht nutzen, ist er
164 wichtig. Nur dadurch werden unsere Tabellen auf dem <cite>metadata</cite>-Objekt
165 registriert.</p>
166 <p>Schauen wir auf die Anwendungsklasse. Der Konstruktor dieser Klasse nimmt die
167 Datenbank-URI entgegen, die &#8211; einfach gesagt &#8211; den Typ der Datenbank und die
168 Verbindungsdaten enthält. Für SQLite das wäre zum Beispiel
169 <tt class="docutils literal"><span class="pre">sqlite:////tmp/shorty.db</span></tt> (die vier Slashes sind <strong>kein</strong> Tippfehler).</p>
170 <p>Im Konstruktor erstellen wir auch gleich eine Datenbank-Engine für diese URI und
171 aktivieren das automatische Umwandeln von Bytestrings nach Unicode. Das ist
172 nützlich, weil sowohl Jinja als auch Werkzeug intern nur Unicode verwenden.</p>
173 <p>Des Weiteren binden wir die Anwendung an das <cite>local</cite>-Objekt. Das ist
174 eigentlich nicht nötig, aber nützlich, wenn wir mit der Anwendung in der
175 Python-Shell spielen wollen. Damit werden direkt nach dem Instantiieren der
176 Anwendung die Datenbankfunktionen testen. Wenn wir das nicht tun, wird der
177 Python-Interpreter einen Fehler werfen, wenn außerhalb eines Requests versucht
178 wird, eine SQLAlchemy-Session zu erstellen.</p>
179 <p>Die Methode <cite>init_database</cite> können wir später im Managementscript verwenden, um
180 alle Tabellen zu erstellen, die wir definiert haben.</p>
181 <p>Nun zur eigentlichen WSGI-Anwendung, der <cite>__call__</cite>-Methode. Dort passiert das
182 so genannte &#8220;Request Dispatching&#8221;, also das Weiterleiten von eingehenden
183 Anfragen zu den richtigen Funktionen. Als Erstes erstellen wir dort ein neues
184 Request-Objekt, um nicht direkt mit <cite>environ</cite>, dem Dictionary mit den
185 Umgebungsvariablen, arbeiten zu müssen. Dann binden wir die Anwendung an das
186 <cite>local</cite>-Objekt für den aktuellen Kontext.</p>
187 <p>Anschließend erstellen wir einen URL-Adapter, indem wir die URL-Map an die
188 aktuelle WSGI-Umgebung binden. Der Adapter weiß dann, wie die aktuelle URL
189 aussieht, wo die Anwendung eingebunden ist etc. Diesen Adapter können wir
190 nutzen, um URLs zu erzeugen oder gegen den aktuellen Request zu matchen. Wir
191 binden diesen Adapter auch an das <cite>local</cite>-Objekt, damit wir im <cite>utils</cite>-Modul
192 auf ihn zugreifen können.</p>
193 <p>Danach kommt ein <cite>try</cite>/<cite>except</cite>-Konstrukt, das HTTP-Fehler abfängt, die während
194 des Matchings oder in einer View-Funktion auftreten können. Wenn der Adapter
195 keinen Endpoint für die aktuelle URL findet, wird er eine <cite>NotFound</cite>-Exception
196 werfen, die wir wie ein Response Objekt aufrufen können. Der Endpoint ist in
197 unserem Fall der Name der Funktion im <cite>views</cite>-Modul, die wir aufrufen möchten.
198 Wir suchen uns einfach mit <cite>getattr</cite> die Funktion dem Namen nach heraus und
199 rufen sie mit dem Request-Objekt und den URL-Werten auf.</p>
200 <p>Am Schluss rufen wir das gewonnene Response-Objekt (oder die Exception) als
201 WSGI-Anwendung auf und übergeben den Rückgabewert dieser Funktion an den
202 <cite>ClosingIterator</cite>, zusammen mit zwei Funktionen fürs Aufräumen. Dies schließt
203 die SQLAlchemy-Session und leert das <cite>local</cite>-Objekt für diesen Request.</p>
204 <p>Nun müssen wir zwei leere Dateien <tt class="docutils literal"><span class="pre">shorty/views.py</span></tt> und <tt class="docutils literal"><span class="pre">shorty/models.py</span></tt>
205 erstellen, damit die Imports nicht fehlschlagen. Den tatsächlichen Code für
206 diese Module werden wir ein wenig später erstellen.</p>
207 </div>
208 <div class="section">
209 <h3 id="teil-2-die-utilities">Teil 2: Die Utilities</h3>
210 <p>Nun haben wir die eigentliche WSGI-Applikation fertig gestellt, aber wir müssen
211 das Utility-Modul noch um Code ergänzen, damit die Imports klappen. Fürs Erste
212 fügen wir nur die Objekte hinzu, die wir brauchen, damit die Applikation
213 funktioniert. Der folgende Code landet in der Datei <tt class="docutils literal"><span class="pre">shorty/utils.py</span></tt>:</p>
214 <div class="syntax"><pre><span class="k">from</span> <span class="nn">sqlalchemy</span> <span class="k">import</span> <span class="n">MetaData</span>
215 <span class="k">from</span> <span class="nn">sqlalchemy.orm</span> <span class="k">import</span> <span class="n">create_session</span><span class="p">,</span> <span class="n">scoped_session</span>
216 <span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">Local</span><span class="p">,</span> <span class="n">LocalManager</span>
217 <span class="k">from</span> <span class="nn">werkzeug.routing</span> <span class="k">import</span> <span class="n">Map</span><span class="p">,</span> <span class="n">Rule</span>
218
219 <span class="n">local</span> <span class="o">=</span> <span class="n">Local</span><span class="p">()</span>
220 <span class="n">local_manager</span> <span class="o">=</span> <span class="n">LocalManager</span><span class="p">([</span><span class="n">local</span><span class="p">])</span>
221 <span class="n">application</span> <span class="o">=</span> <span class="n">local</span><span class="p">(</span><span class="s">&#39;application&#39;</span><span class="p">)</span>
222
223 <span class="n">metadata</span> <span class="o">=</span> <span class="n">MetaData</span><span class="p">()</span>
224 <span class="n">session</span> <span class="o">=</span> <span class="n">scoped_session</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="n">create_session</span><span class="p">(</span><span class="n">application</span><span class="o">.</span><span class="n">database_engine</span><span class="p">,</span>
225 <span class="n">transactional</span><span class="o">=</span><span class="bp">True</span><span class="p">),</span> <span class="n">local_manager</span><span class="o">.</span><span class="n">get_ident</span><span class="p">)</span>
226
227 <span class="n">url_map</span> <span class="o">=</span> <span class="n">Map</span><span class="p">()</span>
228 <span class="k">def</span> <span class="nf">expose</span><span class="p">(</span><span class="n">rule</span><span class="p">,</span> <span class="o">**</span><span class="n">kw</span><span class="p">):</span>
229 <span class="k">def</span> <span class="nf">decorate</span><span class="p">(</span><span class="n">f</span><span class="p">):</span>
230 <span class="n">kw</span><span class="p">[</span><span class="s">&#39;endpoint&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">__name__</span>
231 <span class="n">url_map</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Rule</span><span class="p">(</span><span class="n">rule</span><span class="p">,</span> <span class="o">**</span><span class="n">kw</span><span class="p">))</span>
232 <span class="k">return</span> <span class="n">f</span>
233 <span class="k">return</span> <span class="n">decorate</span>
234
235 <span class="k">def</span> <span class="nf">url_for</span><span class="p">(</span><span class="n">endpoint</span><span class="p">,</span> <span class="n">_external</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="o">**</span><span class="n">values</span><span class="p">):</span>
236 <span class="k">return</span> <span class="n">local</span><span class="o">.</span><span class="n">url_adapter</span><span class="o">.</span><span class="n">build</span><span class="p">(</span><span class="n">endpoint</span><span class="p">,</span> <span class="n">values</span><span class="p">,</span> <span class="n">force_external</span><span class="o">=</span><span class="n">_external</span><span class="p">)</span>
237 </pre></div>
238 <p>Zunächst importieren wir wieder eine Menge, dann erstellen wir das
239 <cite>local</cite>-Objekt und den Manager dafür, wie bereits im vorherigen Abschnitt
240 besprochen. Neu ist hier, dass der Aufruf eines <cite>local</cite>-Objekts mit einem
241 String ein Proxy-Objekt zurück gibt. Dieses zeigt stets auf die gleichnamigen
242 Attribute des <cite>local</cite>-Objekts. Beispielsweise verweist nun <cite>application</cite>
243 dauerhaft auf <cite>local.application</cite>. Wenn du jedoch darauf zugreifst und kein
244 Objekt an <cite>local.application</cite> gebunden ist, erhältst du einen <cite>RuntimeError</cite>.</p>
245 <p>Die folgenden drei Zeilen sind im Prinzip alles, um SQLAlchemy 0.4 oder höher in
246 eine Werkzeug-Anwendung einzubinden. Wir erstellen ein Metadaten-Objekt für all
247 unsere Tabellen sowie eine &#8220;scoped session&#8221; über die
248 <cite>scoped_session</cite>-Factory-Funktion. Dadurch wird SQLAlchemy angewiesen,
249 praktisch denselben Algorithmus zur Ermittlung des aktuellen Kontextes zu
250 verwenden, wie es auch Werkzeug für die <cite>local</cite>-Objekte tut, und die
251 Datenbank-Engine der aktuellen Applikation zu benutzen.</p>
252 <p>Wenn wir nicht vorhaben, mehrere Instanzen der Applikation in derselben Instanz
253 des Python-Interpreters zu unterstützen, können wir den Code einfach halten,
254 indem wir nicht über das aktuelle <cite>local</cite>-Objekt auf die Applikation zugreifen,
255 sondern einen anderen Weg nehmen. Dieser Ansatz wird etwa von Django verfolgt,
256 macht es allerdings unmöglich, mehrere solcher Applikationen zu kombinieren.</p>
257 <p>Der restliche Code des Moduls wird für unsere Views benutzt. Die Idee besteht
258 darin, Dekoratoren zu benutzen, um die URL-Dispatching-Regeln für
259 View-Funktionen festzulegen, anstatt ein zentrales Modul <tt class="docutils literal"><span class="pre">urls.py</span></tt> zu
260 verwenden, wie es Django tut, oder über eine <tt class="docutils literal"><span class="pre">.htaccess</span></tt>-Datei URLs
261 umzuschreiben, wie man es in PHP machen würde. Dies ist <strong>eine</strong> Möglichkeit,
262 dies zu tun, und es gibt unzählige andere Wege der Handhabung von
263 URL-Regeldefinitionen.</p>
264 <p>Die Funktion <cite>url_for</cite>, die wir ebenfalls definieren, bietet einen einfachen
265 Weg, URLs anhand des Endpointes zu generieren. Wir werden sie später in den
266 Views als auch unserem Model verwenden.</p>
267 </div>
268 <div class="section">
269 <h3 id="unterbrechung-und-nun-etwas-komplett-anderes">Unterbrechung: Und nun etwas komplett anderes</h3>
270 <p>Da wir nun das Grundgerüst für unsere Anwendung fertig gestellt haben, können
271 wir jetzt erst einmal relaxen und uns etwas komplett anderem zuwenden: den
272 Verwaltungs-Scripts. Während der Entwicklung erledigt man häufig immer
273 wiederkehrende Aufgaben, wie zum Beispiel das Starten eines Entwicklungs-Servers
274 (im Gegensatz zu PHP benötigt Werkzeug keinen Apache-Server; der in Python
275 integrierte <cite>wsgiref</cite>-Server ist völlig ausreichend und für die Entwicklung auf
276 jeden Fall empfehlenswert), das Starten eines Python-Interpreters (um mit den
277 Datenbankobjekten herumzuspielen oder die Datenbank zu initialisieren) etc.</p>
278 <p>Werkzeug macht es unglaublich einfach, solche Verwaltungs-Scripts zu schreiben.
279 Der folgende Code implementiert ein voll funktionsfähiges Verwaltungs-Script
280 und gehört in die <cite>manage.py</cite>-Datei, welche du am Anfang erstellt hast:</p>
281 <div class="syntax"><pre><span class="c">#!/usr/bin/env python</span>
282 <span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">script</span>
283
284 <span class="k">def</span> <span class="nf">make_app</span><span class="p">():</span>
285 <span class="k">from</span> <span class="nn">shorty.application</span> <span class="k">import</span> <span class="n">Shorty</span>
286 <span class="k">return</span> <span class="n">Shorty</span><span class="p">(</span><span class="s">&#39;sqlite:////tmp/shorty.db&#39;</span><span class="p">)</span>
287
288 <span class="k">def</span> <span class="nf">make_shell</span><span class="p">():</span>
289 <span class="k">from</span> <span class="nn">shorty</span> <span class="k">import</span> <span class="n">models</span><span class="p">,</span> <span class="n">utils</span>
290 <span class="n">application</span> <span class="o">=</span> <span class="n">make_app</span><span class="p">()</span>
291 <span class="k">return</span> <span class="nb">locals</span><span class="p">()</span>
292
293 <span class="n">action_runserver</span> <span class="o">=</span> <span class="n">script</span><span class="o">.</span><span class="n">make_runserver</span><span class="p">(</span><span class="n">make_app</span><span class="p">,</span> <span class="n">use_reloader</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
294 <span class="n">action_shell</span> <span class="o">=</span> <span class="n">script</span><span class="o">.</span><span class="n">make_shell</span><span class="p">(</span><span class="n">make_shell</span><span class="p">)</span>
295 <span class="n">action_initdb</span> <span class="o">=</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">make_app</span><span class="p">()</span><span class="o">.</span><span class="n">init_database</span><span class="p">()</span>
296
297 <span class="n">script</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
298 </pre></div>
299 <p><cite>werkzeug.script</cite> ist genauer in der <a class="reference" href="script.html">Script-Dokumentation</a> beschrieben, und da
300 der Großteil des Codes verständlich sein sollte, werden wir hier nicht näher
301 darauf eingehen.</p>
302 <p>Es ist aber wichtig, dass du <tt class="docutils literal"><span class="pre">python</span> <span class="pre">manage.py</span> <span class="pre">shell</span></tt> ausführen kannst, um
303 eine interaktive Python-Shell zu starten. Solltest du einen Traceback
304 bekommen, kontrolliere bitte die darin genannte Code-Zeile und vergleiche sie
305 mit dem entsprechenden Code in dieser Anleitung.</p>
306 <p>Sobald das Script läuft, können wir mit dem Schreiben der Datenbank-Models
307 beginnen.</p>
308 </div>
309 <div class="section">
310 <h3 id="teil-3-datenbank-models">Teil 3: Datenbank-Models</h3>
311 <p>Jetzt können wir die Models erstellen. Da die Anwendung ziemlich einfach ist,
312 haben wir nur ein Model und eine Tabelle:</p>
313 <div class="syntax"><pre><span class="k">from</span> <span class="nn">datetime</span> <span class="k">import</span> <span class="n">datetime</span>
314 <span class="k">from</span> <span class="nn">sqlalchemy</span> <span class="k">import</span> <span class="n">Table</span><span class="p">,</span> <span class="n">Column</span><span class="p">,</span> <span class="n">String</span><span class="p">,</span> <span class="n">Boolean</span><span class="p">,</span> <span class="n">DateTime</span>
315 <span class="k">from</span> <span class="nn">shorty.utils</span> <span class="k">import</span> <span class="n">session</span><span class="p">,</span> <span class="n">metadata</span><span class="p">,</span> <span class="n">url_for</span><span class="p">,</span> <span class="n">get_random_uid</span>
316
317 <span class="n">url_table</span> <span class="o">=</span> <span class="n">Table</span><span class="p">(</span><span class="s">&#39;urls&#39;</span><span class="p">,</span> <span class="n">metadata</span><span class="p">,</span>
318 <span class="n">Column</span><span class="p">(</span><span class="s">&#39;uid&#39;</span><span class="p">,</span> <span class="n">String</span><span class="p">(</span><span class="mf">140</span><span class="p">),</span> <span class="n">primary_key</span><span class="o">=</span><span class="bp">True</span><span class="p">),</span>
319 <span class="n">Column</span><span class="p">(</span><span class="s">&#39;target&#39;</span><span class="p">,</span> <span class="n">String</span><span class="p">(</span><span class="mf">500</span><span class="p">)),</span>
320 <span class="n">Column</span><span class="p">(</span><span class="s">&#39;added&#39;</span><span class="p">,</span> <span class="n">DateTime</span><span class="p">),</span>
321 <span class="n">Column</span><span class="p">(</span><span class="s">&#39;public&#39;</span><span class="p">,</span> <span class="n">Boolean</span><span class="p">)</span>
322 <span class="p">)</span>
323
324 <span class="k">class</span> <span class="nc">URL</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
325
326 <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="n">public</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">uid</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">added</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
327 <span class="bp">self</span><span class="o">.</span><span class="n">target</span> <span class="o">=</span> <span class="n">target</span>
328 <span class="bp">self</span><span class="o">.</span><span class="n">public</span> <span class="o">=</span> <span class="n">public</span>
329 <span class="bp">self</span><span class="o">.</span><span class="n">added</span> <span class="o">=</span> <span class="n">added</span> <span class="ow">or</span> <span class="n">datetime</span><span class="o">.</span><span class="n">utcnow</span><span class="p">()</span>
330 <span class="k">if</span> <span class="ow">not</span> <span class="n">uid</span><span class="p">:</span>
331 <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
332 <span class="n">uid</span> <span class="o">=</span> <span class="n">get_random_uid</span><span class="p">()</span>
333 <span class="k">if</span> <span class="ow">not</span> <span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">uid</span><span class="p">):</span>
334 <span class="k">break</span>
335 <span class="bp">self</span><span class="o">.</span><span class="n">uid</span> <span class="o">=</span> <span class="n">uid</span>
336
337 <span class="nd">@property</span>
338 <span class="k">def</span> <span class="nf">short_url</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
339 <span class="k">return</span> <span class="n">url_for</span><span class="p">(</span><span class="s">&#39;link&#39;</span><span class="p">,</span> <span class="n">uid</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">uid</span><span class="p">,</span> <span class="n">_external</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
340
341 <span class="k">def</span> <span class="nf">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
342 <span class="k">return</span> <span class="s">&#39;&lt;URL </span><span class="si">%r</span><span class="s">&gt;&#39;</span> <span class="o">%</span> <span class="bp">self</span><span class="o">.</span><span class="n">uid</span>
343
344 <span class="n">session</span><span class="o">.</span><span class="n">mapper</span><span class="p">(</span><span class="n">URL</span><span class="p">,</span> <span class="n">url_table</span><span class="p">)</span>
345 </pre></div>
346 <p>Dieses Modul ist gut überschaubar. Wir importieren alles, was wir von
347 SQLAlchemy benötigen, und erstellen die Tabelle. Dann fügen wir eine Klasse
348 für diese Tabelle hinzu und verbinden beide miteinander. Für eine
349 detailliertere Erklärung bezüglich SQLAlchemy solltest du dir das
350 <a class="reference" href="http://www.sqlalchemy.org/docs/04/ormtutorial.html">exzellente Tutorial</a> anschauen.</p>
351 <p>Im Konstruktor generieren wir solange eine eindeutige ID, bis wir eine finden,
352 die noch nicht belegt ist. Die <cite>get_random_uid</cite>-Funktion fehlt &#8211; wir müssen
353 sie noch in unser <cite>utils</cite>-Modul einfügen:</p>
354 <div class="syntax"><pre><span class="k">from</span> <span class="nn">random</span> <span class="k">import</span> <span class="n">sample</span><span class="p">,</span> <span class="n">randrange</span>
355
356 <span class="n">URL_CHARS</span> <span class="o">=</span> <span class="s">&#39;abcdefghijkmpqrstuvwxyzABCDEFGHIJKLMNPQRST23456789&#39;</span>
357
358 <span class="k">def</span> <span class="nf">get_random_uid</span><span class="p">():</span>
359 <span class="k">return</span> <span class="s">&#39;&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">sample</span><span class="p">(</span><span class="n">URL_CHARS</span><span class="p">,</span> <span class="n">randrange</span><span class="p">(</span><span class="mf">3</span><span class="p">,</span> <span class="mf">9</span><span class="p">)))</span>
360 </pre></div>
361 <p>Wenn das getan ist, können wir <tt class="docutils literal"><span class="pre">python</span> <span class="pre">manage.py</span> <span class="pre">initdb</span></tt> ausführen, um die
362 Datenbank zu erstellen und <tt class="docutils literal"><span class="pre">python</span> <span class="pre">manage.py</span> <span class="pre">shell</span></tt>, um damit herumzuspielen:</p>
363 <div class="syntax"><pre><span class="go">Interactive Werkzeug Shell</span>
364 <span class="gp">&gt;&gt;&gt; </span><span class="k">from</span> <span class="nn">shorty.models</span> <span class="k">import</span> <span class="n">session</span><span class="p">,</span> <span class="n">URL</span>
365 </pre></div>
366 <p>Jetzt können wir einige URLs zu der Datenbank hinzufügen:</p>
367 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">urls</span> <span class="o">=</span> <span class="p">[</span><span class="n">URL</span><span class="p">(</span><span class="s">&#39;http://example.org/&#39;</span><span class="p">),</span> <span class="n">URL</span><span class="p">(</span><span class="s">&#39;http://localhost:5000/&#39;</span><span class="p">)]</span>
368 <span class="gp">&gt;&gt;&gt; </span><span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
369 <span class="go">[]</span>
370 <span class="gp">&gt;&gt;&gt; </span><span class="n">session</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
371 <span class="gp">&gt;&gt;&gt; </span><span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
372 <span class="go">[&lt;URL &#39;5cFbsk&#39;&gt;, &lt;URL &#39;mpugsT&#39;&gt;]</span>
373 </pre></div>
374 <p>Wie du sehen kannst, müssen wir <tt class="docutils literal"><span class="pre">session.commit()</span></tt> aufrufen, um die Änderungen
375 in der Datenbank zu speichern. Nun erstellen wir ein privates Element mit einer
376 eigenen UID:</p>
377 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">URL</span><span class="p">(</span><span class="s">&#39;http://werkzeug.pocoo.org/&#39;</span><span class="p">,</span> <span class="bp">False</span><span class="p">,</span> <span class="s">&#39;werkzeug-webpage&#39;</span><span class="p">)</span>
378 <span class="gp">&gt;&gt;&gt; </span><span class="n">session</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
379 </pre></div>
380 <p>Dann fragen wir alle ab:</p>
381 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">filter_by</span><span class="p">(</span><span class="n">public</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
382 <span class="go">[&lt;URL &#39;werkzeug-webpage&#39;&gt;]</span>
383 <span class="gp">&gt;&gt;&gt; </span><span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">filter_by</span><span class="p">(</span><span class="n">public</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
384 <span class="go">[&lt;URL &#39;5cFbsk&#39;&gt;, &lt;URL &#39;mpugsT&#39;&gt;]</span>
385 <span class="gp">&gt;&gt;&gt; </span><span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&#39;werkzeug-webpage&#39;</span><span class="p">)</span>
386 <span class="go">&lt;URL &#39;werkzeug-webpage&#39;&gt;</span>
387 </pre></div>
388 <p>Jetzt haben wir einige Datensätze in der Datenbank und wissen ungefähr, auf
389 welche Weise SQLAlchemy funktioniert. Zeit, unsere Views zu erstellen.</p>
390 </div>
391 <div class="section">
392 <h3 id="teil-4-die-view-funktionen">Teil 4: Die View-Funktionen</h3>
393 <p>Nachdem wir mit SQLAlchemy herumgespielt haben, können wir zurück zu Werkzeug
394 gehen und anfangen, unsere View-Funktionen zu erstellen. Der Begriff
395 &#8220;View-Funktion&#8221; kommt von Django. Dort werden die Funktionen, die Templates
396 befüllen und ausgeben, so genannt. Deshalb ist unser Beispiel eine Umsetzung
397 von MVT (Model, View, Template) und nicht etwa MVC (Model, View, Controller).
398 Die beiden Bezeichnungen bedeuten dasselbe, aber es ist viel einfacher,
399 dieselbe Benennung wie Django zu nutzen.</p>
400 <p>Als Anfang erstellen wir einfach eine View-Funktion für neue URLs und eine
401 Funktion, die eine Nachricht über einen neuen Link darstellt. Das wird der
402 Inhalt unserer noch leeren <tt class="docutils literal"><span class="pre">views.py</span></tt>-Datei:</p>
403 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">redirect</span>
404 <span class="k">from</span> <span class="nn">werkzeug.exceptions</span> <span class="k">import</span> <span class="n">NotFound</span>
405 <span class="k">from</span> <span class="nn">shorty.utils</span> <span class="k">import</span> <span class="n">session</span><span class="p">,</span> <span class="n">render_template</span><span class="p">,</span> <span class="n">expose</span><span class="p">,</span> <span class="n">validate_url</span><span class="p">,</span> \
406 <span class="n">url_for</span>
407 <span class="k">from</span> <span class="nn">shorty.models</span> <span class="k">import</span> <span class="n">URL</span>
408
409 <span class="nd">@expose</span><span class="p">(</span><span class="s">&#39;/&#39;</span><span class="p">)</span>
410 <span class="k">def</span> <span class="nf">new</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
411 <span class="n">error</span> <span class="o">=</span> <span class="n">url</span> <span class="o">=</span> <span class="s">&#39;&#39;</span>
412 <span class="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n">method</span> <span class="o">==</span> <span class="s">&#39;POST&#39;</span><span class="p">:</span>
413 <span class="n">url</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&#39;url&#39;</span><span class="p">)</span>
414 <span class="n">alias</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&#39;alias&#39;</span><span class="p">)</span>
415 <span class="k">if</span> <span class="ow">not</span> <span class="n">validate_url</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
416 <span class="n">error</span> <span class="o">=</span> <span class="s">u&quot;Entschuldigung, aber ich kann die angegebene &quot;</span> \
417 <span class="s">u&quot;URL nicht kürzen.&quot;</span>
418 <span class="k">elif</span> <span class="n">alias</span><span class="p">:</span>
419 <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">alias</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mf">140</span><span class="p">:</span>
420 <span class="n">error</span> <span class="o">=</span> <span class="s">&#39;Dein Alias ist zu lang&#39;</span>
421 <span class="k">elif</span> <span class="s">&#39;/&#39;</span> <span class="ow">in</span> <span class="n">alias</span><span class="p">:</span>
422 <span class="n">error</span> <span class="o">=</span> <span class="s">&#39;Dein Alias darf keinen Slash beinhalten&#39;</span>
423 <span class="k">elif</span> <span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">alias</span><span class="p">):</span>
424 <span class="n">error</span> <span class="o">=</span> <span class="s">&#39;Der angegeben Alias existiert bereits&#39;</span>
425 <span class="k">if</span> <span class="ow">not</span> <span class="n">error</span><span class="p">:</span>
426 <span class="n">uid</span> <span class="o">=</span> <span class="n">URL</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="s">&#39;private&#39;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="p">,</span> <span class="n">alias</span><span class="p">)</span><span class="o">.</span><span class="n">uid</span>
427 <span class="n">session</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
428 <span class="k">return</span> <span class="n">redirect</span><span class="p">(</span><span class="n">url_for</span><span class="p">(</span><span class="s">&#39;display&#39;</span><span class="p">,</span> <span class="n">uid</span><span class="o">=</span><span class="n">uid</span><span class="p">))</span>
429 <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">&#39;new.html&#39;</span><span class="p">,</span> <span class="n">error</span><span class="o">=</span><span class="n">error</span><span class="p">,</span> <span class="n">url</span><span class="o">=</span><span class="n">url</span><span class="p">)</span>
430
431 <span class="nd">@expose</span><span class="p">(</span><span class="s">&#39;/display/&lt;uid&gt;&#39;</span><span class="p">)</span>
432 <span class="k">def</span> <span class="nf">display</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">uid</span><span class="p">):</span>
433 <span class="n">url</span> <span class="o">=</span> <span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">uid</span><span class="p">)</span>
434 <span class="k">if</span> <span class="ow">not</span> <span class="n">url</span><span class="p">:</span>
435 <span class="k">raise</span> <span class="n">NotFound</span><span class="p">()</span>
436 <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">&#39;display.html&#39;</span><span class="p">,</span> <span class="n">url</span><span class="o">=</span><span class="n">url</span><span class="p">)</span>
437
438 <span class="nd">@expose</span><span class="p">(</span><span class="s">&#39;/u/&lt;uid&gt;&#39;</span><span class="p">)</span>
439 <span class="k">def</span> <span class="nf">link</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">uid</span><span class="p">):</span>
440 <span class="n">url</span> <span class="o">=</span> <span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">uid</span><span class="p">)</span>
441 <span class="k">if</span> <span class="ow">not</span> <span class="n">url</span><span class="p">:</span>
442 <span class="k">raise</span> <span class="n">NotFound</span><span class="p">()</span>
443 <span class="k">return</span> <span class="n">redirect</span><span class="p">(</span><span class="n">url</span><span class="o">.</span><span class="n">target</span><span class="p">,</span> <span class="mf">301</span><span class="p">)</span>
444
445 <span class="nd">@expose</span><span class="p">(</span><span class="s">&#39;/list/&#39;</span><span class="p">,</span> <span class="n">defaults</span><span class="o">=</span><span class="p">{</span><span class="s">&#39;page&#39;</span><span class="p">:</span> <span class="mf">1</span><span class="p">})</span>
446 <span class="nd">@expose</span><span class="p">(</span><span class="s">&#39;/list/&lt;int:page&gt;&#39;</span><span class="p">)</span>
447 <span class="k">def</span> <span class="nf">list</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">page</span><span class="p">):</span>
448 <span class="k">pass</span>
449 </pre></div>
450 <p>Wieder einmal ziemlich viel Code, aber das meiste ist normale
451 Formularvalidierung. Wir erstellen zwei Funktionen, <cite>new</cite> und <cite>display</cite>, und
452 dekorieren sie mit unserem <cite>expose</cite>-Dekorator aus dem <cite>utils</cite>-Modul. Dieser
453 Dekorator fügt eine neue Regel zur URL-Map hinzu, indem er alle Parameter zum
454 Konstruktor eines Rule-Objekts übergibt und den Endpoint auf den Namen der
455 Funktion setzt. Damit können wir einfach URLs zu den Funktionen erzeugen &#8211;
456 wir nutzen ihre Funktionsnamen als Endpoint.</p>
457 <p>Denke daran, dass dieser Code nicht unbedingt eine gute Idee für größere
458 Anwendungen ist. In solchen Fällen ist es besser, den vollen Importnamen mit
459 einem allgemeinen Prefix oder etwas Ähnlichem als Endpoint zu nutzen. Sonst
460 wird es ziemlich verwirrend.</p>
461 <p>Die Formularvalidierung in der <cite>new</cite>-Methode ist ziemlich simpel. Wir
462 kontrollieren, ob die aktuelle HTTP-Methode <cite>POST</cite> ist. Falls ja nehmen wir
463 die Daten vom Request und validieren sie. Wenn dort kein Fehler auftritt,
464 erstellen wir ein neues <cite>URL</cite>-Objekt, übergeben es der Datenbank und leiten auf
465 die Anzeige-Seite um.</p>
466 <p>Die <cite>display</cite>-Funktion ist nicht viel komplizierter. Die URL-Rule erwartet
467 einen <cite>uid</cite>-Parameter, welchen die Funktion entsprechend akzeptiert. Danach
468 holen wir das <cite>URL</cite>-Objekt mit der angegebenen UID und geben das Template aus,
469 dem wir wiederum das <cite>URL</cite>-Objekt übergeben.</p>
470 <p>Wenn die URL nicht existiert, werfen wir eine <cite>NotFound</cite>-Exception, welche eine
471 statische &#8220;404 Seite nicht gefunden&#8221;-Seite anzeigt. Wir können diese später
472 mit einer speziellen Fehlerseite ersetzen, indem wir die Exception auffangen,
473 bevor die <cite>HTTPException</cite> geworfen wird.</p>
474 <p>Die View-Funktion <cite>link</cite> wird von unseren Models in der <cite>short_url</cite>-Eigenschaft
475 genutzt und ist die kurze URL, die wir vermitteln. Wenn also die URL-UID
476 <tt class="docutils literal"><span class="pre">foobar</span></tt> ist, wird die URL unter <tt class="docutils literal"><span class="pre">http://localhost:5000/u/foobar</span></tt>
477 erreichbar sein.</p>
478 <p>Die View-Funktion <cite>list</cite> wurde noch nicht geschrieben, das machen wir später.
479 Wichtig ist allerdings, dass die Funktion einen optionalen URL-Parameter
480 akzeptiert. Der erste Dekorator sagt Werkzeug, dass für den Request-Pfad
481 <tt class="docutils literal"><span class="pre">/page/</span></tt> die erste Seite angezeigt wird (da der Parameter <cite>page</cite>
482 standardmäßig auf 1 gesetzt wird). Wichtiger ist die Tatsache, dass Werkzeug
483 URLs normalisiert. Wenn du also <tt class="docutils literal"><span class="pre">/page</span></tt> oder <tt class="docutils literal"><span class="pre">/page/1</span></tt> aufrufst, wirst du
484 in beiden Fällen zu <tt class="docutils literal"><span class="pre">/page/</span></tt> umgeleitet. Das geschieht automatisch und macht
485 Google glücklich. Wenn du dieses Verhalten nicht magst, kannst du es
486 abschalten.</p>
487 <p>Und wieder einmal müssen wir zwei Objekte aus dem <cite>utils</cite>-Modul importieren,
488 die jetzt noch nicht existieren. Eines von diesen soll ein Jinja-Template in
489 ein Response-Objekt verwandeln, das andere prüft eine URL. Fügen wir also
490 diese zu <tt class="docutils literal"><span class="pre">utils.py</span></tt> hinzu:</p>
491 <div class="syntax"><pre><span class="k">from</span> <span class="nn">os</span> <span class="k">import</span> <span class="n">path</span>
492 <span class="k">from</span> <span class="nn">urlparse</span> <span class="k">import</span> <span class="n">urlparse</span>
493 <span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">Response</span>
494 <span class="k">from</span> <span class="nn">jinja</span> <span class="k">import</span> <span class="n">Environment</span><span class="p">,</span> <span class="n">FileSystemLoader</span>
495
496 <span class="n">ALLOWED_SCHEMES</span> <span class="o">=</span> <span class="n">frozenset</span><span class="p">([</span><span class="s">&#39;http&#39;</span><span class="p">,</span> <span class="s">&#39;https&#39;</span><span class="p">,</span> <span class="s">&#39;ftp&#39;</span><span class="p">,</span> <span class="s">&#39;ftps&#39;</span><span class="p">])</span>
497 <span class="n">TEMPLATE_PATH</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">__file__</span><span class="p">),</span> <span class="s">&#39;templates&#39;</span><span class="p">)</span>
498 <span class="n">jinja_env</span> <span class="o">=</span> <span class="n">Environment</span><span class="p">(</span><span class="n">loader</span><span class="o">=</span><span class="n">FileSystemLoader</span><span class="p">(</span><span class="n">TEMPLATE_PATH</span><span class="p">))</span>
499 <span class="n">jinja_env</span><span class="o">.</span><span class="n">globals</span><span class="p">[</span><span class="s">&#39;url_for&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">url_for</span>
500
501 <span class="k">def</span> <span class="nf">render_template</span><span class="p">(</span><span class="n">template</span><span class="p">,</span> <span class="o">**</span><span class="n">context</span><span class="p">):</span>
502 <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">jinja_env</span><span class="o">.</span><span class="n">get_template</span><span class="p">(</span><span class="n">template</span><span class="p">)</span><span class="o">.</span><span class="n">render</span><span class="p">(</span><span class="o">**</span><span class="n">context</span><span class="p">),</span>
503 <span class="n">mimetype</span><span class="o">=</span><span class="s">&#39;text/html&#39;</span><span class="p">)</span>
504
505 <span class="k">def</span> <span class="nf">validate_url</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
506 <span class="k">return</span> <span class="n">urlparse</span><span class="p">(</span><span class="n">url</span><span class="p">)[</span><span class="mf">0</span><span class="p">]</span> <span class="ow">in</span> <span class="n">ALLOWED_SCHEMES</span>
507 </pre></div>
508 <p>Im Grunde ist das alles. Die Validierungsfunktion prüft, ob deine URL wie eine
509 HTTP- oder FTP-URL aussieht. Wir machen dies, um uns zu versichern, dass
510 niemand potentiell gefährliches JavaScript oder ähnliche URLs abschickt. Die
511 <cite>render_template</cite>-Funktion ist auch nicht viel komplizierter, sie schaut nach
512 einem Template im Dateisystem im <cite>templates</cite>-Ordner und gibt es als Response
513 aus.</p>
514 <p>Weiterhin fürgen wir die <cite>url_for</cite>-Funktion in den globalen Kontext des
515 Templates ein, so dass wir auch in Templates URLs erzeugen können.</p>
516 <p>Da wir jetzt unsere beiden ersten View-Funktionen haben, ist es Zeit, die
517 Templates hinzuzufügen.</p>
518 </div>
519 <div class="section">
520 <h3 id="teil-5-die-templates">Teil 5: Die Templates</h3>
521 <p>Wir haben beschlossen, in diesem Beispiel Jinja-Templates zu nutzen. Wenn du
522 weißt, wie man Django-Templates nutzt, sollte es dir bekannt vorkommen; wenn du
523 bis jetzt mit PHP gearbeitet hast, kannst du Jinja-Templates mit Smarty
524 vergleichen. Wenn du bis jetzt PHP als Templatesprache genutzt hast, solltest
525 du für dein nächstes Projekt mal <a class="reference" href="http://www.makotemplates.org/">Mako</a> ansehen.</p>
526 <p><strong>Sicherheitswarnung</strong>: Wir nutzen hier Jinja, welches eine textbasierte
527 Template-Engine ist. Da Jinja nicht weiß, womit es arbeitet, musst du, wenn du
528 HTML-Templates erstellst, <em>alle</em> Werte maskieren, die irgendwann an
529 irgendeinem Punkt irgendeines der folgenden Zeichen enthalten können: <tt class="docutils literal"><span class="pre">&gt;</span></tt>,
530 <tt class="docutils literal"><span class="pre">&lt;</span></tt> oder <tt class="docutils literal"><span class="pre">&amp;</span></tt>. Innerhalb von Attributen musst du außerdem Anführungszeichen
531 maskieren. Du kannst Jinjas <tt class="docutils literal"><span class="pre">|e</span></tt>-Filter für normales Escaping benutzen.
532 Wenn du <cite>true</cite> als Argument übergibst, maskiert es außerdem Anführungszeichen
533 (<tt class="docutils literal"><span class="pre">|e(true)</span></tt>). Wie du in den Beispielen unterhalb sehen kannst, maskieren wir
534 die URLs nicht. Der Grund dafür ist, dass wir keine <tt class="docutils literal"><span class="pre">&amp;</span></tt> in den URLs haben
535 und deshalb ist es sicher, auf Escaping zu verzichten.</p>
536 <p>Der Einfachheit halber werden wir HTML 4 in unseren Templates nutzen. Wenn du
537 etwas Erfahrung mit XHTML hast, kannst du sie in XHTML schreiben. Aber beachte,
538 dass das Beispiel-Stylesheet unten nicht mit XHTML funktioniert.</p>
539 <p>Eine coole Sache, die Jinja von Django übernommen hat, ist Templatevererbung.
540 Das bedeutet, dass wir oft genutzte Stücke in ein Basistemplate auslagern und
541 es mit Platzhaltern füllen können. Beispielsweise landen der Doctype und der
542 HTML-Rahmen in der Datei <tt class="docutils literal"><span class="pre">templates/layout.html</span></tt>:</p>
543 <div class="syntax"><pre><span class="cp">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01//EN&quot;</span>
544 <span class="cp"> &quot;http://www.w3.org/TR/html4/strict.dtd&quot;&gt;</span>
545 <span class="nt">&lt;html&gt;</span>
546 <span class="nt">&lt;head&gt;</span>
547 <span class="nt">&lt;title&gt;</span>Shorty<span class="nt">&lt;/title&gt;</span>
548 <span class="nt">&lt;/head&gt;</span>
549 <span class="nt">&lt;body&gt;</span>
550 <span class="nt">&lt;h1&gt;&lt;a</span> <span class="na">href=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">&#39;new&#39;</span><span class="o">)</span> <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;</span>Shorty<span class="nt">&lt;/a&gt;&lt;/h1&gt;</span>
551 <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">&quot;body&quot;</span><span class="nt">&gt;</span><span class="cp">{%</span> <span class="k">block</span> <span class="nv">body</span> <span class="cp">%}{%</span> <span class="k">endblock</span> <span class="cp">%}</span><span class="nt">&lt;/div&gt;</span>
552 <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">&quot;footer&quot;</span><span class="nt">&gt;</span>
553 <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">&#39;new&#39;</span><span class="o">)</span> <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;</span>Neu<span class="nt">&lt;/a&gt;</span> |
554 <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">&#39;list&#39;</span><span class="o">)</span> <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;</span>Liste<span class="nt">&lt;/a&gt;</span> |
555 Benutze Shorty für Gutes, nicht für Böses
556 <span class="nt">&lt;/div&gt;</span>
557 <span class="nt">&lt;/body&gt;</span>
558 <span class="nt">&lt;/html&gt;</span>
559 </pre></div>
560 <p>Von diesem Template können wir in unserer <tt class="docutils literal"><span class="pre">templates/new.html</span></tt> erben:</p>
561 <div class="syntax"><pre><span class="cp">{%</span> <span class="k">extends</span> <span class="s1">&#39;layout.html&#39;</span> <span class="cp">%}</span>
562 <span class="cp">{%</span> <span class="k">block</span> <span class="nv">body</span> <span class="cp">%}</span>
563 <span class="nt">&lt;h2&gt;</span>Erstelle eine Shorty-URL!<span class="nt">&lt;/h2&gt;</span>
564 <span class="cp">{%</span> <span class="k">if</span> <span class="nv">error</span> <span class="cp">%}</span><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">&quot;error&quot;</span><span class="nt">&gt;</span><span class="cp">{{</span> <span class="nv">error</span> <span class="cp">}}</span><span class="nt">&lt;/div&gt;</span><span class="cp">{%</span> <span class="k">endif</span> -<span class="cp">%}</span>
565 <span class="nt">&lt;form</span> <span class="na">action=</span><span class="s">&quot;&quot;</span> <span class="na">method=</span><span class="s">&quot;post&quot;</span><span class="nt">&gt;</span>
566 <span class="nt">&lt;p&gt;</span>Gebe die URL an, die du kürzen willst<span class="nt">&lt;/p&gt;</span>
567 <span class="nt">&lt;p&gt;&lt;input</span> <span class="na">type=</span><span class="s">&quot;text&quot;</span> <span class="na">name=</span><span class="s">&quot;url&quot;</span> <span class="na">id=</span><span class="s">&quot;url&quot;</span> <span class="na">value=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">url</span><span class="o">|</span><span class="nf">e</span><span class="o">(</span><span class="kp">true</span><span class="o">)</span> <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;&lt;/p&gt;</span>
568 <span class="nt">&lt;p&gt;</span>Optional kannst du der URL einen merkbaren Namen geben<span class="nt">&lt;/p&gt;</span>
569 <span class="nt">&lt;p&gt;&lt;input</span> <span class="na">type=</span><span class="s">&quot;text&quot;</span> <span class="na">id=</span><span class="s">&quot;alias&quot;</span> <span class="na">name=</span><span class="s">&quot;alias&quot;</span><span class="nt">&gt;</span><span class="c">{#</span>
570 <span class="c"> #}</span><span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">&quot;submit&quot;</span> <span class="na">id=</span><span class="s">&quot;submit&quot;</span> <span class="na">value=</span><span class="s">&quot;Mach!&quot;</span><span class="nt">&gt;&lt;/p&gt;</span>
571 <span class="nt">&lt;p&gt;&lt;input</span> <span class="na">type=</span><span class="s">&quot;checkbox&quot;</span> <span class="na">name=</span><span class="s">&quot;private&quot;</span> <span class="na">id=</span><span class="s">&quot;private&quot;</span><span class="nt">&gt;</span>
572 <span class="nt">&lt;label</span> <span class="na">for=</span><span class="s">&quot;private&quot;</span><span class="nt">&gt;</span>mache diese URL privat, also zeige sie
573 nicht auf der Liste<span class="nt">&lt;/label&gt;&lt;/p&gt;</span>
574 <span class="nt">&lt;/form&gt;</span>
575 <span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
576 </pre></div>
577 <p>Wenn du dich über den Kommentar zwischen den beiden <cite>input</cite>-Elementen wunderst,
578 das ist ein sauberer Trick, die Templates sauber zu halten ohne Leerzeichen
579 zwischen beide Elemente zu setzen. Wir haben ein Stylesheet vorbereitet,
580 welches dort keine Leerzeichen erwartet.</p>
581 <p>Ein zweites Template für die Anzeige-Seite (<tt class="docutils literal"><span class="pre">templates/display.html</span></tt>):</p>
582 <div class="syntax"><pre><span class="cp">{%</span> <span class="k">extends</span> <span class="s1">&#39;layout.html&#39;</span> <span class="cp">%}</span>
583 <span class="cp">{%</span> <span class="k">block</span> <span class="nv">body</span> <span class="cp">%}</span>
584 <span class="nt">&lt;h2&gt;</span>Verkürzte URL<span class="nt">&lt;/h2&gt;</span>
585 <span class="nt">&lt;p&gt;</span>
586 Die URL <span class="cp">{{</span> <span class="nv">url.target</span><span class="o">|</span><span class="nf">urlize</span><span class="o">(</span><span class="m">40</span><span class="o">,</span> <span class="kp">true</span><span class="o">)</span> <span class="cp">}}</span>
587 wurde gekürzt zu <span class="cp">{{</span> <span class="nv">url.short_url</span><span class="o">|</span><span class="nf">urlize</span> <span class="cp">}}</span>.
588 <span class="nt">&lt;/p&gt;</span>
589 <span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
590 </pre></div>
591 <p>Jinjas <cite>urlize</cite>-Filter übersetzt eine URL in einem Text in einen klickbaren
592 Link. Wenn du ihm einen Integer übergibst, wird er den angezeigten Text auf
593 diese Anzahl Zeichen kürzen; wenn du <cite>true</cite> als zweiten Parameter übergibst,
594 wird ein <cite>nofollow</cite>-Flag hinzugefügt.</p>
595 <p>Da wir jetzt unsere ersten beiden Templates fertig haben, ist es Zeit, den
596 Server zu starten und auf den Teil der Anwendung zu schauen, der bereits
597 funktioniert: neue URLs hinzufügen und weitergeleitet werden.</p>
598 </div>
599 <div class="section">
600 <h3 id="zwischenschritt-das-design-hinzuf-gen">Zwischenschritt: Das Design hinzufügen</h3>
601 <p>Jetzt ist es Zeit, etwas anderes zu machen: ein Design hinzufügen.
602 Designelemente sind normalerweise in statischen CSS-Stylesheets definiert.
603 Also müssen wir einige statische Dateien irgendwo ablegen &#8211; aber das ist ein
604 wenig kompliziert. Wenn du mit bis jetzt mit PHP gearbeitet hast, wirst du
605 gemerkt haben, dass es hier nichts gibt, was eine URL zum Dateisystempfad
606 übersetzt und so direkt auf statische Dateien zugreift. Du musst dem Webserver
607 oder unserem Entwicklungsserver explizit sagen, dass es einen Pfad gibt, der
608 die statischen Dateien beinhaltet.</p>
609 <p>Django empfiehlt eine separate Subdomain und einen eigenen Server für die
610 statischen Dateien, was eine sehr gute Idee für Umgebungen mit hoher Serverlast
611 ist, aber zu viel des Guten für diese simple Anwendung.</p>
612 <p>Hier also folgendes Vorgehen: Wir lassen unsere Anwendung die statischen Dateien
613 ausliefern, aber im Produktionsmodus solltest du dem Apachen mitteilen, dass er
614 diese Dateien selbst ausliefern soll. Das geschieht mit Hilfe der
615 <cite>Alias</cite>-Direktive in der Konfiguration von Apache:</p>
616 <div class="syntax"><pre><span class="nb">Alias</span> <span class="sx">/static</span> <span class="sx">/path/to/static/files</span>
617 </pre></div>
618 <p>Das ist um einiges schneller.</p>
619 <p>Und wie sagen wir unserer Anwendung, dass sie den Ordner mit statischen Dateien
620 als <tt class="docutils literal"><span class="pre">/static</span></tt> verfügbar machen soll? Glücklicherweise ist das ziemlich
621 einfach, da Werkzeug dafür eine WSGI-Middleware liefert. Es gibt zwei
622 Möglichkeiten, diese zu integrieren: Entweder wrappen wir die ganze Anwendung
623 in diese Middleware (diesen Weg empfehlen wir wirklich nicht) oder wir wrappen
624 nur die Ausführungsfunktion (viel besser, weil wir die Referenz auf das
625 Anwendungsobjekt nicht verlieren). Also gehen wir zurück zur
626 <tt class="docutils literal"><span class="pre">application.py</span></tt> und passen den Code ein wenig an.</p>
627 <p>Als Erstes musst du einen neuen Import hinzufügen und den Pfad zu den
628 statischen Dateien ermitteln:</p>
629 <div class="syntax"><pre><span class="k">from</span> <span class="nn">os</span> <span class="k">import</span> <span class="n">path</span>
630 <span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">SharedDataMiddleware</span>
631
632 <span class="n">STATIC_PATH</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">__file__</span><span class="p">),</span> <span class="s">&#39;static&#39;</span><span class="p">)</span>
633 </pre></div>
634 <p>Es wäre besser, die Pfad-Manipulation in die <tt class="docutils literal"><span class="pre">utils.py</span></tt>-Datei zu verschieben,
635 weil wir den Template-Pfad bereits dort ermittelt haben. Aber das ist nicht
636 wirklich von Interesse, und wegen der Einfachheit können wir es im
637 Anwendungsmodul lassen.</p>
638 <p>Wie können wir also die Ausführungsfunktion wrappen? Theoretisch müssen wir
639 einfach <tt class="docutils literal"><span class="pre">self.__call__</span> <span class="pre">=</span> <span class="pre">wrap(self.__call__)</span></tt> schreiben, doch leider klappt
640 das so nicht in Python. Es ist aber nicht viel schwieriger: Benenne einfach
641 <cite>__call__</cite> in <cite>dispatch</cite> um und füge eine neue <cite>__call__</cite>-Methode hinzu:</p>
642 <div class="syntax"><pre><span class="k">def</span> <span class="nf">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
643 <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">dispatch</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">)</span>
644 </pre></div>
645 <p>Jetzt können wir in unsere <cite>__init__</cite>-Funktion gehen und die Middleware
646 zuschalten, indem wir die <cite>dispatch</cite>-Methode einschieben:</p>
647 <div class="syntax"><pre><span class="bp">self</span><span class="o">.</span><span class="n">dispatch</span> <span class="o">=</span> <span class="n">SharedDataMiddleware</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">dispatch</span><span class="p">,</span> <span class="p">{</span>
648 <span class="s">&#39;/static&#39;</span><span class="p">:</span> <span class="n">STATIC_PATH</span>
649 <span class="p">})</span>
650 </pre></div>
651 <p>Das war jetzt nicht schwer. Mit diesem Weg können wir WSGI-Middlewares in der
652 Anwendungsklasse einhaken!</p>
653 <p>Eine andere gute Idee ist es, unserer <cite>url_map</cite> im <cite>utils</cite>-Modul den Ort
654 unserer statischen Dateien mitzuteilen, indem wir eine Regel hinzufügen. Auf
655 diesem Weg können wir URLs zu den statischen Dateien in den Templates
656 generieren:</p>
657 <div class="syntax"><pre><span class="n">url_map</span> <span class="o">=</span> <span class="n">Map</span><span class="p">([</span><span class="n">Rule</span><span class="p">(</span><span class="s">&#39;/static/&lt;file&gt;&#39;</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="s">&#39;static&#39;</span><span class="p">,</span> <span class="n">build_only</span><span class="o">=</span><span class="bp">True</span><span class="p">)])</span>
658 </pre></div>
659 <p>Jetzt können wir unsere <tt class="docutils literal"><span class="pre">templates/layout.html</span></tt>-Datei wieder öffnen und einen
660 Link zum <tt class="docutils literal"><span class="pre">style.css</span></tt>-Stylesheet hinzufügen, welches wir danach erstellen
661 werden:</p>
662 <div class="syntax"><pre><span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">&quot;stylesheet&quot;</span> <span class="na">type=</span><span class="s">&quot;text/css&quot;</span> <span class="na">href=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">&#39;static&#39;</span><span class="o">,</span>
663 <span class="nv">file</span><span class="o">=</span><span class="s1">&#39;style.css&#39;</span><span class="o">)</span> <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;</span>
664 </pre></div>
665 <p>Das geht natürlich in den <cite>&lt;head&gt;</cite>-Tag, wo zur Zeit nur der Titel festgelegt
666 ist.</p>
667 <p>Du kannst jetzt ein nettes Layout gestalten oder das <a class="reference" href="http://dev.pocoo.org/projects/werkzeug/browser/examples/shorty/static/style.css">Beispiel-Stylesheet</a>
668 nutzen. In beiden Fällen musst du es in die Datei <tt class="docutils literal"><span class="pre">static/style.css</span></tt>
669 einfügen.</p>
670 </div>
671 <div class="section">
672 <h3 id="teil-6-ffentliche-urls-auflisten">Teil 6: Öffentliche URLs auflisten</h3>
673 <p>Jetzt wollen wir alle öffentlichen URLs auf der &#8220;List&#8221;-Seite auflisten. Das
674 sollte kein großes Problem sein, aber wir wollen auch eine Art Seitenumbruch
675 haben. Da wir alle URLs auf einmal ausgeben, haben wir früher oder später
676 eine endlose Seite, die Minuten zum Laden benötigt.</p>
677 <p>Beginnen wir also mit dem Hinzufügen einer <cite>Pagination</cite>-Klasse zu unserem
678 <cite>utils</cite>-Modul:</p>
679 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">cached_property</span>
680
681 <span class="k">class</span> <span class="nc">Pagination</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
682
683 <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">query</span><span class="p">,</span> <span class="n">per_page</span><span class="p">,</span> <span class="n">page</span><span class="p">,</span> <span class="n">endpoint</span><span class="p">):</span>
684 <span class="bp">self</span><span class="o">.</span><span class="n">query</span> <span class="o">=</span> <span class="n">query</span>
685 <span class="bp">self</span><span class="o">.</span><span class="n">per_page</span> <span class="o">=</span> <span class="n">per_page</span>
686 <span class="bp">self</span><span class="o">.</span><span class="n">page</span> <span class="o">=</span> <span class="n">page</span>
687 <span class="bp">self</span><span class="o">.</span><span class="n">endpoint</span> <span class="o">=</span> <span class="n">endpoint</span>
688
689 <span class="nd">@cached_property</span>
690 <span class="k">def</span> <span class="nf">count</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
691 <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">count</span><span class="p">()</span>
692
693 <span class="nd">@cached_property</span>
694 <span class="k">def</span> <span class="nf">entries</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
695 <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">offset</span><span class="p">((</span><span class="bp">self</span><span class="o">.</span><span class="n">page</span> <span class="o">-</span> <span class="mf">1</span><span class="p">)</span> <span class="o">*</span> <span class="bp">self</span><span class="o">.</span><span class="n">per_page</span><span class="p">)</span> \
696 <span class="o">.</span><span class="n">limit</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">per_page</span><span class="p">)</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
697
698 <span class="n">has_previous</span> <span class="o">=</span> <span class="nb">property</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="o">.</span><span class="n">page</span> <span class="o">&gt;</span> <span class="mf">1</span><span class="p">)</span>
699 <span class="n">has_next</span> <span class="o">=</span> <span class="nb">property</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="o">.</span><span class="n">page</span> <span class="o">&lt;</span> <span class="n">x</span><span class="o">.</span><span class="n">pages</span><span class="p">)</span>
700 <span class="n">previous</span> <span class="o">=</span> <span class="nb">property</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">url_for</span><span class="p">(</span><span class="n">x</span><span class="o">.</span><span class="n">endpoint</span><span class="p">,</span> <span class="n">page</span><span class="o">=</span><span class="n">x</span><span class="o">.</span><span class="n">page</span> <span class="o">-</span> <span class="mf">1</span><span class="p">))</span>
701 <span class="n">next</span> <span class="o">=</span> <span class="nb">property</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">url_for</span><span class="p">(</span><span class="n">x</span><span class="o">.</span><span class="n">endpoint</span><span class="p">,</span> <span class="n">page</span><span class="o">=</span><span class="n">x</span><span class="o">.</span><span class="n">page</span> <span class="o">+</span> <span class="mf">1</span><span class="p">))</span>
702 <span class="n">pages</span> <span class="o">=</span> <span class="nb">property</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="nb">max</span><span class="p">(</span><span class="mf">0</span><span class="p">,</span> <span class="n">x</span><span class="o">.</span><span class="n">count</span> <span class="o">-</span> <span class="mf">1</span><span class="p">)</span> <span class="o">//</span> <span class="n">x</span><span class="o">.</span><span class="n">per_page</span> <span class="o">+</span> <span class="mf">1</span><span class="p">)</span>
703 </pre></div>
704 <p>Dies ist eine sehr einfache Klasse, die das meiste der Seitenumbrüche für uns
705 übernimmt. Wir können ihr eine unausgeführte SQLAlchemy-Abfrage (Query)
706 übergeben, die Anzahl der Elemente pro Seite, die aktuelle Seite und den
707 Endpoint, welcher für die URL-Generation benutzt wird. Der
708 <cite>cached_property</cite>-Dekorator funktioniert, wie du siehst, fast genau so wie der
709 normale <cite>property</cite>-Dekorator, mit der Ausnahme, dass er sich das Ergebnis merkt.
710 Wir werden diese Klasse nicht genau besprechen, aber das Grundprinzip ist, dass
711 ein Zugriff auf <cite>pagination.entries</cite> die Elemente für die aktuelle Seite ausgibt
712 und dass die anderen Eigenschaften Werte zurückgeben, sodass wir sie im Template
713 nutzen können.</p>
714 <p>Jetzt können wir die <cite>Pagination</cite>-Klasse in unser Views-Modul importieren und
715 etwas Code zu der <cite>list</cite>-Funktion hinzufügen:</p>
716 <div class="syntax"><pre><span class="k">from</span> <span class="nn">shorty.utils</span> <span class="k">import</span> <span class="n">Pagination</span>
717
718 <span class="nd">@expose</span><span class="p">(</span><span class="s">&#39;/list/&#39;</span><span class="p">,</span> <span class="n">defaults</span><span class="o">=</span><span class="p">{</span><span class="s">&#39;page&#39;</span><span class="p">:</span> <span class="mf">1</span><span class="p">})</span>
719 <span class="nd">@expose</span><span class="p">(</span><span class="s">&#39;/list/&lt;int:page&gt;&#39;</span><span class="p">)</span>
720 <span class="k">def</span> <span class="nf">list</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">page</span><span class="p">):</span>
721 <span class="n">query</span> <span class="o">=</span> <span class="n">URL</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">filter_by</span><span class="p">(</span><span class="n">public</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
722 <span class="n">pagination</span> <span class="o">=</span> <span class="n">Pagination</span><span class="p">(</span><span class="n">query</span><span class="p">,</span> <span class="mf">30</span><span class="p">,</span> <span class="n">page</span><span class="p">,</span> <span class="s">&#39;list&#39;</span><span class="p">)</span>
723 <span class="k">if</span> <span class="n">pagination</span><span class="o">.</span><span class="n">page</span> <span class="o">&gt;</span> <span class="mf">1</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">pagination</span><span class="o">.</span><span class="n">entries</span><span class="p">:</span>
724 <span class="k">raise</span> <span class="n">NotFound</span><span class="p">()</span>
725 <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">&#39;list.html&#39;</span><span class="p">,</span> <span class="n">pagination</span><span class="o">=</span><span class="n">pagination</span><span class="p">)</span>
726 </pre></div>
727 <p>Die If-Bedingung in dieser Funktion versichert, dass ein Statuscode 404
728 zurückgegeben wird, wenn wir nicht auf der ersten Seite sind und es keine
729 Einträge zum Anzeigen gibt (einen Aufruf von <tt class="docutils literal"><span class="pre">/list/42</span></tt> ohne Einträge auf
730 dieser Seite nicht mit einem 404 zu quittieren, wäre schlechter Stil).</p>
731 <p>Und schließlich das Template:</p>
732 <div class="syntax"><pre><span class="cp">{%</span> <span class="k">extends</span> <span class="s1">&#39;layout.html&#39;</span> <span class="cp">%}</span>
733 <span class="cp">{%</span> <span class="k">block</span> <span class="nv">body</span> <span class="cp">%}</span>
734 <span class="nt">&lt;h2&gt;</span>URL Liste<span class="nt">&lt;/h2&gt;</span>
735 <span class="nt">&lt;ul&gt;</span>
736 <span class="cp">{%</span>- <span class="k">for</span> <span class="nv">url</span> <span class="k">in</span> <span class="nv">pagination.entries</span> <span class="cp">%}</span>
737 <span class="nt">&lt;li&gt;&lt;a</span> <span class="na">href=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">url.short_url</span><span class="o">|</span><span class="nf">e</span> <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;</span><span class="cp">{{</span> <span class="nv">url.uid</span><span class="o">|</span><span class="nf">e</span> <span class="cp">}}</span><span class="nt">&lt;/a&gt;</span> <span class="ni">&amp;raquo;</span>
738 <span class="nt">&lt;small&gt;</span><span class="cp">{{</span> <span class="nv">url.target</span><span class="o">|</span><span class="nf">urlize</span><span class="o">(</span><span class="m">38</span><span class="o">,</span> <span class="kp">true</span><span class="o">)</span> <span class="cp">}}</span><span class="nt">&lt;/small&gt;&lt;/li&gt;</span>
739 <span class="cp">{%</span>- <span class="k">else</span> <span class="cp">%}</span>
740 <span class="nt">&lt;li&gt;&lt;em&gt;</span>keine URLs bis jetzt gekürzt<span class="nt">&lt;/em&gt;&lt;/li&gt;</span>
741 <span class="cp">{%</span>- <span class="k">endfor</span> <span class="cp">%}</span>
742 <span class="nt">&lt;/ul&gt;</span>
743 <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">&quot;pagination&quot;</span><span class="nt">&gt;</span>
744 <span class="cp">{%</span>- <span class="k">if</span> <span class="nv">pagination.has_previous</span> <span class="cp">%}</span><span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">pagination.previous</span>
745 <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;</span><span class="ni">&amp;laquo;</span> Vorherige<span class="nt">&lt;/a&gt;</span>
746 <span class="cp">{%</span>- <span class="k">else</span> <span class="cp">%}</span><span class="nt">&lt;span</span> <span class="na">class=</span><span class="s">&quot;inactive&quot;</span><span class="nt">&gt;</span><span class="ni">&amp;laquo;</span> Vorherige<span class="nt">&lt;/span&gt;</span><span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span>
747 | <span class="cp">{{</span> <span class="nv">pagination.page</span> <span class="cp">}}</span> |
748 <span class="cp">{%</span> <span class="k">if</span> <span class="nv">pagination.has_next</span> <span class="cp">%}</span><span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">pagination.next</span>
749 <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;</span>Nächste <span class="ni">&amp;raquo;</span><span class="nt">&lt;/a&gt;</span>
750 <span class="cp">{%</span>- <span class="k">else</span> <span class="cp">%}</span><span class="nt">&lt;span</span> <span class="na">class=</span><span class="s">&quot;inactive&quot;</span><span class="nt">&gt;</span>Nächste <span class="ni">&amp;raquo;</span><span class="nt">&lt;/span&gt;</span><span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span>
751 <span class="nt">&lt;/div&gt;</span>
752 <span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
753 </pre></div>
754 </div>
755 <div class="section">
756 <h3 id="bonus-404-fehlerseiten-gestalten">Bonus: 404-Fehlerseiten gestalten</h3>
757 <p>Jetzt, da wir unsere Anwendung fertig gestellt haben, können wir kleine
758 Verbesserungen vornehmen, zum Beispiel eigene 404-Fehlerseiten. Das ist
759 ziemlich einfach. Das Erste, was wir machen müssen, ist eine neue Funktion
760 namens <cite>not_found</cite> in den Views zu erstellen, welche ein Template ausgibt:</p>
761 <div class="syntax"><pre><span class="k">def</span> <span class="nf">not_found</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
762 <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">&#39;not_found.html&#39;</span><span class="p">)</span>
763 </pre></div>
764 <p>Dann müssen wir in unser Anwendungsmodul wechseln und die <cite>NotFound</cite>-Exception
765 importieren:</p>
766 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug.exceptions</span> <span class="k">import</span> <span class="n">NotFound</span>
767 </pre></div>
768 <p>Schließlich müssen wir sie auffangen und in eine Response umwandeln. Dieser
769 except-Block kommt <strong>vor</strong> den except-Bock mit <cite>HTTPException</cite>:</p>
770 <div class="syntax"><pre><span class="k">try</span><span class="p">:</span>
771 <span class="c"># das bleibt das gleiche</span>
772 <span class="k">except</span> <span class="n">NotFound</span><span class="p">,</span> <span class="n">e</span><span class="p">:</span>
773 <span class="n">response</span> <span class="o">=</span> <span class="n">views</span><span class="o">.</span><span class="n">not_found</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
774 <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mf">404</span>
775 <span class="k">except</span> <span class="n">HTTPException</span><span class="p">,</span> <span class="n">e</span><span class="p">:</span>
776 <span class="c"># das bleibt das gleiche</span>
777 </pre></div>
778 <p>Jetzt noch <tt class="docutils literal"><span class="pre">templates/not_found.html</span></tt> hinzufügen und du bist fertig:</p>
779 <div class="syntax"><pre><span class="cp">{%</span> <span class="k">extends</span> <span class="s1">&#39;layout.html&#39;</span> <span class="cp">%}</span>
780 <span class="cp">{%</span> <span class="k">block</span> <span class="nv">body</span> <span class="cp">%}</span>
781 <span class="nt">&lt;h2&gt;</span>Seite nicht gefunden<span class="nt">&lt;/h2&gt;</span>
782 <span class="nt">&lt;p&gt;</span>
783 Die aufgerufene Seite existiert nicht auf dem Server. Vielleicht
784 willst du eine <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">&quot;</span><span class="cp">{{</span> <span class="nv">url_for</span><span class="o">(</span><span class="s1">&#39;new&#39;</span><span class="o">)</span> <span class="cp">}}</span><span class="s">&quot;</span><span class="nt">&gt;</span>neue URL
785 hinzufügen<span class="nt">&lt;/a&gt;</span>?
786 <span class="nt">&lt;/p&gt;</span>
787 <span class="cp">{%</span> <span class="k">endblock</span> <span class="cp">%}</span>
788 </pre></div>
789 </div>
790 <div class="section">
791 <h3 id="abschluss">Abschluss</h3>
792 <p>Dieses Tutorial behandelt alles, was du brauchst, um mit Werkzeug, SQLAlchemy
793 und Jinja anzufangen und sollte dir helfen, die beste Lösung für deine Anwendung
794 zu finden. Für einige größere Beispiele, die außerdem einen anderen Aufbau und
795 Ideen zum Ausführen benutzen, solltest du mal einen Blick auf den
796 <a class="reference" href="http://dev.pocoo.org/projects/werkzeug/browser/examples">Beispielordner</a> werfen.</p>
797 <p>In diesem Sinne: Viel Spaß mit Werkzeug!</p>
798 </div>
799
800 <div style="clear:both"></div>
801 </div>
802 <div class="footer">
803 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
804 BSD license.
805 </div>
806 </div>
807 </body>
808 </html>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Unicode // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Unicode</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#unicode-in-python" id="id1" name="id1">Unicode in Python</a></li>
22 <li><a class="reference" href="#unicode-in-http" id="id2" name="id2">Unicode in HTTP</a></li>
23 <li><a class="reference" href="#error-handling" id="id3" name="id3">Error Handling</a></li>
24 <li><a class="reference" href="#request-and-response-objects" id="id4" name="id4">Request and Response Objects</a></li>
25 </ul>
26 </div>
27
28 <p>Since early Python 2 days unicode was part of all default Python builds. It
29 allows developers to write applications that deal with non-ASCII characters
30 in a straightforward way. But working with unicode requires a basic knowledge
31 about that matter, especially when working with libraries that do not support
32 it.</p>
33 <p>Werkzeug uses unicode internally everywhere text data is assumed, even if the
34 HTTP standard is not unicode aware as it. Basically all incoming data is
35 decoded from the charset specified (per default <cite>utf-8</cite>) so that you don&#8217;t
36 operate on bytestrings any more. Outgoing unicode data is then encoded into
37 the target charset again.</p>
38 <div class="section">
39 <h3 id="unicode-in-python">Unicode in Python</h3>
40 <p>In Python 2 there are two basic string types: <cite>str</cite> and <cite>unicode</cite>. <cite>str</cite> may
41 carry encoded unicode data but it&#8217;s always represented in bytes whereas the
42 <cite>unicode</cite> type does not contain bytes but charpoints. What does this mean?
43 Imagine you have the German Umlaut <cite>ö</cite>. In ASCII you cannot represent that
44 character, but in the <cite>latin-1</cite> and <cite>utf-8</cite> character sets you can represent
45 it, but they look differently when encoded:</p>
46 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="s">u&#39;ö&#39;</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s">&#39;latin1&#39;</span><span class="p">)</span>
47 <span class="go">&#39;\xf6&#39;</span>
48 <span class="gp">&gt;&gt;&gt; </span><span class="s">u&#39;ö&#39;</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s">&#39;utf-8&#39;</span><span class="p">)</span>
49 <span class="go">&#39;\xc3\xb6&#39;</span>
50 </pre></div>
51 <p>So an <cite>ö</cite> might look totally different depending on the encoding which makes
52 it hard to work with it. The solution is using the <cite>unicode</cite> type (as we did
53 above, note the <cite>u</cite> prefix before the string). The unicode type does not
54 store the bytes for <cite>ö</cite> but the information, that this is a
55 <tt class="docutils literal"><span class="pre">LATIN</span> <span class="pre">SMALL</span> <span class="pre">LETTER</span> <span class="pre">O</span> <span class="pre">WITH</span> <span class="pre">DIAERESIS</span></tt>.</p>
56 <p>Doing <tt class="docutils literal"><span class="pre">len(u'ö')</span></tt> will always give us the expected &#8220;1&#8221; but <tt class="docutils literal"><span class="pre">len('ö')</span></tt>
57 might give different results depending on the encoding of <tt class="docutils literal"><span class="pre">'ö'</span></tt>.</p>
58 </div>
59 <div class="section">
60 <h3 id="unicode-in-http">Unicode in HTTP</h3>
61 <p>The problem with unicode is that HTTP does not know what unicode is. HTTP
62 is limited to bytes but this is not a big problem as Werkzeug decodes and
63 encodes for us automatically all incoming and outgoing data. Basically what
64 this means is that data sent from the browser to the web application is per
65 default decoded from an utf-8 bytestring into a <cite>unicode</cite> string. Data sent
66 from the application back to the browser that is not yet a bytestring is then
67 encoded back to utf-8.</p>
68 <p>Usually this &#8220;just works&#8221; and we don&#8217;t have to worry about it, but there are
69 situations where this behavior is problematic. For example the Python 2 IO
70 layer is not unicode aware. This means that whenever you work with data from
71 the file system you have to properly decode it. The correct way to load
72 a text file from the file system looks like this:</p>
73 <div class="syntax"><pre><span class="n">f</span> <span class="o">=</span> <span class="nb">file</span><span class="p">(</span><span class="s">&#39;/path/to/the_file.txt&#39;</span><span class="p">,</span> <span class="s">&#39;r&#39;</span><span class="p">)</span>
74 <span class="k">try</span><span class="p">:</span>
75 <span class="n">text</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s">&#39;utf-8&#39;</span><span class="p">)</span> <span class="c"># assuming the file is utf-8 encoded</span>
76 <span class="k">finally</span><span class="p">:</span>
77 <span class="n">f</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
78 </pre></div>
79 <p>There is also the codecs module which provides an open function that decodes
80 automatically from the given encoding.</p>
81 </div>
82 <div class="section">
83 <h3 id="error-handling">Error Handling</h3>
84 <p>With Werkzeug 0.3 onwards you can further control the way Werkzeug works with
85 unicode. In the past Werkzeug ignored encoding errors silently on incoming
86 data. This decision was made to avoid internal server errors if the user
87 tampered with the submitted data. However there are situations where you
88 want to abort with a <cite>400 BAD REQUEST</cite> instead of silently ignoring the error.</p>
89 <p>All the functions that do internal decoding now accept an <cite>errors</cite> keyword
90 argument that behaves like the <cite>errors</cite> parameter of the builtin string method
91 <cite>decode</cite>. The following values are possible:</p>
92 <dl>
93 <dt><cite>ignore</cite></dt>
94 <dd>This is the default behavior and tells the codec to ignore characters that
95 it doesn&#8217;t understand silently.</dd>
96 <dt><cite>replace</cite></dt>
97 <dd>The codec will replace unknown characters with a replacement character
98 (<cite>U+FFFD</cite> <tt class="docutils literal"><span class="pre">REPLACEMENT</span> <span class="pre">CHARACTER</span></tt>)</dd>
99 <dt><cite>strict</cite></dt>
100 <dd>Raise an exception if decoding fails.</dd>
101 </dl>
102 <p>Unlike the regular python decoding Werkzeug does not raise an
103 <cite>UnicodeDecodeError</cite> if the decoding failed but an <cite>HTTPUnicodeError</cite> which
104 is a direct subclass of <cite>UnicodeError</cite> and the <cite>BadRequest</cite> HTTP exception.
105 The reason is that if this exception is not caught by the application but
106 a catch-all for HTTP exceptions exists a default <cite>400 BAD REQUEST</cite> error
107 page is displayed.</p>
108 <p>There is additional error handling available which is a Werkzeug extension
109 to the regular codec error handling which is called <cite>fallback</cite>. Often you
110 want to use utf-8 but support latin1 as legacy encoding too if decoding
111 failed. For this case you can use the <cite>fallback</cite> error handling. For
112 example you can specify <tt class="docutils literal"><span class="pre">'fallback:iso-8859-15'</span></tt> to tell Werkzeug it should
113 try with <cite>iso-8859-15</cite> if <cite>utf-8</cite> failed. If this decoding fails too (which
114 should not happen for most legacy charsets such as <cite>iso-8859-15</cite>) the error
115 is silently ignored as if the error handling was <cite>ignore</cite>.</p>
116 <p>Further details are available as part of the API documentation of the concrete
117 implementations of the functions or classes working with unicode.</p>
118 </div>
119 <div class="section">
120 <h3 id="request-and-response-objects">Request and Response Objects</h3>
121 <p>As request and response objects usually are the central entities of Werkzeug
122 powered applications you can change the default encoding Werkzeug operates on
123 by subclassing these two classes. For example you can easily set the
124 application to utf-7 and strict error handling:</p>
125 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">BaseRequest</span><span class="p">,</span> <span class="n">BaseResponse</span>
126
127 <span class="k">class</span> <span class="nc">Request</span><span class="p">(</span><span class="n">BaseRequest</span><span class="p">):</span>
128 <span class="n">charset</span> <span class="o">=</span> <span class="s">&#39;utf-7&#39;</span>
129 <span class="n">encoding_errors</span> <span class="o">=</span> <span class="s">&#39;strict&#39;</span>
130
131 <span class="k">class</span> <span class="nc">Response</span><span class="p">(</span><span class="n">BaseResponse</span><span class="p">):</span>
132 <span class="n">charset</span> <span class="o">=</span> <span class="s">&#39;utf-7&#39;</span>
133 </pre></div>
134 <p>Keep in mind that the error handling is only customizable for all decoding
135 but not encoding. If Werkzeug encounters an encoding error it will raise a
136 <cite>UnicodeEncodeError</cite>. It&#8217;s your responsibility to not create data that is
137 not present in the target charset (a non issue with all unicode encodings
138 such as utf-8).</p>
139 </div>
140
141 <div style="clear:both"></div>
142 </div>
143 <div class="footer">
144 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
145 BSD license.
146 </div>
147 </div>
148 </body>
149 </html>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Utilities // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Utilities</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#data-structures" id="id1" name="id1">Data Structures</a></li>
22 <li><a class="reference" href="#working-with-http-headers" id="id2" name="id2">Working with HTTP Headers</a></li>
23 <li><a class="reference" href="#url-helpers" id="id3" name="id3">URL Helpers</a></li>
24 <li><a class="reference" href="#html-helpers" id="id4" name="id4">HTML Helpers</a></li>
25 <li><a class="reference" href="#wsgi-helpers" id="id5" name="id5">WSGI Helpers</a></li>
26 <li><a class="reference" href="#helper-functions" id="id6" name="id6">Helper Functions</a></li>
27 </ul>
28 </div>
29
30 <p>Werkzeug comes with a bunch of utilties that can be useful for WSGI
31 applications. Most of the classes provided by this module are used by the
32 wrappers, you can however use them without the wrappers, too.</p>
33 <p>All the utilities can be directly imported from the <cite>werkzeug</cite> module.</p>
34 <div class="section">
35 <h3 id="data-structures">Data Structures</h3>
36 <dl>
37 <dt><strong>class</strong> <cite id="werkzeug.utils.MultiDict">MultiDict</cite></dt>
38 <dd><p class="first">A <cite>MultiDict</cite> is a dictionary subclass customized to deal with multiple
39 values for the same key which is for example used by the parsing functions
40 in the wrappers. This is necessary because some HTML form elements pass
41 multiple values for the same key.</p>
42 <p><cite>MultiDict</cite> implements the all standard dictionary methods. Internally,
43 it saves all values for a key as a list, but the standard dict access
44 methods will only return the first value for a key. If you want to gain
45 access to the other values too you have to use the <cite>list</cite> methods as
46 explained below.</p>
47 <p>Basic Usage:</p>
48 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">d</span> <span class="o">=</span> <span class="n">MultiDict</span><span class="p">([(</span><span class="s">&#39;a&#39;</span><span class="p">,</span> <span class="s">&#39;b&#39;</span><span class="p">),</span> <span class="p">(</span><span class="s">&#39;a&#39;</span><span class="p">,</span> <span class="s">&#39;c&#39;</span><span class="p">)])</span>
49 <span class="gp">&gt;&gt;&gt; </span><span class="n">d</span>
50 <span class="go">MultiDict([(&#39;a&#39;, &#39;b&#39;), (&#39;a&#39;, &#39;c&#39;)])</span>
51 <span class="gp">&gt;&gt;&gt; </span><span class="n">d</span><span class="p">[</span><span class="s">&#39;a&#39;</span><span class="p">]</span>
52 <span class="go">&#39;b&#39;</span>
53 <span class="gp">&gt;&gt;&gt; </span><span class="n">d</span><span class="o">.</span><span class="n">getlist</span><span class="p">(</span><span class="s">&#39;a&#39;</span><span class="p">)</span>
54 <span class="go">[&#39;b&#39;, &#39;c&#39;]</span>
55 <span class="gp">&gt;&gt;&gt; </span><span class="s">&#39;a&#39;</span> <span class="ow">in</span> <span class="n">d</span>
56 <span class="go">True</span>
57 </pre></div>
58 <p>It behaves like a normal dict thus all dict functions will only return the
59 first value when multiple values for one key are found.</p>
60 <p>From Werkzeug 0.3 onwards, the <cite>KeyError</cite> raised by this class is also a
61 subclass of the <cite>BadRequest</cite> HTTP exception and will render a page for a
62 <tt class="docutils literal"><span class="pre">400</span> <span class="pre">BAD</span> <span class="pre">REQUEST</span></tt> if catched in a catch-all for HTTP exceptions.</p>
63 <p>The following additional methods exist that are used to get or set the
64 other values for a key or convert:</p>
65 <dl>
66 <dt><cite id="werkzeug.utils.MultiDict.get">get</cite> <tt class="func-signature docutils literal"><span class="pre">(key,</span> <span class="pre">default=None,</span> <span class="pre">type=None)</span></tt></dt>
67 <dd><p class="first">Return the default value if the requested data doesn&#8217;t exist.
68 If <cite>type</cite> is provided and is a callable it should convert the value,
69 return it or raise a <cite>ValueError</cite> if that is not possible. In this
70 case the function will return the default as if the value was not
71 found.</p>
72 <p>Example:</p>
73 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">d</span> <span class="o">=</span> <span class="n">MultiDict</span><span class="p">(</span><span class="n">foo</span><span class="o">=</span><span class="s">&#39;42&#39;</span><span class="p">,</span> <span class="n">bar</span><span class="o">=</span><span class="s">&#39;blub&#39;</span><span class="p">)</span>
74 <span class="gp">&gt;&gt;&gt; </span><span class="n">d</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&#39;foo&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
75 <span class="go">42</span>
76 <span class="gp">&gt;&gt;&gt; </span><span class="n">d</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&#39;bar&#39;</span><span class="p">,</span> <span class="o">-</span><span class="mf">1</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
77 <span class="go">-1</span>
78 </pre></div>
79 </dd>
80 </dl>
81 <dl>
82 <dt><cite id="werkzeug.utils.MultiDict.getlist">getlist</cite> <tt class="func-signature docutils literal"><span class="pre">(key,</span> <span class="pre">type=None)</span></tt></dt>
83 <dd><p class="first">Return the list of items for a given key. If that key is not in the
84 <cite>MultiDict</cite>, the return value will be an empty list. Just as <cite>get</cite>
85 <cite>getlist</cite> accepts a <cite>type</cite> parameter. All items will be converted
86 with the callable defined there.</p>
87 <p class="last"><strong>returns</strong>: list</p>
88 </dd>
89 </dl>
90 <dl>
91 <dt><cite id="werkzeug.utils.MultiDict.setlist">setlist</cite> <tt class="func-signature docutils literal"><span class="pre">(key,</span> <span class="pre">new_list)</span></tt></dt>
92 <dd><p class="first">Remove the old values for a key and add new ones. Note that the list
93 you pass the values in will be shallow-copied before it is inserted in
94 the dictionary.</p>
95 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">multidict</span><span class="o">.</span><span class="n">setlist</span><span class="p">(</span><span class="s">&#39;foo&#39;</span><span class="p">,</span> <span class="p">[</span><span class="s">&#39;1&#39;</span><span class="p">,</span> <span class="s">&#39;2&#39;</span><span class="p">])</span>
96 <span class="gp">&gt;&gt;&gt; </span><span class="n">multidict</span><span class="p">[</span><span class="s">&#39;foo&#39;</span><span class="p">]</span>
97 <span class="go">&#39;1&#39;</span>
98 <span class="gp">&gt;&gt;&gt; </span><span class="n">multidict</span><span class="o">.</span><span class="n">getlist</span><span class="p">(</span><span class="s">&#39;foo&#39;</span><span class="p">)</span>
99 <span class="go">[&#39;1&#39;, &#39;2&#39;]</span>
100 </pre></div>
101 </dd>
102 </dl>
103 <dl>
104 <dt><cite id="werkzeug.utils.MultiDict.setlistdefault">setlistdefault</cite> <tt class="func-signature docutils literal"><span class="pre">(key,</span> <span class="pre">default_list=())</span></tt></dt>
105 <dd>Like <cite>setdefault</cite> but sets multiple values.</dd>
106 </dl>
107 <p>These functions work like the functions with the same name without
108 list. Unlike the regular dict functions those operate on all the
109 values as lists, not only on the first one:</p>
110 <p><cite>lists</cite>, <cite>listvalues</cite>, <cite>iterlists</cite>, <cite>iterlistvalues</cite>, <cite>poplist</cite>,
111 and <cite>popitemlist</cite>.</p>
112 <p class="last">Also notable: <cite>update</cite> adds values and does not replace existing ones.</p>
113 </dd>
114 </dl>
115 <dl>
116 <dt><strong>class</strong> <cite id="werkzeug.utils.CombinedMultiDict">CombinedMultiDict</cite></dt>
117 <dd><p class="first">A read only <cite>MultiDict</cite> decorator that you can pass multiple <cite>MultiDict</cite>
118 instances as sequence and it will combine the return values of all wrapped
119 dicts:</p>
120 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">MultiDict</span><span class="p">,</span> <span class="n">CombinedMultiDict</span>
121 <span class="gp">&gt;&gt;&gt; </span><span class="n">post</span> <span class="o">=</span> <span class="n">MultiDict</span><span class="p">([(</span><span class="s">&#39;foo&#39;</span><span class="p">,</span> <span class="s">&#39;bar&#39;</span><span class="p">)])</span>
122 <span class="gp">&gt;&gt;&gt; </span><span class="n">get</span> <span class="o">=</span> <span class="n">MultiDict</span><span class="p">([(</span><span class="s">&#39;blub&#39;</span><span class="p">,</span> <span class="s">&#39;blah&#39;</span><span class="p">)])</span>
123 <span class="gp">&gt;&gt;&gt; </span><span class="n">combined</span> <span class="o">=</span> <span class="n">CombinedMultiDict</span><span class="p">([</span><span class="n">get</span><span class="p">,</span> <span class="n">post</span><span class="p">])</span>
124 <span class="gp">&gt;&gt;&gt; </span><span class="n">combined</span><span class="p">[</span><span class="s">&#39;foo&#39;</span><span class="p">]</span>
125 <span class="go">&#39;bar&#39;</span>
126 <span class="gp">&gt;&gt;&gt; </span><span class="n">combined</span><span class="p">[</span><span class="s">&#39;blub&#39;</span><span class="p">]</span>
127 <span class="go">&#39;blah&#39;</span>
128 </pre></div>
129 <p>This works for all read operations and will raise a <cite>TypeError</cite> for
130 methods that usually change data which isn&#8217;t possible.</p>
131 <p class="last">From Werkzeug 0.3 onwards, the <cite>KeyError</cite> raised by this class is also a
132 subclass of the <cite>BadRequest</cite> HTTP exception and will render a page for a
133 <tt class="docutils literal"><span class="pre">400</span> <span class="pre">BAD</span> <span class="pre">REQUEST</span></tt> if catched in a catch-all for HTTP exceptions.</p>
134 </dd>
135 </dl>
136 <dl>
137 <dt><strong>class</strong> <cite id="werkzeug.utils.Headers">Headers</cite></dt>
138 <dd><p class="first">An object that stores some headers. It has a dict like interface
139 but is ordered and can store keys multiple times.</p>
140 <p>This data structure is useful if you want a nicer way to handle WSGI
141 headers which are stored as tuples in a list.</p>
142 <p>From Werkzeug 0.3 onwards, the <cite>KeyError</cite> raised by this class is also a
143 subclass of the <cite>BadRequest</cite> HTTP exception and will render a page for a
144 <tt class="docutils literal"><span class="pre">400</span> <span class="pre">BAD</span> <span class="pre">REQUEST</span></tt> if catched in a catch-all for HTTP exceptions.</p>
145 <dl>
146 <dt><cite id="werkzeug.utils.Headers.__init__">__init__</cite> <tt class="func-signature docutils literal"><span class="pre">(defaults=None,</span> <span class="pre">_list=None)</span></tt></dt>
147 <dd>Create a new <cite>Headers</cite> object based on a list or dict of headers
148 which are used as default values. This does not reuse the list passed
149 to the constructor for internal usage. To create a <cite>Headers</cite> object
150 that uses as internal storage the list or list-like object provided
151 it&#8217;s possible to use the <cite>linked</cite> classmethod.</dd>
152 </dl>
153 <dl>
154 <dt><cite id="werkzeug.utils.Headers.linked">linked</cite> <tt class="func-signature docutils literal"><span class="pre">(headerlist)</span></tt></dt>
155 <dd><p class="first">Create a new <cite>Headers</cite> object that uses the list of headers passed
156 as internal storage:</p>
157 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">headerlist</span> <span class="o">=</span> <span class="p">[(</span><span class="s">&#39;Content-Length&#39;</span><span class="p">,</span> <span class="s">&#39;40&#39;</span><span class="p">)]</span>
158 <span class="gp">&gt;&gt;&gt; </span><span class="n">headers</span> <span class="o">=</span> <span class="n">Headers</span><span class="o">.</span><span class="n">linked</span><span class="p">(</span><span class="n">headerlist</span><span class="p">)</span>
159 <span class="gp">&gt;&gt;&gt; </span><span class="n">headers</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="s">&#39;Content-Type&#39;</span><span class="p">,</span> <span class="s">&#39;text/html&#39;</span><span class="p">)</span>
160 <span class="gp">&gt;&gt;&gt; </span><span class="n">headerlist</span>
161 <span class="go">[(&#39;Content-Length&#39;, &#39;40&#39;), (&#39;Content-Type&#39;, &#39;text/html&#39;)]</span>
162 </pre></div>
163 <p class="last"><strong>returns</strong>: new linked <cite>Headers</cite> object.</p>
164 </dd>
165 </dl>
166 <dl>
167 <dt><cite id="werkzeug.utils.Headers.get">get</cite> <tt class="func-signature docutils literal"><span class="pre">(key,</span> <span class="pre">default=None,</span> <span class="pre">type=None)</span></tt></dt>
168 <dd><p class="first">Return the default value if the requested data doesn&#8217;t exist.
169 If <cite>type</cite> is provided and is a callable it should convert the value,
170 return it or raise a <cite>ValueError</cite> if that is not possible. In this
171 case the function will return the default as if the value was not
172 found.</p>
173 <p>Example:</p>
174 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">d</span> <span class="o">=</span> <span class="n">Headers</span><span class="p">([(</span><span class="s">&#39;Content-Length&#39;</span><span class="p">,</span> <span class="s">&#39;42&#39;</span><span class="p">)])</span>
175 <span class="gp">&gt;&gt;&gt; </span><span class="n">d</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&#39;Content-Length&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
176 <span class="go">42</span>
177 </pre></div>
178 <p class="last">If a headers object is bound you must notadd unicode strings
179 because no encoding takes place.</p>
180 </dd>
181 </dl>
182 <dl>
183 <dt><cite id="werkzeug.utils.Headers.getlist">getlist</cite> <tt class="func-signature docutils literal"><span class="pre">(key,</span> <span class="pre">type=None)</span></tt></dt>
184 <dd><p class="first">Return the list of items for a given key. If that key is not in the
185 <cite>MultiDict</cite>, the return value will be an empty list. Just as <cite>get</cite>
186 <cite>getlist</cite> accepts a <cite>type</cite> parameter. All items will be converted
187 with the callable defined there.</p>
188 <p class="last"><strong>returns</strong>: list</p>
189 </dd>
190 </dl>
191 <dl>
192 <dt><cite id="werkzeug.utils.Headers.add">add</cite> <tt class="func-signature docutils literal"><span class="pre">(key,</span> <span class="pre">value)</span></tt></dt>
193 <dd>add a new header tuple to the list</dd>
194 </dl>
195 <dl>
196 <dt><cite id="werkzeug.utils.Headers.set">set</cite> <tt class="func-signature docutils literal"><span class="pre">(key,</span> <span class="pre">value)</span></tt></dt>
197 <dd>remove all header tuples for key and add
198 a new one</dd>
199 </dl>
200 <dl>
201 <dt><cite id="werkzeug.utils.Headers.extend">extend</cite> <tt class="func-signature docutils literal"><span class="pre">(iterable)</span></tt></dt>
202 <dd>Extend the headers with a dict or an iterable yielding keys and
203 values.</dd>
204 </dl>
205 <dl>
206 <dt><cite id="werkzeug.utils.Headers.clear">clear</cite> <tt class="func-signature docutils literal"><span class="pre">()</span></tt></dt>
207 <dd>clears all headers</dd>
208 </dl>
209 <dl>
210 <dt><cite id="werkzeug.utils.Headers.to_list">to_list</cite> <tt class="func-signature docutils literal"><span class="pre">(charset='utf-8')</span></tt></dt>
211 <dd><p class="first">Convert the headers into a list and converts the unicode header
212 items to the specified charset.</p>
213 <p class="last"><strong>returns</strong>: list</p>
214 </dd>
215 </dl>
216 <dl>
217 <dt><cite id="werkzeug.utils.Headers.__contains__">__contains__</cite> <tt class="func-signature docutils literal"><span class="pre">(key)</span></tt></dt>
218 <dd>Check if a key is present.</dd>
219 </dl>
220 <dl>
221 <dt><cite id="werkzeug.utils.Headers.__iter__">__iter__</cite> <tt class="func-signature docutils literal"><span class="pre">()</span></tt></dt>
222 <dd>Yield <tt class="docutils literal"><span class="pre">(key,</span> <span class="pre">value)</span></tt> tuples.</dd>
223 </dl>
224 <p class="last">All the other dict functions such as <cite>iterkeys</cite> are available and
225 work the same.</p>
226 </dd>
227 </dl>
228 <dl>
229 <dt><strong>class</strong> <cite id="werkzeug.utils.EnvironHeaders">EnvironHeaders</cite></dt>
230 <dd><p class="first">Read only version of the headers from a WSGI environment. This
231 provides the same interface as <cite>Headers</cite> and is constructed from
232 a WSGI environment.</p>
233 <p class="last">From Werkzeug 0.3 onwards, the <cite>KeyError</cite> raised by this class is also a
234 subclass of the <cite>BadRequest</cite> HTTP exception and will render a page for a
235 <tt class="docutils literal"><span class="pre">400</span> <span class="pre">BAD</span> <span class="pre">REQUEST</span></tt> if catched in a catch-all for HTTP exceptions.</p>
236 </dd>
237 </dl>
238 </div>
239 <div class="section">
240 <h3 id="working-with-http-headers">Working with HTTP Headers</h3>
241 <dl>
242 <dt><cite id="werkzeug.http.parse_set_header">parse_set_header</cite> <tt class="func-signature docutils literal"><span class="pre">(value,</span> <span class="pre">on_update=None)</span></tt></dt>
243 <dd>Parse a set like header and return a <cite>HeaderSet</cite> object. The return
244 value is an object that treats the items case insensitive and keeps the
245 order of the items.</dd>
246 </dl>
247 <dl>
248 <dt><cite id="werkzeug.http.parse_list_header">parse_list_header</cite> <tt class="func-signature docutils literal"><span class="pre">(value)</span></tt></dt>
249 <dd><p class="first">Parse lists as described by RFC 2068 Section 2.</p>
250 <p class="last">In particular, parse comma-separated lists where the elements of
251 the list may include quoted-strings. A quoted-string could
252 contain a comma. A non-quoted string could have quotes in the
253 middle. Quotes are removed automatically after parsing.</p>
254 </dd>
255 </dl>
256 <dl>
257 <dt><cite id="werkzeug.http.parse_dict_header">parse_dict_header</cite> <tt class="func-signature docutils literal"><span class="pre">(value)</span></tt></dt>
258 <dd>Parse lists of key, value paits as described by RFC 2068 Section 2 and
259 convert them into a python dict. If there is no value for a key it will
260 be <cite>None</cite>.</dd>
261 </dl>
262 <dl>
263 <dt><cite id="werkzeug.http.dump_header">dump_header</cite> <tt class="func-signature docutils literal"><span class="pre">(iterable,</span> <span class="pre">allow_token=True)</span></tt></dt>
264 <dd><p class="first">Dump an HTTP header again. This is the reversal of
265 <cite>parse_list_header</cite>, <cite>parse_set_header</cite> and <cite>parse_dict_header</cite>. This
266 also quotes strings that include an equals sign unless you pass it as dict
267 of key, value pairs.</p>
268 <p class="last">The <cite>allow_token</cite> parameter can be set to <cite>False</cite> to disallow tokens as
269 values. If this is enabled all values are quoted.</p>
270 </dd>
271 </dl>
272 <dl>
273 <dt><strong>class</strong> <cite id="werkzeug.http.Accept">Accept</cite></dt>
274 <dd><p class="first">An <cite>Accept</cite> object is just a list subclass for lists of
275 <tt class="docutils literal"><span class="pre">(value,</span> <span class="pre">quality)</span></tt> tuples. It is automatically sorted by quality.</p>
276 <dl>
277 <dt><cite>provided</cite></dt>
278 <dd><cite>True</cite> if the <cite>Accept</cite> object was created from a list, <cite>False</cite> if the
279 initial value was <cite>None</cite>. This is used by the request wrappers to
280 keep the information if the header was present or not.</dd>
281 <dt><cite>best</cite></dt>
282 <dd>The best value (does not return a tuple!).</dd>
283 </dl>
284 <dl>
285 <dt><cite id="werkzeug.http.Accept.__getitem__">__getitem__</cite> <tt class="func-signature docutils literal"><span class="pre">(key)</span></tt></dt>
286 <dd>Beside index lookup (getting item n) you can also pass it a string
287 to get the quality for the item. If the item is not in the list, the
288 returned quality is <tt class="docutils literal"><span class="pre">0</span></tt>.</dd>
289 </dl>
290 <dl>
291 <dt><cite id="werkzeug.http.Accept.find">find</cite> <tt class="func-signature docutils literal"><span class="pre">(key)</span></tt></dt>
292 <dd>Get the position of an entry or return -1</dd>
293 </dl>
294 <dl>
295 <dt><cite id="werkzeug.http.Accept.values">values</cite> <tt class="func-signature docutils literal"><span class="pre">()</span></tt></dt>
296 <dd>Return a list of the values, not the qualities.</dd>
297 </dl>
298 <dl class="last">
299 <dt><cite id="werkzeug.http.Accept.itervalues">itervalues</cite> <tt class="func-signature docutils literal"><span class="pre">()</span></tt></dt>
300 <dd>Iterate over all values.</dd>
301 </dl>
302 </dd>
303 </dl>
304 <dl>
305 <dt><cite id="werkzeug.http.parse_accept_header">parse_accept_header</cite> <tt class="func-signature docutils literal"><span class="pre">(value)</span></tt></dt>
306 <dd><p class="first">Parses an HTTP Accept-* header. This does not implement a complete
307 valid algorithm but one that supports at least value and quality
308 extraction.</p>
309 <p class="last">Returns a new <cite>Accept</cite> object (basicly a list of <tt class="docutils literal"><span class="pre">(value,</span> <span class="pre">quality)</span></tt>
310 tuples sorted by the quality with some additional accessor methods).</p>
311 </dd>
312 </dl>
313 <dl>
314 <dt><strong>class</strong> <cite id="werkzeug.http.CacheControl">CacheControl</cite></dt>
315 <dd><p class="first">Subclass of a dict that stores values for a Cache-Control header. It
316 has accesors for all the cache-control directives specified in RFC 2616.
317 The class does not differentiate between request and response directives.</p>
318 <p>Because the cache-control directives in the HTTP header use dashes the
319 python descriptors use underscores for that.</p>
320 <p>To get a header of the <cite>CacheControl</cite> object again you can convert the
321 object into a string or call the <cite>to_header()</cite> function. If you plan
322 to subclass it and add your own items have a look at the sourcecode for
323 that class.</p>
324 <p>The following attributes are exposed:</p>
325 <p class="last"><cite>no_cache</cite>, <cite>no_store</cite>, <cite>max_age</cite>, <cite>max_stale</cite>, <cite>min_fresh</cite>,
326 <cite>no_transform</cite>, <cite>only_if_cached</cite>, <cite>public</cite>, <cite>private</cite>, <cite>must_revalidate</cite>,
327 <cite>proxy_revalidate</cite>, and <cite>s_maxage</cite></p>
328 </dd>
329 </dl>
330 <dl>
331 <dt><cite id="werkzeug.http.parse_cache_control_header">parse_cache_control_header</cite> <tt class="func-signature docutils literal"><span class="pre">(value,</span> <span class="pre">on_update=None)</span></tt></dt>
332 <dd>Parse a cache control header. The RFC differs between response and
333 request cache control, this method does not. It&#8217;s your responsibility
334 to not use the wrong control statements.</dd>
335 </dl>
336 <dl>
337 <dt><strong>class</strong> <cite id="werkzeug.http.HeaderSet">HeaderSet</cite></dt>
338 <dd><p class="first">Similar to the <cite>ETags</cite> class this implements a set like structure.
339 Unlike <cite>ETags</cite> this is case insensitive and used for vary, allow, and
340 content-language headers.</p>
341 <p>If not constructed using the <cite>parse_set_header</cite> function the instanciation
342 works like this:</p>
343 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">hs</span> <span class="o">=</span> <span class="n">HeaderSet</span><span class="p">([</span><span class="s">&#39;foo&#39;</span><span class="p">,</span> <span class="s">&#39;bar&#39;</span><span class="p">,</span> <span class="s">&#39;baz&#39;</span><span class="p">])</span>
344 <span class="gp">&gt;&gt;&gt; </span><span class="n">hs</span>
345 <span class="go">HeaderSet([&#39;foo&#39;, &#39;bar&#39;, &#39;baz&#39;])</span>
346 </pre></div>
347 <dl>
348 <dt><cite id="werkzeug.http.HeaderSet.add">add</cite> <tt class="func-signature docutils literal"><span class="pre">(header)</span></tt></dt>
349 <dd>Add a new header to the set.</dd>
350 </dl>
351 <dl>
352 <dt><cite id="werkzeug.http.HeaderSet.remove">remove</cite> <tt class="func-signature docutils literal"><span class="pre">(header)</span></tt></dt>
353 <dd>Remove a layer from the set. This raises an <cite>IndexError</cite> if the
354 header is not in the set.</dd>
355 </dl>
356 <dl>
357 <dt><cite id="werkzeug.http.HeaderSet.update">update</cite> <tt class="func-signature docutils literal"><span class="pre">(iterable)</span></tt></dt>
358 <dd>Add all the headers from the iterable to the set.</dd>
359 </dl>
360 <dl>
361 <dt><cite id="werkzeug.http.HeaderSet.discard">discard</cite> <tt class="func-signature docutils literal"><span class="pre">(header)</span></tt></dt>
362 <dd>Like remove but ignores errors.</dd>
363 </dl>
364 <dl>
365 <dt><cite id="werkzeug.http.HeaderSet.find">find</cite> <tt class="func-signature docutils literal"><span class="pre">(header)</span></tt></dt>
366 <dd>Return the index of the header in the set or return -1 if not found.</dd>
367 </dl>
368 <dl>
369 <dt><cite id="werkzeug.http.HeaderSet.index">index</cite> <tt class="func-signature docutils literal"><span class="pre">(header)</span></tt></dt>
370 <dd>Return the index of the headerin the set or raise an <cite>IndexError</cite>.</dd>
371 </dl>
372 <dl>
373 <dt><cite id="werkzeug.http.HeaderSet.clear">clear</cite> <tt class="func-signature docutils literal"><span class="pre">()</span></tt></dt>
374 <dd>Clear the set.</dd>
375 </dl>
376 <dl>
377 <dt><cite id="werkzeug.http.HeaderSet.as_set">as_set</cite> <tt class="func-signature docutils literal"><span class="pre">(preserve_casing=False)</span></tt></dt>
378 <dd><p class="first">Return the set as real python set structure. When calling this
379 all the items are converted to lowercase and the ordering is lost.</p>
380 <p class="last">If <cite>preserve_casing</cite> is <cite>True</cite> the items in the set returned will
381 have the original case like in the <cite>HeaderSet</cite>, otherwise they will
382 be lowercase.</p>
383 </dd>
384 </dl>
385 <dl class="last">
386 <dt><cite id="werkzeug.http.HeaderSet.to_header">to_header</cite> <tt class="func-signature docutils literal"><span class="pre">()</span></tt></dt>
387 <dd>Convert the header set into an HTTP header string.</dd>
388 </dl>
389 </dd>
390 </dl>
391 <dl>
392 <dt><strong>class</strong> <cite id="werkzeug.useragents.UserAgent">UserAgent</cite></dt>
393 <dd><p class="first">Represents a user agent. Pass it a WSGI environment or an user agent
394 string and you can inspect some of the details from the user agent
395 string via the attributes. The following attribute exist:</p>
396 <ul class="last simple">
397 <li><cite>string</cite>, the raw user agent string</li>
398 <li><cite>platform</cite>, the browser platform</li>
399 <li><cite>browser</cite>, the name of the browser</li>
400 <li><cite>version</cite>, the version of the browser</li>
401 <li><cite>language</cite>, the language of the browser</li>
402 </ul>
403 </dd>
404 </dl>
405 <dl>
406 <dt><cite id="werkzeug.http.parse_date">parse_date</cite> <tt class="func-signature docutils literal"><span class="pre">(value)</span></tt></dt>
407 <dd><p class="first">Parse one of the following date formats into a datetime object:</p>
408 <div class="syntax"><pre>Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
409 Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
410 Sun Nov 6 08:49:37 1994 ; ANSI C&#39;s asctime() format
411 </pre></div>
412 <p class="last">If parsing fails the return value is <cite>None</cite>.</p>
413 </dd>
414 </dl>
415 <dl>
416 <dt><cite id="werkzeug.utils.cookie_date">cookie_date</cite> <tt class="func-signature docutils literal"><span class="pre">(expires=None)</span></tt></dt>
417 <dd><p class="first">Formats the time to ensure compatibility with Netscape&#8217;s cookie
418 standard.</p>
419 <p>Accepts a floating point number expressed in seconds since the epoc in, a
420 datetime object or a timetuple. All times in UTC. The <cite>parse_date</cite>
421 function in <cite>werkzeug.http</cite> can be used to parse such a date.</p>
422 <p class="last">Outputs a string in the format <tt class="docutils literal"><span class="pre">Wdy,</span> <span class="pre">DD-Mon-YYYY</span> <span class="pre">HH:MM:SS</span> <span class="pre">GMT</span></tt>.</p>
423 </dd>
424 </dl>
425 <dl>
426 <dt><cite id="werkzeug.utils.http_date">http_date</cite> <tt class="func-signature docutils literal"><span class="pre">(timestamp=None)</span></tt></dt>
427 <dd><p class="first">Formats the time to match the RFC1123 date format.</p>
428 <p>Accepts a floating point number expressed in seconds since the epoc in, a
429 datetime object or a timetuple. All times in UTC. The <cite>parse_date</cite>
430 function in <cite>werkzeug.http</cite> can be used to parse such a date.</p>
431 <p class="last">Outputs a string in the format <tt class="docutils literal"><span class="pre">Wdy,</span> <span class="pre">DD</span> <span class="pre">Mon</span> <span class="pre">YYYY</span> <span class="pre">HH:MM:SS</span> <span class="pre">GMT</span></tt>.</p>
432 </dd>
433 </dl>
434 <dl>
435 <dt><cite id="werkzeug.utils.parse_form_data">parse_form_data</cite> <tt class="func-signature docutils literal"><span class="pre">(environ,</span> <span class="pre">stream_factory=None,</span> <span class="pre">charset='utf-8',</span> <span class="pre">errors='ignore')</span></tt></dt>
436 <dd><p class="first">Parse the form data in the environ and return it as tuple in the form
437 <tt class="docutils literal"><span class="pre">(stream,</span> <span class="pre">form,</span> <span class="pre">files)</span></tt>. You should only call this method if the
438 transport method is <cite>POST</cite> or <cite>PUT</cite>.</p>
439 <p class="last">If the mimetype of the data transmitted is <cite>multipart/form-data</cite> the
440 files multidict will be filled with <cite>FileStorage</cite> objects. If the
441 mimetype is unknow the input stream is wrapped and returned as first
442 argument, else the stream is empty.</p>
443 </dd>
444 </dl>
445 <dl>
446 <dt><strong>class</strong> <cite id="werkzeug.http.ETags">ETags</cite></dt>
447 <dd><p class="first">A set that can be used to check if one etag is present in a collection
448 of etags.</p>
449 <dl>
450 <dt><cite id="werkzeug.http.ETags.is_weak">is_weak</cite> <tt class="func-signature docutils literal"><span class="pre">(etag)</span></tt></dt>
451 <dd>Check if an etag is weak.</dd>
452 </dl>
453 <dl>
454 <dt><cite id="werkzeug.http.ETags.contains_weak">contains_weak</cite> <tt class="func-signature docutils literal"><span class="pre">(etag)</span></tt></dt>
455 <dd>Check if an etag is part of the set including weak and strong tags.</dd>
456 </dl>
457 <dl>
458 <dt><cite id="werkzeug.http.ETags.contains">contains</cite> <tt class="func-signature docutils literal"><span class="pre">(etag)</span></tt></dt>
459 <dd>Check if an etag is part of the set ignoring weak tags.</dd>
460 </dl>
461 <dl>
462 <dt><cite id="werkzeug.http.ETags.contains_raw">contains_raw</cite> <tt class="func-signature docutils literal"><span class="pre">(etag)</span></tt></dt>
463 <dd>When passed a quoted tag it will check if this tag is part of the
464 set. If the tag is weak it is checked against weak and strong tags,
465 otherwise weak only.</dd>
466 </dl>
467 <dl>
468 <dt><cite id="werkzeug.http.ETags.as_set">as_set</cite> <tt class="func-signature docutils literal"><span class="pre">(include_weak=False)</span></tt></dt>
469 <dd>Convert the <cite>ETags</cite> object into a python set. Per default all the
470 weak etags are not part of this set.</dd>
471 </dl>
472 <dl class="last">
473 <dt><cite id="werkzeug.http.ETags.to_header">to_header</cite> <tt class="func-signature docutils literal"><span class="pre">()</span></tt></dt>
474 <dd>Convert the etags set into a HTTP header string.</dd>
475 </dl>
476 </dd>
477 </dl>
478 <dl>
479 <dt><cite id="werkzeug.http.parse_etags">parse_etags</cite> <tt class="func-signature docutils literal"><span class="pre">(value)</span></tt></dt>
480 <dd>Parse and etag header. Returns an <cite>ETags</cite> object.</dd>
481 </dl>
482 <dl>
483 <dt><cite id="werkzeug.http.quote_etag">quote_etag</cite> <tt class="func-signature docutils literal"><span class="pre">(etag,</span> <span class="pre">weak=False)</span></tt></dt>
484 <dd>Quote an etag.</dd>
485 </dl>
486 <dl>
487 <dt><cite id="werkzeug.http.unquote_etag">unquote_etag</cite> <tt class="func-signature docutils literal"><span class="pre">(etag)</span></tt></dt>
488 <dd>Unquote a single etag. Return a <tt class="docutils literal"><span class="pre">(etag,</span> <span class="pre">weak)</span></tt> tuple.</dd>
489 </dl>
490 <dl>
491 <dt><cite id="werkzeug.http.generate_etag">generate_etag</cite> <tt class="func-signature docutils literal"><span class="pre">(data)</span></tt></dt>
492 <dd>Generate an etag for some data.</dd>
493 </dl>
494 <dl>
495 <dt><cite id="werkzeug.http.is_resource_modified">is_resource_modified</cite> <tt class="func-signature docutils literal"><span class="pre">(environ,</span> <span class="pre">etag=None,</span> <span class="pre">data=None,</span> <span class="pre">last_modified=None)</span></tt></dt>
496 <dd>Convenience method for conditional requests.</dd>
497 </dl>
498 <dl>
499 <dt><strong>class</strong> <cite id="werkzeug.http.Authorization">Authorization</cite></dt>
500 <dd><p class="first">Represents an <cite>Authorization</cite> header sent by the client. You should
501 not create this kind of object yourself but use it when it&#8217;s returned by
502 the <cite>parse_authorization_header</cite> function.</p>
503 <p>This object is a dict subclass and can be altered by setting dict items
504 but it should be considered immutable as it&#8217;s returned by the client and
505 not meant for modifications.</p>
506 <dl>
507 <dt><cite id="werkzeug.http.WWWAuthenticate.to_header">to_header</cite> <tt class="func-signature docutils literal"><span class="pre">()</span></tt></dt>
508 <dd>Convert the stored values into a WWW-Authenticate header.</dd>
509 </dl>
510 <dl>
511 <dt><cite id="werkzeug.http.Authorization.username">username</cite></dt>
512 <dd>The username transmitted. This is set for both basic and digest
513 auth all the time.</dd>
514 </dl>
515 <dl>
516 <dt><cite id="werkzeug.http.Authorization.password">password</cite></dt>
517 <dd>When the authentication type is basic this is the password
518 transmitted by the client, else <cite>None</cite>.</dd>
519 </dl>
520 <dl>
521 <dt><cite id="werkzeug.http.Authorization.realm">realm</cite></dt>
522 <dd>This is the server realm send back for digest auth. For HTTP
523 digest auth.</dd>
524 </dl>
525 <dl>
526 <dt><cite id="werkzeug.http.Authorization.nonce">nonce</cite></dt>
527 <dd>The nonce the server send for digest auth, send back by the client.
528 A nonce should be unique for every 401 response for HTTP digest
529 auth.</dd>
530 </dl>
531 <dl>
532 <dt><cite id="werkzeug.http.Authorization.uri">uri</cite></dt>
533 <dd>The URI from Request-URI of the Request-Line; duplicated because
534 proxies are allowed to change the Request-Line in transit. HTTP
535 digest auth only.</dd>
536 </dl>
537 <dl>
538 <dt><cite id="werkzeug.http.Authorization.nc">nc</cite></dt>
539 <dd>The nonce count value transmitted by clients if a qop-header is
540 also transmitted. HTTP digest auth only.</dd>
541 </dl>
542 <dl>
543 <dt><cite id="werkzeug.http.Authorization.cnonce">cnonce</cite></dt>
544 <dd>If the server sent a qop-header in the <tt class="docutils literal"><span class="pre">WWW-Authenticate</span></tt>
545 header, the client has to provide this value for HTTP digest auth.
546 See the RFC for more details.</dd>
547 </dl>
548 <dl>
549 <dt><cite id="werkzeug.http.Authorization.response">response</cite></dt>
550 <dd>A string of 32 hex digits computed as defined in RFC 2617, which
551 proves that the user knows a password. Digest auth only.</dd>
552 </dl>
553 <dl>
554 <dt><cite id="werkzeug.http.Authorization.opaque">opaque</cite></dt>
555 <dd>The opaque header from the server returned unchanged by the client.
556 It is recommended that this string be base64 or hexadecimal data.
557 Digest auth only.</dd>
558 </dl>
559 <dl class="last">
560 <dt><cite id="werkzeug.http.Authorization.qop">qop</cite></dt>
561 <dd>Indicates what &#8220;quality of protection&#8221; the client has applied to
562 the message for HTTP digest auth.</dd>
563 </dl>
564 </dd>
565 </dl>
566 <dl>
567 <dt><strong>class</strong> <cite id="werkzeug.http.WWWAuthenticate">WWWAuthenticate</cite></dt>
568 <dd><p class="first">Provides simple access to <cite>WWW-Authenticate</cite> headers.</p>
569 <dl>
570 <dt><cite id="werkzeug.http.WWWAuthenticate.set_basic">set_basic</cite> <tt class="func-signature docutils literal"><span class="pre">(realm='authentication</span> <span class="pre">required')</span></tt></dt>
571 <dd>Clear the auth info and enable basic auth.</dd>
572 </dl>
573 <dl>
574 <dt><cite id="werkzeug.http.WWWAuthenticate.set_digest">set_digest</cite> <tt class="func-signature docutils literal"><span class="pre">(realm,</span> <span class="pre">nonce,</span> <span class="pre">qop=('auth',),</span> <span class="pre">opaque=None,</span> <span class="pre">algorithm=None,</span> <span class="pre">stale=False)</span></tt></dt>
575 <dd>Clear the auth info and enable digest auth.</dd>
576 </dl>
577 <dl>
578 <dt><cite id="werkzeug.http.WWWAuthenticate.to_header">to_header</cite> <tt class="func-signature docutils literal"><span class="pre">()</span></tt></dt>
579 <dd>Convert the stored values into a WWW-Authenticate header.</dd>
580 </dl>
581 <dl>
582 <dt><cite id="werkzeug.http.WWWAuthenticate.type">type</cite></dt>
583 <dd>The type of the auth machanism. HTTP currently specifies
584 <cite>Basic</cite> and <cite>Digest</cite>.</dd>
585 </dl>
586 <dl>
587 <dt><cite id="werkzeug.http.WWWAuthenticate.realm">realm</cite></dt>
588 <dd>A string to be displayed to users so they know which username and
589 password to use. This string should contain at least the name of
590 the host performing the authentication and might additionally
591 indicate the collection of users who might have access.</dd>
592 </dl>
593 <dl>
594 <dt><cite id="werkzeug.http.WWWAuthenticate.stale">stale</cite></dt>
595 <dd>A flag, indicating that the previous request from the client was
596 rejected because the nonce value was stale.</dd>
597 </dl>
598 <dl>
599 <dt><cite id="werkzeug.http.WWWAuthenticate.domain">domain</cite></dt>
600 <dd>A list of URIs that define the protection space. If a URI is an
601 absolte path, it is relative to the canonical root URL of the
602 server being accessed.</dd>
603 </dl>
604 <dl>
605 <dt><cite id="werkzeug.http.WWWAuthenticate.nonce">nonce</cite></dt>
606 <dd>A server-specified data string which should be uniquely generated
607 each time a 401 response is made. It is recommended that this
608 string be base64 or hexadecimal data.</dd>
609 </dl>
610 <dl>
611 <dt><cite id="werkzeug.http.WWWAuthenticate.opaque">opaque</cite></dt>
612 <dd>A string of data, specified by the server, which should be returned
613 by the client unchanged in the Authorization header of subsequent
614 requests with URIs in the same protection space. It is recommended
615 that this string be base64 or hexadecimal data.</dd>
616 </dl>
617 <dl>
618 <dt><cite id="werkzeug.http.WWWAuthenticate.algorithm">algorithm</cite></dt>
619 <dd>A string indicating a pair of algorithms used to produce the digest
620 and a checksum. If this is not present it is assumed to be &#8220;MD5&#8221;.
621 If the algorithm is not understood, the challenge should be ignored
622 (and a different one used, if there is more than one).</dd>
623 </dl>
624 <dl class="last">
625 <dt><cite id="werkzeug.http.WWWAuthenticate.qop">qop</cite></dt>
626 <dd>A set of quality-of-privacy modifies such as auth and auth-int.</dd>
627 </dl>
628 </dd>
629 </dl>
630 <dl>
631 <dt><cite id="werkzeug.http.parse_authorization_header">parse_authorization_header</cite> <tt class="func-signature docutils literal"><span class="pre">(value)</span></tt></dt>
632 <dd>Parse an HTTP basic/digest authorization header transmitted by the web
633 browser. The return value is either <cite>None</cite> if the header was invalid or
634 not given, otherwise an <cite>Authorization</cite> object.</dd>
635 </dl>
636 <dl>
637 <dt><cite id="werkzeug.http.parse_www_authenticate_header">parse_www_authenticate_header</cite> <tt class="func-signature docutils literal"><span class="pre">(value,</span> <span class="pre">on_update=None)</span></tt></dt>
638 <dd>Parse an HTTP WWW-Authenticate header into a <cite>WWWAuthenticate</cite>
639 object.</dd>
640 </dl>
641 <dl>
642 <dt><cite>HTTP_STATUS_CODES</cite></dt>
643 <dd>A dict of status code -&gt; default status message pairs. This is used
644 by the wrappers and other places where a integer status code is expanded
645 to a string throughout Werkzeug.</dd>
646 </dl>
647 </div>
648 <div class="section">
649 <h3 id="url-helpers">URL Helpers</h3>
650 <dl>
651 <dt><cite id="werkzeug.utils.url_decode">url_decode</cite> <tt class="func-signature docutils literal"><span class="pre">(s,</span> <span class="pre">charset='utf-8',</span> <span class="pre">decode_keys=False,</span> <span class="pre">include_empty=True,</span> <span class="pre">errors='ignore')</span></tt></dt>
652 <dd><p class="first">Parse a querystring and return it as <cite>MultiDict</cite>. Per default only
653 values are decoded into unicode strings. If <cite>decode_keys</cite> is set to
654 <tt class="docutils literal"><span class="pre">True</span></tt> the same will happen for keys.</p>
655 <p>Per default a missing value for a key will default to an empty key. If
656 you don&#8217;t want that behavior you can set <cite>include_empty</cite> to <cite>False</cite>.</p>
657 <p class="last">Per default encoding errors are ignore. If you want a different behavior
658 you can set <cite>errors</cite> to <tt class="docutils literal"><span class="pre">'replace'</span></tt> or <tt class="docutils literal"><span class="pre">'strict'</span></tt>. In strict mode a
659 <cite>HTTPUnicodeError</cite> is raised.</p>
660 </dd>
661 </dl>
662 <dl>
663 <dt><cite id="werkzeug.utils.url_encode">url_encode</cite> <tt class="func-signature docutils literal"><span class="pre">(obj,</span> <span class="pre">charset='utf-8',</span> <span class="pre">encode_keys=False)</span></tt></dt>
664 <dd>URL encode a dict/<cite>MultiDict</cite>. If a value is <cite>None</cite> it will not appear
665 in the result string. Per default only values are encoded into the target
666 charset strings. If <cite>encode_keys</cite> is set to <tt class="docutils literal"><span class="pre">True</span></tt> unicode keys are
667 supported too.</dd>
668 </dl>
669 <dl>
670 <dt><cite id="werkzeug.utils.url_quote">url_quote</cite> <tt class="func-signature docutils literal"><span class="pre">(s,</span> <span class="pre">charset='utf-8',</span> <span class="pre">safe='/:')</span></tt></dt>
671 <dd>URL encode a single string with a given encoding.</dd>
672 </dl>
673 <dl>
674 <dt><cite id="werkzeug.utils.url_quote_plus">url_quote_plus</cite> <tt class="func-signature docutils literal"><span class="pre">(s,</span> <span class="pre">charset='utf-8',</span> <span class="pre">safe='')</span></tt></dt>
675 <dd>URL encode a single string with the given encoding and convert
676 whitespace to &#8220;+&#8221;.</dd>
677 </dl>
678 <dl>
679 <dt><cite id="werkzeug.utils.url_unquote">url_unquote</cite> <tt class="func-signature docutils literal"><span class="pre">(s,</span> <span class="pre">charset='utf-8',</span> <span class="pre">errors='ignore')</span></tt></dt>
680 <dd><p class="first">URL decode a single string with a given decoding.</p>
681 <p class="last">Per default encoding errors are ignore. If you want a different behavior
682 you can set <cite>errors</cite> to <tt class="docutils literal"><span class="pre">'replace'</span></tt> or <tt class="docutils literal"><span class="pre">'strict'</span></tt>. In strict mode a
683 <cite>HTTPUnicodeError</cite> is raised.</p>
684 </dd>
685 </dl>
686 <dl>
687 <dt><cite id="werkzeug.utils.url_unquote_plus">url_unquote_plus</cite> <tt class="func-signature docutils literal"><span class="pre">(s,</span> <span class="pre">charset='utf-8',</span> <span class="pre">errors='ignore')</span></tt></dt>
688 <dd><p class="first">URL decode a single string with the given decoding and decode
689 a &#8220;+&#8221; to whitespace.</p>
690 <p class="last">Per default encoding errors are ignore. If you want a different behavior
691 you can set <cite>errors</cite> to <tt class="docutils literal"><span class="pre">'replace'</span></tt> or <tt class="docutils literal"><span class="pre">'strict'</span></tt>. In strict mode a
692 <cite>HTTPUnicodeError</cite> is raised.</p>
693 </dd>
694 </dl>
695 <dl>
696 <dt><cite id="werkzeug.utils.url_fix">url_fix</cite> <tt class="func-signature docutils literal"><span class="pre">(s,</span> <span class="pre">charset='utf-8')</span></tt></dt>
697 <dd><p class="first">Sometimes you get an URL by a user that just isn&#8217;t a real URL because
698 it contains unsafe characters like &#8216; &#8216; and so on. This function can fix
699 some of the problems in a similar way browsers handle data entered by the
700 user:</p>
701 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">url_fix</span><span class="p">(</span><span class="s">u&#39;http://de.wikipedia.org/wiki/Elf (Begriffsklärung)&#39;</span><span class="p">)</span>
702 <span class="go">&#39;http://de.wikipedia.org/wiki/Elf%20%28Begriffskl%C3%A4rung%29&#39;</span>
703 </pre></div>
704 <dl class="last">
705 <dt>Parameters</dt>
706 <dd><strong>charset</strong>: The target charset for the URL if the url was given as
707 unicode string.</dd>
708 </dl>
709 </dd>
710 </dl>
711 <dl>
712 <dt><strong>class</strong> <cite id="werkzeug.utils.Href">Href</cite></dt>
713 <dd><p class="first">Implements a callable that constructs URLs with the given base. The
714 function can be called with any number of positional and keyword
715 arguments which than are used to assemble the URL. Works with URLs
716 and posix paths.</p>
717 <p>Positional arguments are appended as individual segments to
718 the path of the URL:</p>
719 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">href</span> <span class="o">=</span> <span class="n">Href</span><span class="p">(</span><span class="s">&#39;/foo&#39;</span><span class="p">)</span>
720 <span class="gp">&gt;&gt;&gt; </span><span class="n">href</span><span class="p">(</span><span class="s">&#39;bar&#39;</span><span class="p">,</span> <span class="mf">23</span><span class="p">)</span>
721 <span class="go">&#39;/foo/bar/23&#39;</span>
722 <span class="gp">&gt;&gt;&gt; </span><span class="n">href</span><span class="p">(</span><span class="s">&#39;foo&#39;</span><span class="p">,</span> <span class="n">bar</span><span class="o">=</span><span class="mf">23</span><span class="p">)</span>
723 <span class="go">&#39;/foo/foo?bar=23&#39;</span>
724 </pre></div>
725 <p>If any of the arguments (positional or keyword) evaluates to <cite>None</cite> it
726 will be skipped. If no keyword arguments are given the last argument
727 can be a <cite>dict</cite> or <cite>MultiDict</cite> (or any other dict subclass), otherwise
728 the keyword arguments are used for the query parameters, cutting off
729 the first trailing underscore of the parameter name:</p>
730 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">href</span><span class="p">(</span><span class="n">is_</span><span class="o">=</span><span class="mf">42</span><span class="p">)</span>
731 <span class="go">&#39;/foo?is=42&#39;</span>
732 </pre></div>
733 <p>Accessing attributes on the href object creates a new href object with
734 the attribute name as prefix:</p>
735 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">bar_href</span> <span class="o">=</span> <span class="n">href</span><span class="o">.</span><span class="n">bar</span>
736 <span class="gp">&gt;&gt;&gt; </span><span class="n">bar_href</span><span class="p">(</span><span class="s">&quot;blub&quot;</span><span class="p">)</span>
737 <span class="go">&#39;/foo/bar/blub&#39;</span>
738 </pre></div>
739 </dd>
740 </dl>
741 </div>
742 <div class="section">
743 <h3 id="html-helpers">HTML Helpers</h3>
744 <dl>
745 <dt><cite id="werkzeug.utils.escape">escape</cite> <tt class="func-signature docutils literal"><span class="pre">(s,</span> <span class="pre">quote=False)</span></tt></dt>
746 <dd><p class="first">Replace special characters &#8220;&amp;&#8221;, &#8220;&lt;&#8221; and &#8220;&gt;&#8221; to HTML-safe sequences. If
747 the optional flag <cite>quote</cite> is <cite>True</cite>, the quotation mark character (&#8220;) is
748 also translated.</p>
749 <p class="last">There is a special handling for <cite>None</cite> which escapes to an empty string.</p>
750 </dd>
751 </dl>
752 <dl>
753 <dt><cite id="werkzeug.utils.unescape">unescape</cite> <tt class="func-signature docutils literal"><span class="pre">(s)</span></tt></dt>
754 <dd>The reverse function of <cite>escape</cite>. This unescapes all the HTML
755 entities, not only the XML entities inserted by <cite>escape</cite>.</dd>
756 </dl>
757 <dl>
758 <dt><strong>class</strong> <cite id="werkzeug.utils.HTMLBuilder">HTMLBuilder</cite></dt>
759 <dd><p class="first">Helper object for HTML generation.</p>
760 <p>Per default there are two instances of that class. The <cite>html</cite> one, and
761 the <cite>xhtml</cite> one for those two dialects. The class uses keyword parameters
762 and positional parameters to generate small snippets of HTML.</p>
763 <p>Keyword parameters are converted to XML/SGML attributes, positional
764 arguments are used as children. Because Python accepts positional
765 arguments before keyword arguments it&#8217;s a good idea to use a list with the
766 star-syntax for some children:</p>
767 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">html</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="n">class_</span><span class="o">=</span><span class="s">&#39;foo&#39;</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="n">html</span><span class="o">.</span><span class="n">a</span><span class="p">(</span><span class="s">&#39;foo&#39;</span><span class="p">,</span> <span class="n">href</span><span class="o">=</span><span class="s">&#39;foo.html&#39;</span><span class="p">),</span> <span class="s">&#39; &#39;</span><span class="p">,</span>
768 <span class="gp">... </span> <span class="n">html</span><span class="o">.</span><span class="n">a</span><span class="p">(</span><span class="s">&#39;bar&#39;</span><span class="p">,</span> <span class="n">href</span><span class="o">=</span><span class="s">&#39;bar.html&#39;</span><span class="p">)])</span>
769 <span class="go">&#39;&lt;p class=&quot;foo&quot;&gt;&lt;a href=&quot;foo.html&quot;&gt;foo&lt;/a&gt; &lt;a href=&quot;bar.html&quot;&gt;bar&lt;/a&gt;&lt;/p&gt;&#39;</span>
770 </pre></div>
771 <p>This class works around some browser limitations and can not be used for
772 arbitrary SGML/XML generation. For that purpose lxml and similar
773 libraries exist.</p>
774 <p>Calling the builder escapes the string passed:</p>
775 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">html</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="n">html</span><span class="p">(</span><span class="s">&quot;&lt;foo&gt;&quot;</span><span class="p">))</span>
776 <span class="go">&#39;&lt;p&gt;&amp;lt;foo&amp;gt;&lt;/p&gt;&#39;</span>
777 </pre></div>
778 </dd>
779 </dl>
780 </div>
781 <div class="section">
782 <h3 id="wsgi-helpers">WSGI Helpers</h3>
783 <dl>
784 <dt><strong>class</strong> <cite id="werkzeug.utils.ClosingIterator">ClosingIterator</cite></dt>
785 <dd><p class="first">The WSGI specification requires that all middlewares and gateways
786 respect the <cite>close</cite> callback of an iterator. Because it is useful to add
787 another close action to a returned iterator and adding a custom iterator
788 is a boring task this class can be used for that:</p>
789 <div class="syntax"><pre><span class="k">return</span> <span class="n">ClosingIterator</span><span class="p">(</span><span class="n">app</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">),</span> <span class="p">[</span><span class="n">cleanup_session</span><span class="p">,</span>
790 <span class="n">cleanup_locals</span><span class="p">])</span>
791 </pre></div>
792 <p>If there is just one close function it can be bassed instead of the list.</p>
793 <p>A closing iterator is non needed if the application uses response objects
794 and finishes the processing if the resonse is started:</p>
795 <div class="syntax"><pre><span class="k">try</span><span class="p">:</span>
796 <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">)</span>
797 <span class="k">finally</span><span class="p">:</span>
798 <span class="n">cleanup_session</span><span class="p">()</span>
799 <span class="n">cleanup_locals</span><span class="p">()</span>
800 </pre></div>
801 </dd>
802 </dl>
803 <dl>
804 <dt><strong>class</strong> <cite id="werkzeug.utils.SharedDataMiddleware">SharedDataMiddleware</cite></dt>
805 <dd><p class="first">A WSGI middleware that provides static content for development
806 environments or simple server setups. Usage is quite simple:</p>
807 <div class="syntax"><pre><span class="k">import</span> <span class="nn">os</span>
808 <span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">SharedDataMiddleware</span>
809
810 <span class="n">app</span> <span class="o">=</span> <span class="n">SharedDataMiddleware</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="p">{</span>
811 <span class="s">&#39;/shared&#39;</span><span class="p">:</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">__file__</span><span class="p">),</span> <span class="s">&#39;shared&#39;</span><span class="p">)</span>
812 <span class="p">})</span>
813 </pre></div>
814 <p>The contents of the folder <tt class="docutils literal"><span class="pre">./shared</span></tt> will now be available on
815 <tt class="docutils literal"><span class="pre">http://example.com/shared/</span></tt>. This is pretty useful during development
816 because a standalone media server is not required. One can also mount
817 files on the root folder and still continue to use the application because
818 the shared data middleware forwards all unhandled requests to the
819 application, even if the requests are below one of the shared folders.</p>
820 <p>If <cite>pkg_resources</cite> is available you can also tell the middleware to serve
821 files from package data:</p>
822 <div class="syntax"><pre><span class="n">app</span> <span class="o">=</span> <span class="n">SharedDataMiddleware</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="p">{</span>
823 <span class="s">&#39;/shared&#39;</span><span class="p">:</span> <span class="p">(</span><span class="s">&#39;myapplication&#39;</span><span class="p">,</span> <span class="s">&#39;shared_files&#39;</span><span class="p">)</span>
824 <span class="p">})</span>
825 </pre></div>
826 <p class="last">This will then serve the <tt class="docutils literal"><span class="pre">shared_files</span></tt> folder in the <cite>myapplication</cite>
827 python package.</p>
828 </dd>
829 </dl>
830 <dl>
831 <dt><strong>class</strong> <cite id="werkzeug.utils.DispatcherMiddleware">DispatcherMiddleware</cite></dt>
832 <dd><p class="first">Allows one to mount middlewares or application in a WSGI application.
833 This is useful if you want to combine multiple WSGI applications:</p>
834 <div class="syntax"><pre><span class="n">app</span> <span class="o">=</span> <span class="n">DispatcherMiddleware</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="p">{</span>
835 <span class="s">&#39;/app2&#39;</span><span class="p">:</span> <span class="n">app2</span><span class="p">,</span>
836 <span class="s">&#39;/app3&#39;</span><span class="p">:</span> <span class="n">app3</span>
837 <span class="p">})</span>
838 </pre></div>
839 </dd>
840 </dl>
841 <dl>
842 <dt><strong>class</strong> <cite id="werkzeug.utils.FileStorage">FileStorage</cite></dt>
843 <dd><p class="first">The <cite>FileStorage</cite> object is a thin wrapper over incoming files. It is
844 used by the request object to represent uploaded files. All the
845 attributes of the wrapper stream are proxied by the file storage so
846 it&#8217;s possible to do <tt class="docutils literal"><span class="pre">storage.read()</span></tt> instead of the long form
847 <tt class="docutils literal"><span class="pre">storage.stream.read()</span></tt>.</p>
848 <dl>
849 <dt><cite>name</cite></dt>
850 <dd>The name of the form field which represents the data.</dd>
851 <dt><cite>filename</cite></dt>
852 <dd>The incoming filename.</dd>
853 <dt><cite>content_type</cite></dt>
854 <dd>The mimetype of the file. E.g. <tt class="docutils literal"><span class="pre">'text/html'</span></tt>.</dd>
855 <dt><cite>content_length / len()</cite></dt>
856 <dd>The expected length of the file.</dd>
857 <dt><cite>stream</cite></dt>
858 <dd>Gives you access to the underlaying stream. Note that the exact
859 methods of this stream and its type is not strictly specified. In
860 most situations it will be a <cite>TemporaryFile</cite> object.</dd>
861 </dl>
862 <dl>
863 <dt><cite id="werkzeug.utils.FileStorage.__init__">__init__</cite> <tt class="func-signature docutils literal"><span class="pre">(stream=None,</span> <span class="pre">filename=None,</span> <span class="pre">name=None,</span> <span class="pre">content_type='application/octet-stream',</span> <span class="pre">content_length=-1)</span></tt></dt>
864 <dd><p class="first">Creates a new <cite>FileStorage</cite> object.</p>
865 <dl class="last">
866 <dt>Parameters</dt>
867 <dd><p class="first"><strong>stream</strong>: the input stream for uploaded file. Usually this
868 points to a temporary file.</p>
869 <p><strong>filename</strong>: The filename of the file on the client.</p>
870 <p><strong>name</strong>: the name of the form field</p>
871 <p><strong>content_type</strong>: the content type of the file</p>
872 <p class="last"><strong>content_length</strong>: the content length of the file.</p>
873 </dd>
874 </dl>
875 </dd>
876 </dl>
877 <dl class="last">
878 <dt><cite id="werkzeug.utils.FileStorage.save">save</cite> <tt class="func-signature docutils literal"><span class="pre">(dst,</span> <span class="pre">buffer_size=16384)</span></tt></dt>
879 <dd>Save the file to a destination path or file object. If the
880 destination is a file object you have to close it yourself after the
881 call. The buffer size is the number of bytes held in the memory
882 during the copy process. It defaults to 16KB.</dd>
883 </dl>
884 </dd>
885 </dl>
886 <dl>
887 <dt><cite id="werkzeug.utils.get_host">get_host</cite> <tt class="func-signature docutils literal"><span class="pre">(environ)</span></tt></dt>
888 <dd>Return the real host for the given WSGI enviornment. This takes care
889 of the <cite>X-Forwarded-Host</cite> header.</dd>
890 </dl>
891 <dl>
892 <dt><cite id="werkzeug.utils.get_current_url">get_current_url</cite> <tt class="func-signature docutils literal"><span class="pre">(environ,</span> <span class="pre">root_only=False,</span> <span class="pre">strip_querystring=False,</span> <span class="pre">host_only=False)</span></tt></dt>
893 <dd><p class="first">A handy helper function that recreates the full URL for the current
894 request or parts of it. Here an example:</p>
895 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">env</span> <span class="o">=</span> <span class="n">create_environ</span><span class="p">(</span><span class="s">&quot;/?param=foo&quot;</span><span class="p">,</span> <span class="s">&quot;http://localhost/script&quot;</span><span class="p">)</span>
896 <span class="gp">&gt;&gt;&gt; </span><span class="n">get_current_url</span><span class="p">(</span><span class="n">env</span><span class="p">)</span>
897 <span class="go">&#39;http://localhost/script/?param=foo&#39;</span>
898 <span class="gp">&gt;&gt;&gt; </span><span class="n">get_current_url</span><span class="p">(</span><span class="n">env</span><span class="p">,</span> <span class="n">root_only</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
899 <span class="go">&#39;http://localhost/script/&#39;</span>
900 <span class="gp">&gt;&gt;&gt; </span><span class="n">get_current_url</span><span class="p">(</span><span class="n">env</span><span class="p">,</span> <span class="n">host_only</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
901 <span class="go">&#39;http://localhost/&#39;</span>
902 <span class="gp">&gt;&gt;&gt; </span><span class="n">get_current_url</span><span class="p">(</span><span class="n">env</span><span class="p">,</span> <span class="n">strip_querystring</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
903 <span class="go">&#39;http://localhost/script/&#39;</span>
904 </pre></div>
905 </dd>
906 </dl>
907 <dl>
908 <dt><cite id="werkzeug.utils.responder">responder</cite> <tt class="func-signature docutils literal"><span class="pre">(f)</span></tt></dt>
909 <dd><p class="first">Marks a function as responder. Decorate a function with it and it
910 will automatically call the return value as WSGI application.</p>
911 <p>Example:</p>
912 <div class="syntax"><pre><span class="nd">@responder</span>
913 <span class="k">def</span> <span class="nf">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
914 <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="s">&#39;Hello World!&#39;</span><span class="p">)</span>
915 </pre></div>
916 </dd>
917 </dl>
918 <dl>
919 <dt><cite id="werkzeug.utils.create_environ">create_environ</cite> <tt class="func-signature docutils literal"><span class="pre">(path='/',</span> <span class="pre">base_url=None,</span> <span class="pre">query_string=None,</span> <span class="pre">method='GET',</span> <span class="pre">input_stream=None,</span> <span class="pre">content_type=None,</span> <span class="pre">content_length=0,</span> <span class="pre">errors_stream=None,</span> <span class="pre">multithread=False,</span> <span class="pre">multiprocess=False,</span> <span class="pre">run_once=False)</span></tt></dt>
920 <dd><p class="first">Create a new WSGI environ dict based on the values passed. The first
921 parameter should be the path of the request which defaults to &#8216;/&#8217;. The
922 second one can either be a absolute path (in that case the host is
923 localhost:80) or a full path to the request with scheme, netloc port and
924 the path to the script.</p>
925 <p>If the <cite>path</cite> contains a query string it will be used, even if the
926 <cite>query_string</cite> parameter was given. If it does not contain one
927 the <cite>query_string</cite> parameter is used as querystring. In that case
928 it can either be a dict, MultiDict or string.</p>
929 <p>The following options exist:</p>
930 <dl class="last">
931 <dt><cite>method</cite></dt>
932 <dd>The request method. Defaults to <cite>GET</cite></dd>
933 <dt><cite>input_stream</cite></dt>
934 <dd>The input stream. Defaults to an empty read only stream.</dd>
935 <dt><cite>content_type</cite></dt>
936 <dd>The content type for this request. Default is an empty content
937 type.</dd>
938 <dt><cite>content_length</cite></dt>
939 <dd>The value for the content length header. Defaults to 0.</dd>
940 <dt><cite>errors_stream</cite></dt>
941 <dd>The wsgi.errors stream. Defaults to <cite>sys.stderr</cite>.</dd>
942 <dt><cite>multithread</cite></dt>
943 <dd>The multithreaded flag for the WSGI Environment. Defaults to <cite>False</cite>.</dd>
944 <dt><cite>multiprocess</cite></dt>
945 <dd>The multiprocess flag for the WSGI Environment. Defaults to <cite>False</cite>.</dd>
946 <dt><cite>run_once</cite></dt>
947 <dd>The run_once flag for the WSGI Environment. Defaults to <cite>False</cite>.</dd>
948 </dl>
949 </dd>
950 </dl>
951 <dl>
952 <dt><cite id="werkzeug.utils.run_wsgi_app">run_wsgi_app</cite> <tt class="func-signature docutils literal"><span class="pre">(app,</span> <span class="pre">environ,</span> <span class="pre">buffered=False)</span></tt></dt>
953 <dd><p class="first">Return a tuple in the form (app_iter, status, headers) of the
954 application output. This works best if you pass it an application that
955 returns a iterator all the time.</p>
956 <p>Sometimes applications may use the <cite>write()</cite> callable returned
957 by the <cite>start_response</cite> function. This tries to resolve such edge
958 cases automatically. But if you don&#8217;t get the expected output you
959 should set <cite>buffered</cite> to <cite>True</cite> which enforces buffering.</p>
960 <p class="last">If passed an invalid WSGI application the behavior of this function is
961 undefined. Never pass non-conforming WSGI applications to this function.</p>
962 </dd>
963 </dl>
964 </div>
965 <div class="section">
966 <h3 id="helper-functions">Helper Functions</h3>
967 <dl>
968 <dt><cite id="werkzeug.utils.cached_property">cached_property</cite> <tt class="func-signature docutils literal"><span class="pre">(func,</span> <span class="pre">name=None,</span> <span class="pre">doc=None)</span></tt></dt>
969 <dd><p class="first">A decorator that converts a function into a lazy property. The
970 function wrapped is called the first time to retrieve the result
971 and than that calculated result is used the next time you access
972 the value:</p>
973 <div class="syntax"><pre><span class="k">class</span> <span class="nc">Foo</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
974
975 <span class="nd">@cached_property</span>
976 <span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
977 <span class="c"># calculate something important here</span>
978 <span class="k">return</span> <span class="mf">42</span>
979 </pre></div>
980 </dd>
981 </dl>
982 <dl>
983 <dt><cite id="werkzeug.utils.environ_property">environ_property</cite> <tt class="func-signature docutils literal"><span class="pre">(name,</span> <span class="pre">default=None,</span> <span class="pre">load_func=None,</span> <span class="pre">dump_func=None,</span> <span class="pre">read_only=None,</span> <span class="pre">doc=None)</span></tt></dt>
984 <dd><p class="first">Maps request attributes to environment variables. This works not only
985 for the Werzeug request object, but also any other class with an
986 environ attribute:</p>
987 <div class="syntax"><pre><span class="gp">&gt;&gt;&gt; </span><span class="k">class</span> <span class="nc">test_p</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
988 <span class="gp">... </span> <span class="n">environ</span> <span class="o">=</span> <span class="p">{</span> <span class="s">&#39;test&#39;</span><span class="p">:</span> <span class="s">&#39;test&#39;</span> <span class="p">}</span>
989 <span class="gp">... </span> <span class="n">test</span> <span class="o">=</span> <span class="n">environ_property</span><span class="p">(</span><span class="s">&#39;test&#39;</span><span class="p">)</span>
990 <span class="gp">&gt;&gt;&gt; </span><span class="n">var</span> <span class="o">=</span> <span class="n">test_p</span><span class="p">()</span>
991 <span class="gp">&gt;&gt;&gt; </span><span class="n">var</span><span class="o">.</span><span class="n">test</span>
992 <span class="go">test</span>
993 </pre></div>
994 <p>If you pass it a second value it&#8217;s used as default if the key does not
995 exist, the third one can be a converter that takes a value and converts
996 it. If it raises <cite>ValueError</cite> or <cite>TypeError</cite> the default value is used.
997 If no default value is provided <cite>None</cite> is used.</p>
998 <p class="last">Per default the property is read only. You have to explicitly enable it
999 by passing <tt class="docutils literal"><span class="pre">read_only=False</span></tt> to the constructor.</p>
1000 </dd>
1001 </dl>
1002 <dl>
1003 <dt><cite id="werkzeug.utils.header_property">header_property</cite> <tt class="func-signature docutils literal"><span class="pre">(name,</span> <span class="pre">default=None,</span> <span class="pre">load_func=None,</span> <span class="pre">dump_func=None,</span> <span class="pre">read_only=None,</span> <span class="pre">doc=None)</span></tt></dt>
1004 <dd>Like <cite>environ_property</cite> but for headers.</dd>
1005 </dl>
1006 <dl>
1007 <dt><cite id="werkzeug.utils.parse_cookie">parse_cookie</cite> <tt class="func-signature docutils literal"><span class="pre">(header,</span> <span class="pre">charset='utf-8',</span> <span class="pre">errors='ignore')</span></tt></dt>
1008 <dd><p class="first">Parse a cookie. Either from a string or WSGI environ.</p>
1009 <p class="last">Per default encoding errors are ignore. If you want a different behavior
1010 you can set <cite>errors</cite> to <tt class="docutils literal"><span class="pre">'replace'</span></tt> or <tt class="docutils literal"><span class="pre">'strict'</span></tt>. In strict mode a
1011 <cite>HTTPUnicodeError</cite> is raised.</p>
1012 </dd>
1013 </dl>
1014 <dl>
1015 <dt><cite id="werkzeug.utils.dump_cookie">dump_cookie</cite> <tt class="func-signature docutils literal"><span class="pre">(key,</span> <span class="pre">value='',</span> <span class="pre">max_age=None,</span> <span class="pre">expires=None,</span> <span class="pre">path='/',</span> <span class="pre">domain=None,</span> <span class="pre">secure=None,</span> <span class="pre">httponly=False,</span> <span class="pre">charset='utf-8',</span> <span class="pre">sync_expires=True)</span></tt></dt>
1016 <dd><p class="first">Creates a new Set-Cookie header without the <tt class="docutils literal"><span class="pre">Set-Cookie</span></tt> prefix
1017 The parameters are the same as in the cookie Morsel object in the
1018 Python standard library but it accepts unicode data too.</p>
1019 <dl class="last">
1020 <dt>Parameters</dt>
1021 <dd><p class="first"><strong>max_age</strong>: should be a number of seconds, or <cite>None</cite> (default) if
1022 the cookie should last only as long as the client&#8217;s
1023 browser session. Additionally <cite>timedelta</cite> objects
1024 are accepted too.</p>
1025 <p><strong>expires</strong>: should be a <cite>datetime</cite> object or unix timestamp.</p>
1026 <p><strong>path</strong>: limits the cookie to a given path, per default it will
1027 span the whole domain.</p>
1028 <p><strong>domain</strong>: Use this if you want to set a cross-domain cookie. For
1029 example, <tt class="docutils literal"><span class="pre">domain=&quot;.example.com&quot;</span></tt> will set a cookie
1030 that is readable by the domain <tt class="docutils literal"><span class="pre">www.example.com</span></tt>,
1031 <tt class="docutils literal"><span class="pre">foo.example.com</span></tt> etc. Otherwise, a cookie will only
1032 be readable by the domain that set it.</p>
1033 <p><strong>secure</strong>: The cookie will only be available via HTTPS</p>
1034 <p><strong>httponly</strong>: disallow JavaScript to access the cookie. This is an
1035 extension to the cookie standard and probably not
1036 supported by all browsers.</p>
1037 <p><strong>charset</strong>: the encoding for unicode values.</p>
1038 <p class="last"><strong>sync_expires</strong>: automatically set expires if max_age is defined
1039 but expires not.</p>
1040 </dd>
1041 </dl>
1042 </dd>
1043 </dl>
1044 <dl>
1045 <dt><cite id="werkzeug.utils.redirect">redirect</cite> <tt class="func-signature docutils literal"><span class="pre">(location,</span> <span class="pre">code=302)</span></tt></dt>
1046 <dd>Return a response object (a WSGI application) that, if called,
1047 redirects the client to the target location. Supported codes are 301,
1048 302, 303, 305, and 307. 300 is not supported because it&#8217;s not a real
1049 redirect and 304 because it&#8217;s the answer for a request with a request
1050 with defined If-Modified-Since headers.</dd>
1051 </dl>
1052 <dl>
1053 <dt><cite id="werkzeug.utils.append_slash_redirect">append_slash_redirect</cite> <tt class="func-signature docutils literal"><span class="pre">(environ,</span> <span class="pre">code=301)</span></tt></dt>
1054 <dd>Redirect to the same URL but with a slash appended. The behavior
1055 of this function is undefined if the path ends with a slash already.</dd>
1056 </dl>
1057 <dl>
1058 <dt><cite id="werkzeug.utils.import_string">import_string</cite> <tt class="func-signature docutils literal"><span class="pre">(import_name,</span> <span class="pre">silent=False)</span></tt></dt>
1059 <dd><p class="first">Imports an object based on a string. This use useful if you want to
1060 use import paths as endpoints or something similar. An import path can
1061 be specified either in dotted notation (<tt class="docutils literal"><span class="pre">xml.sax.saxutils.escape</span></tt>)
1062 or with a colon as object delimiter (<tt class="docutils literal"><span class="pre">xml.sax.saxutils:escape</span></tt>).</p>
1063 <p>If the <cite>silent</cite> is True the return value will be <cite>None</cite> if the import
1064 fails.</p>
1065 <p class="last"><strong>returns</strong>: imported object</p>
1066 </dd>
1067 </dl>
1068 <dl>
1069 <dt><cite id="werkzeug.utils.find_modules">find_modules</cite> <tt class="func-signature docutils literal"><span class="pre">(import_path,</span> <span class="pre">include_packages=False,</span> <span class="pre">recursive=False)</span></tt></dt>
1070 <dd><p class="first">Find all the modules below a package. This can be useful to
1071 automatically import all views / controllers so that their metaclasses /
1072 function decorators have a chance to register themselves on the
1073 application.</p>
1074 <p>Packages are not returned unless <cite>include_packages</cite> is <cite>True</cite>. This can
1075 also recursively list modules but in that case it will import all the
1076 packages to get the correct load path of that module.</p>
1077 <p class="last"><strong>returns</strong>: generator</p>
1078 </dd>
1079 </dl>
1080 <dl>
1081 <dt><cite id="werkzeug.utils.validate_arguments">validate_arguments</cite> <tt class="func-signature docutils literal"><span class="pre">(func,</span> <span class="pre">args,</span> <span class="pre">kwargs,</span> <span class="pre">drop_extra=True)</span></tt></dt>
1082 <dd><p class="first">Check if the function accepts the arguments and keyword arguments.
1083 Returns a new <tt class="docutils literal"><span class="pre">(args,</span> <span class="pre">kwargs)</span></tt> tuple that can savely be passed to
1084 the function without causing a <cite>TypeError</cite> because the function signature
1085 is incompatible. If <cite>drop_extra</cite> is set to <cite>True</cite> (which is the default)
1086 any extra positional or keyword arguments are dropped automatically.</p>
1087 <p>The exception raised provides three attributes:</p>
1088 <dl>
1089 <dt><cite>missing</cite></dt>
1090 <dd>A set of argument names that the function expected but where
1091 missing.</dd>
1092 <dt><cite>extra</cite></dt>
1093 <dd>A dict of keyword arguments that the function can not handle but
1094 where provided.</dd>
1095 <dt><cite>extra_positional</cite></dt>
1096 <dd>A list of values that where given by positional argument but the
1097 function cannot accept.</dd>
1098 </dl>
1099 <p>This can be useful for decorators that forward user submitted data to
1100 a view function:</p>
1101 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">ArgumentValidationError</span><span class="p">,</span> <span class="n">validate_arguments</span>
1102
1103 <span class="k">def</span> <span class="nf">sanitize</span><span class="p">(</span><span class="n">f</span><span class="p">):</span>
1104 <span class="k">def</span> <span class="nf">proxy</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
1105 <span class="n">data</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">values</span><span class="o">.</span><span class="n">to_dict</span><span class="p">()</span>
1106 <span class="k">try</span><span class="p">:</span>
1107 <span class="n">args</span><span class="p">,</span> <span class="n">kwargs</span> <span class="o">=</span> <span class="n">validate_arguments</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="p">(</span><span class="n">request</span><span class="p">,),</span> <span class="n">data</span><span class="p">)</span>
1108 <span class="k">except</span> <span class="n">ArgumentValidationError</span><span class="p">:</span>
1109 <span class="k">raise</span> <span class="n">BadRequest</span><span class="p">(</span><span class="s">&#39;The browser failed to transmit all &#39;</span>
1110 <span class="s">&#39;the data expected.&#39;</span><span class="p">)</span>
1111 <span class="k">return</span> <span class="n">f</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
1112 <span class="k">return</span> <span class="n">proxy</span>
1113 </pre></div>
1114 </dd>
1115 </dl>
1116 <dl>
1117 <dt><cite id="werkzeug.utils.bind_arguments">bind_arguments</cite> <tt class="func-signature docutils literal"><span class="pre">(func,</span> <span class="pre">args,</span> <span class="pre">kwargs)</span></tt></dt>
1118 <dd>Bind the arguments provided into a dict. When passed a function,
1119 a tuple of arguments and a dict of keyword arguments <cite>bind_arguments</cite>
1120 returns a dict of names as the function would see it. This can be useful
1121 to implement a cache decorator that uses the function arguments to build
1122 the cache key based on the values of the arguments.</dd>
1123 </dl>
1124 <dl>
1125 <dt><cite id="werkzeug.testapp.test_app">test_app</cite> <tt class="func-signature docutils literal"><span class="pre">(environ,</span> <span class="pre">start_response)</span></tt></dt>
1126 <dd>Simple test application that dumps the environment.</dd>
1127 </dl>
1128 </div>
1129
1130 <div style="clear:both"></div>
1131 </div>
1132 <div class="footer">
1133 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
1134 BSD license.
1135 </div>
1136 </div>
1137 </body>
1138 </html>
Binary diff not shown
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>Wrappers // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>Wrappers</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#base-wrappers" id="id1" name="id1">Base Wrappers</a></li>
22 <li><a class="reference" href="#mixin-classes" id="id2" name="id2">Mixin Classes</a></li>
23 <li><a class="reference" href="#note-on-file-uploads" id="id3" name="id3">Note on File Uploads</a></li>
24 </ul>
25 </div>
26
27 <p>The wrappers are simple request and response objects which you can
28 subclass to do whatever you want them to do. The request object contains
29 the information transmitted by the client (webbrowser) and the response
30 object contains all the information sent back to the browser.</p>
31 <p>An important detail is that the request object is created with the WSGI
32 environ and will act as high-level proxy whereas the response object is an
33 actual WSGI application.</p>
34 <p>Like everything else in Werkzeug these objects will work correctly with
35 unicode data. Incoming form data parsed by the response object will be
36 decoded into an unicode object if possible and if it makes sense.</p>
37 <p>You can import all these objects directly from <cite>werkzeug</cite>.</p>
38 <div class="section">
39 <h3 id="base-wrappers">Base Wrappers</h3>
40 <p>These objects implement a common set of operations. They are missing fancy
41 addon functionality like user agent parsing or etag handling. These features
42 are available by mixing in various mixin classes or using <cite>Request</cite> and
43 <cite>Response</cite>.</p>
44 <dl>
45 <dt><strong>class</strong> <cite id="werkzeug.wrappers.BaseRequest">BaseRequest</cite></dt>
46 <dd><p class="first">Very basic request object. This does not implement advanced stuff like
47 entity tag parsing or cache controls. The request object is created with
48 the WSGI environment as first argument and will add itself to the WSGI
49 environment as <tt class="docutils literal"><span class="pre">'werkzeug.request'</span></tt> unless it&#8217;s created with
50 <cite>populate_request</cite> set to False.</p>
51 <p>There are a couple of mixins available that add additional functionality
52 to the request object, there is also a class called <cite>Request</cite> which
53 subclasses <cite>BaseRequest</cite> and all the important mixins.</p>
54 <p>It&#8217;s a good idea to create a custom subclass of the <cite>BaseRequest</cite> and add
55 missing functionality either via mixins or direct implementation. Here
56 an example for such subclasses:</p>
57 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">BaseRequest</span><span class="p">,</span> <span class="n">ETagRequestMixin</span>
58
59 <span class="k">class</span> <span class="nc">Request</span><span class="p">(</span><span class="n">BaseRequest</span><span class="p">,</span> <span class="n">ETagRequestMixin</span><span class="p">):</span>
60 <span class="k">pass</span>
61 </pre></div>
62 <p>Request objects should be considered <em>read only</em>. Even though the object
63 doesn&#8217;t enforce read only access everywhere you should never modify any
64 data on the object itself unless you know exactly what you are doing.</p>
65 <p>Per default the request object will assume all the text data is <cite>utf-8</cite>
66 encoded. Please refer to <a class="reference" href="unicode.html">the unicode chapter</a> for more
67 details about customizing the behavior.</p>
68 <p><strong>Creating Request Objects</strong></p>
69 <dl>
70 <dt><cite id="werkzeug.wrappers.BaseRequest.__init__">__init__</cite> <tt class="func-signature docutils literal"><span class="pre">(environ,</span> <span class="pre">populate_request=True,</span> <span class="pre">shallow=False)</span></tt></dt>
71 <dd><p class="first">Per default the request object will be added to the WSGI
72 enviornment as <cite>werkzeug.request</cite> to support the debugging system.
73 If you don&#8217;t want that, set <cite>populate_request</cite> to <cite>False</cite>.</p>
74 <p class="last">If <cite>shallow</cite> is <cite>True</cite> the environment is initialized as shallow
75 object around the environ. Every operation that would modify the
76 environ in any way (such as consuming form data) raises an exception
77 unless the <cite>shallow</cite> attribute is explicitly set to <cite>False</cite>. This
78 is useful for middlewares where you don&#8217;t want to consume the form
79 data by accident. A shallow request is not populated to the WSGI
80 environment.</p>
81 </dd>
82 </dl>
83 <dl>
84 <dt><cite id="werkzeug.wrappers.BaseRequest.from_values">from_values</cite> <tt class="func-signature docutils literal"><span class="pre">(path='/',</span> <span class="pre">base_url=None,</span> <span class="pre">query_string=None,</span> <span class="pre">**options)</span></tt></dt>
85 <dd><p class="first">Create a new request object based on the values provided. If
86 environ is given missing values are filled from there. This method is
87 useful for small scripts when you need to simulate a request from an URL.
88 Do not use this method for unittesting, there is a full featured client
89 object in <cite>werkzeug.test</cite> that allows to create multipart requests
90 etc.</p>
91 <p>This accepts the same options as the <cite>create_environ</cite> function from the
92 utils module and additionally an <cite>environ</cite> parameter that can contain
93 values which will override the values from dict returned by
94 <cite>create_environ</cite>.</p>
95 <p>Additionally a dict passed to <cite>query_string</cite> will be encoded in the
96 request class charset.</p>
97 <p class="last"><strong>returns</strong>: request object</p>
98 </dd>
99 </dl>
100 <dl>
101 <dt><cite id="werkzeug.wrappers.BaseRequest.application">application</cite> <tt class="func-signature docutils literal"><span class="pre">(f)</span></tt></dt>
102 <dd><p class="first">Decorate a function as responder that accepts the request as
103 first argument. This works like the <cite>responder</cite> decorator but
104 the function is passed the request object as first argument:</p>
105 <div class="syntax"><pre><span class="nd">@Request</span><span class="o">.</span><span class="n">application</span>
106 <span class="k">def</span> <span class="nf">my_wsgi_app</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
107 <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="s">&#39;Hello World!&#39;</span><span class="p">)</span>
108 </pre></div>
109 </dd>
110 </dl>
111 <p><strong>Properties</strong></p>
112 <dl class="last">
113 <dt><cite>path</cite></dt>
114 <dd>The current path requested, relative to the position where the WSGI
115 application is mounted (<cite>PATH_INFO</cite>). It will contain a leading slash and
116 will be at least a string with a single slash when accessing the URL root.</dd>
117 <dt><cite>script_root</cite></dt>
118 <dd>The root path for the script (<cite>SCRIPT_NAME</cite>). Does not contain a trailing
119 slash.</dd>
120 <dt><cite>url</cite></dt>
121 <dd>The full URL for the current request.</dd>
122 <dt><cite>base_url</cite></dt>
123 <dd>The current full URL without the query string.</dd>
124 <dt><cite>url_root</cite></dt>
125 <dd>The current URL up to the script root.</dd>
126 <dt><cite>host_url</cite></dt>
127 <dd>The current URL for the host.</dd>
128 <dt><cite>host</cite></dt>
129 <dd>The current hostname, without scheme.</dd>
130 <dt><cite>is_secure</cite></dt>
131 <dd>True if this is an HTTPS request.</dd>
132 <dt><cite>is_multithread</cite></dt>
133 <dd>True if this request was created in a multithreaded environment.</dd>
134 <dt><cite>is_multiprocess</cite></dt>
135 <dd>True if this request was created in a forking environment.</dd>
136 <dt><cite>is_run_once</cite></dt>
137 <dd>True if this request was created on the command line, a CGI script or a
138 similar environment.</dd>
139 <dt><cite>is_xhr</cite></dt>
140 <dd>True if the request was triggered via an JavaScript XMLHttpRequest.
141 This only works with libraries that support the X-Requested-With
142 header and set it to &#8220;XMLHttpRequest&#8221;. Libraries that do that are
143 prototype, jQuery and Mochikit and probably some more.</dd>
144 <dt><cite>method</cite></dt>
145 <dd>The request method. <cite>GET</cite>, <cite>POST</cite> etc.</dd>
146 <dt><cite>args</cite></dt>
147 <dd>A dictionary-like object containing all given HTTP GET parameters. See
148 the <cite>MultiDict</cite> documentation in the <a class="reference" href="utils.html">utils</a> section.</dd>
149 <dt><cite>form</cite></dt>
150 <dd><p class="first">A dictionary-like object containing all given HTTP POST parameters. See
151 the <cite>MultiDict</cite> documentation in the <a class="reference" href="utils.html">utils</a> section.</p>
152 <p class="last">This dict does not contain uploaded files, see <cite>files</cite> regarding that.</p>
153 </dd>
154 <dt><cite>values</cite></dt>
155 <dd>An immutable dictionary-like object containing both the <cite>args</cite> and <cite>form</cite>
156 values. See the <cite>CombinedMultiDict</cite> documentation in the <a class="reference" href="utils.html">utils</a>
157 section.</dd>
158 <dt><cite>cookies</cite></dt>
159 <dd>A dictionary with the submitted cookie values.</dd>
160 <dt><cite>files</cite></dt>
161 <dd><p class="first">A dictionary-like object containing all uploaded files. Each key in
162 <cite>files</cite> is the name from the <tt class="docutils literal"><span class="pre">&lt;input</span> <span class="pre">type=&quot;file&quot;</span> <span class="pre">name=&quot;&quot;</span> <span class="pre">/&gt;</span></tt>. Each
163 value in <cite>files</cite> is a Werkzeug <cite>FileStorage</cite> object with the following
164 members:</p>
165 <ul class="simple">
166 <li><cite>filename</cite> - The name of the uploaded file, as a Python string.</li>
167 <li><cite>type</cite> - The content type of the uploaded file.</li>
168 <li><cite>data</cite> - The raw content of the uploaded file.</li>
169 <li><cite>read()</cite> - Read from the stream.</li>
170 </ul>
171 <p>Note that <cite>files</cite> will only contain data if the request method was POST
172 and the <tt class="docutils literal"><span class="pre">&lt;form&gt;</span></tt> that posted to the request had
173 <tt class="docutils literal"><span class="pre">enctype=&quot;multipart/form-data&quot;</span></tt>. It will be empty otherwise.</p>
174 <p class="last">See the <cite>MultiDict</cite> / <cite>FileStorage</cite> documentation in the <a class="reference" href="utils.html">utils</a>
175 section for more details about the used data structure.</p>
176 </dd>
177 <dt><cite>environ</cite></dt>
178 <dd>The WSGI environment used to create the request object.</dd>
179 <dt><cite>stream</cite></dt>
180 <dd>The buffered stream with incoming data from the webbrowser if the
181 submitted data was not multipart or URL-encoded form data.</dd>
182 <dt><cite>input_stream</cite></dt>
183 <dd>The input stream provided by the client. Used internally by the form
184 data parser. Reading from this stream must never be mixed with
185 accessing <cite>stream</cite>, <cite>data</cite>, <cite>files</cite>, or <cite>form</cite>, because then it&#8217;s not
186 guaranteed that more data is requested from the client than expected.
187 Never read beyond <tt class="docutils literal"><span class="pre">environ['CONTENT_LENGTH']</span></tt>.</dd>
188 <dt><cite>data</cite></dt>
189 <dd>Accessing this the first time reads the whole <cite>stream</cite> and stores it. Keep
190 in mind that this does not read the whole WSGI input stream like Django
191 does.</dd>
192 <dt><cite>remote_addr</cite></dt>
193 <dd>The remote address for the user that created this request. If the class
194 variable <cite>is_behind_proxy</cite> is set to <cite>True</cite> (either by subclassing the
195 process or overriding this variable on the instance) it will try to get
196 the value from the <cite>X_HTTP_FORWARDED_FOR</cite> header. Keep in mind that this
197 is disabled by default because unless you are really behind a proxy this
198 is a security problem.</dd>
199 <dt><cite>access_route</cite></dt>
200 <dd>If you are behind a proxy server this will list all the IP addresses that
201 take place in the request. The end user IP address is the first one in
202 the list, the last proxy server is the last item in the list. This also
203 works if the <cite>is_behind_proxy</cite> class variable is set to <cite>False</cite>.</dd>
204 </dl>
205 </dd>
206 </dl>
207 <dl>
208 <dt><strong>class</strong> <cite id="werkzeug.wrappers.BaseResponse">BaseResponse</cite></dt>
209 <dd><p class="first">Base response class. The most important fact about a response object
210 is that it&#8217;s a regular WSGI application. It&#8217;s initialized with a couple
211 of response parameters (headers, body, status code etc.) and will start a
212 valid WSGI response when called with the environ and start response
213 callable.</p>
214 <p>Because it&#8217;s a WSGI application itself processing usually ends before the
215 actual response is sent to the server. This helps debugging systems
216 because they can catch all the exceptions before responses are started.</p>
217 <p>Here a small example WSGI application that takes advantage of the
218 response objects:</p>
219 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">BaseResponse</span> <span class="k">as</span> <span class="n">Response</span>
220
221 <span class="k">def</span> <span class="nf">index</span><span class="p">():</span>
222 <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="s">&#39;Index page&#39;</span><span class="p">)</span>
223
224 <span class="k">def</span> <span class="nf">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
225 <span class="n">path</span> <span class="o">=</span> <span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&#39;PATH_INFO&#39;</span><span class="p">)</span> <span class="ow">or</span> <span class="s">&#39;/&#39;</span>
226 <span class="k">if</span> <span class="n">path</span> <span class="o">==</span> <span class="s">&#39;/&#39;</span><span class="p">:</span>
227 <span class="n">response</span> <span class="o">=</span> <span class="n">index</span><span class="p">()</span>
228 <span class="k">else</span><span class="p">:</span>
229 <span class="n">response</span> <span class="o">=</span> <span class="n">Response</span><span class="p">(</span><span class="s">&#39;Not Found&#39;</span><span class="p">,</span> <span class="n">status</span><span class="o">=</span><span class="mf">404</span><span class="p">)</span>
230 <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">)</span>
231 </pre></div>
232 <p>Like <cite>BaseRequest</cite> which object is lacking a lot of functionality
233 implemented in mixins. This gives you a better control about the actual
234 API of your response objects, so you can create subclasses and add custom
235 functionality. A full featured response object is available as <cite>Response</cite>
236 which implements a couple of useful mixins.</p>
237 <p>To enforce a new type of already existing responses you can use the
238 <cite>force_type</cite> method. This is useful if you&#8217;re working with different
239 subclasses of response objects and you want to post process them with a
240 know interface.</p>
241 <p>Per default the request object will assume all the text data is <cite>utf-8</cite>
242 encoded. Please refer to <a class="reference" href="unicode.html">the unicode chapter</a> for more
243 details about customizing the behavior.</p>
244 <p><strong>Creating Response Objects</strong></p>
245 <dl>
246 <dt><cite id="werkzeug.wrappers.BaseResponse.__init__">__init__</cite> <tt class="func-signature docutils literal"><span class="pre">(response=None,</span> <span class="pre">status=None,</span> <span class="pre">headers=None,</span> <span class="pre">mimetype=None,</span> <span class="pre">content_type=None)</span></tt></dt>
247 <dd><p class="first">Response can be any kind of iterable or string. If it&#8217;s a string
248 it&#8217;s considered being an iterable with one item which is the string
249 passed. Headers can be a list of tuples or a <cite>Headers</cite> object.</p>
250 <p class="last">Special note for <cite>mimetype</cite> and <cite>content_type</cite>. For most mime types
251 <cite>mimetype</cite> and <cite>content_type</cite> work the same, the difference affects
252 only &#8216;text&#8217; mimetypes. If the mimetype passed with <cite>mimetype</cite> is a
253 mimetype starting with <cite>text/</cite> it becomes a charset parameter defined
254 with the charset of the response object. In constrast the
255 <cite>content_type</cite> parameter is always added as header unmodified.</p>
256 </dd>
257 </dl>
258 <dl>
259 <dt><cite id="werkzeug.wrappers.BaseResponse.force_type">force_type</cite> <tt class="func-signature docutils literal"><span class="pre">(response,</span> <span class="pre">environ=None)</span></tt></dt>
260 <dd><p class="first">Enforce that the WSGI response is a response object of the current
261 type. Werkzeug will use the <cite>BaseResponse</cite> internally in many
262 situations like the exceptions. If you call <cite>get_response</cite> on an
263 exception you will get back a regular <cite>BaseResponse</cite> object, even if
264 you are using a custom subclass.</p>
265 <p>This method can enforce a given response type, and it will also
266 convert arbitrary WSGI callables into response objects if an environ
267 is provided:</p>
268 <div class="syntax"><pre><span class="c"># convert a Werkzeug response object into an instance of the</span>
269 <span class="c"># MyResponseClass subclass.</span>
270 <span class="n">response</span> <span class="o">=</span> <span class="n">MyResponseClass</span><span class="o">.</span><span class="n">force_type</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
271
272 <span class="c"># convert any WSGI application into a response object</span>
273 <span class="n">response</span> <span class="o">=</span> <span class="n">MyResponseClass</span><span class="o">.</span><span class="n">force_type</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">environ</span><span class="p">)</span>
274 </pre></div>
275 <p>This is especially useful if you want to post-process responses in
276 the main dispatcher and use functionality provided by your subclass.</p>
277 <p class="last">Keep in mind that this will modify response objects in place if
278 possible!</p>
279 </dd>
280 </dl>
281 <dl>
282 <dt><cite id="werkzeug.wrappers.BaseResponse.from_app">from_app</cite> <tt class="func-signature docutils literal"><span class="pre">(app,</span> <span class="pre">environ,</span> <span class="pre">buffered=False)</span></tt></dt>
283 <dd>Create a new response object from an application output. This
284 works best if you pass it an application that returns a generator all
285 the time. Sometimes applications may use the <cite>write()</cite> callable
286 returned by the <cite>start_response</cite> function. This tries to resolve such
287 edge cases automatically. But if you don&#8217;t get the expected output
288 you should set <cite>buffered</cite> to <cite>True</cite> which enforces buffering.</dd>
289 </dl>
290 <p><strong>Properties</strong></p>
291 <dl>
292 <dt><cite>response</cite></dt>
293 <dd>The application iterator. If constructed from a string this will
294 be a list, otherwise the object provided as application iterator.</dd>
295 <dt><cite>headers</cite></dt>
296 <dd>A <cite>Headers</cite> object representing the response headers.</dd>
297 <dt><cite>status</cite></dt>
298 <dd>The response status as string.</dd>
299 <dt><cite>status_code</cite></dt>
300 <dd>The response status as integer.</dd>
301 <dt><cite>data</cite></dt>
302 <dd>When accessed the response iterator is buffered and, encoded and
303 returned as bytestring.</dd>
304 <dt><cite>header_list</cite></dt>
305 <dd>Read only list that contains the current list for the headers in
306 the response encoding.</dd>
307 <dt><cite>is_streamed</cite></dt>
308 <dd><p class="first">If the response is streamed (the response is not a sequence) this
309 property is <cite>True</cite>. In this case streamed means that there is no
310 information about the number of iterations. This is usully <cite>True</cite>
311 if a generator is passed to the response object.</p>
312 <p class="last">This is useful for checking before applying some sort of post
313 filtering that should not take place for streamed responses.</p>
314 </dd>
315 </dl>
316 <p><strong>Methods</strong></p>
317 <dl>
318 <dt><cite id="werkzeug.wrappers.BaseResponse.iter_encoded">iter_encoded</cite> <tt class="func-signature docutils literal"><span class="pre">(charset=None)</span></tt></dt>
319 <dd>Iter the response encoded with the encoding specified. If no
320 encoding is given the encoding from the class is used. Note that
321 this does not encode data that is already a bytestring.</dd>
322 </dl>
323 <dl>
324 <dt><cite id="werkzeug.wrappers.BaseResponse.set_cookie">set_cookie</cite> <tt class="func-signature docutils literal"><span class="pre">(key,</span> <span class="pre">value='',</span> <span class="pre">max_age=None,</span> <span class="pre">expires=None,</span> <span class="pre">path='/',</span> <span class="pre">domain=None,</span> <span class="pre">secure=None,</span> <span class="pre">httponly=False)</span></tt></dt>
325 <dd><p class="first">Sets a cookie. The parameters are the same as in the cookie <cite>Morsel</cite>
326 object in the Python standard library but it accepts unicode data too:</p>
327 <ul class="last">
328 <li><dl class="first">
329 <dt><cite>max_age</cite> should be a number of seconds, or <cite>None</cite> (default) if the</dt>
330 <dd><p class="first last">cookie should last only as long as the client’s browser session.</p>
331 </dd>
332 </dl>
333 </li>
334 <li><p class="first"><cite>expires</cite> should be a <cite>datetime</cite> object or UNIX timestamp.</p>
335 </li>
336 <li><p class="first">Use <cite>domain</cite> if you want to set a cross-domain cookie. For example,
337 <tt class="docutils literal"><span class="pre">domain=&quot;.example.com&quot;</span></tt> will set a cookie that is readable by the
338 domain <tt class="docutils literal"><span class="pre">www.example.com</span></tt>, <tt class="docutils literal"><span class="pre">foo.example.com</span></tt> etc. Otherwise, a
339 cookie will only be readable by the domain that set it.</p>
340 </li>
341 <li><p class="first"><cite>path</cite> limits the cookie to a given path, per default it will span
342 the whole domain.</p>
343 </li>
344 </ul>
345 </dd>
346 </dl>
347 <dl>
348 <dt><cite id="werkzeug.wrappers.BaseResponse.delete_cookie">delete_cookie</cite> <tt class="func-signature docutils literal"><span class="pre">(key,</span> <span class="pre">path='/',</span> <span class="pre">domain=None)</span></tt></dt>
349 <dd>Delete a cookie. Fails silently if key doesn&#8217;t exist.</dd>
350 </dl>
351 <dl>
352 <dt><cite id="werkzeug.wrappers.BaseResponse.fix_headers">fix_headers</cite> <tt class="func-signature docutils literal"><span class="pre">(environ)</span></tt></dt>
353 <dd>This is automatically called right before the response is started
354 and should fix common mistakes in headers. For example location
355 headers are joined with the root URL here.</dd>
356 </dl>
357 <dl>
358 <dt><cite id="werkzeug.wrappers.BaseResponse.close">close</cite> <tt class="func-signature docutils literal"><span class="pre">()</span></tt></dt>
359 <dd>Close the wrapped response if possible.</dd>
360 </dl>
361 <dl>
362 <dt><cite id="werkzeug.wrappers.BaseResponse.freeze">freeze</cite> <tt class="func-signature docutils literal"><span class="pre">()</span></tt></dt>
363 <dd>Call this method if you want to make your response object ready for
364 pickeling. This buffers the generator if there is one.</dd>
365 </dl>
366 <dl class="last">
367 <dt><cite id="werkzeug.wrappers.BaseResponse.__call__">__call__</cite> <tt class="func-signature docutils literal"><span class="pre">(environ,</span> <span class="pre">start_response)</span></tt></dt>
368 <dd>Process this response as WSGI application.</dd>
369 </dl>
370 </dd>
371 </dl>
372 </div>
373 <div class="section">
374 <h3 id="mixin-classes">Mixin Classes</h3>
375 <p>Werkzeug also provides helper mixins for various HTTP related functionality
376 such as etags, cache control, user agents etc. When subclassing you can
377 mix those classes in to extend the functionality of the <cite>BaseRequest</cite> or
378 <cite>BaseResponse</cite> object. Here a small example for a request object that
379 parses accept headers:</p>
380 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">BaseRequest</span><span class="p">,</span> <span class="n">AcceptMixin</span>
381
382 <span class="k">class</span> <span class="nc">Request</span><span class="p">(</span><span class="n">BaseRequest</span><span class="p">,</span> <span class="n">AcceptMixin</span><span class="p">):</span>
383 <span class="k">pass</span>
384 </pre></div>
385 <p>The <cite>Request</cite> and <cite>Response</cite> classes subclass the <cite>BaseRequest</cite> and
386 <cite>BaseResponse</cite> classes and implement all the mixins Werkzeug provides:</p>
387 <dl>
388 <dt><strong>class</strong> <cite id="werkzeug.wrappers.Request">Request</cite></dt>
389 <dd><p class="first">Full featured request object implementing the following mixins:</p>
390 <ul class="last simple">
391 <li><cite>AcceptMixin</cite> for accept header parsing</li>
392 <li><cite>ETagRequestMixin</cite> for etag and cache control handling</li>
393 <li><cite>UserAgentMixin</cite> for user agent introspection</li>
394 <li><cite>AuthorizationMixin</cite> for http auth handling</li>
395 </ul>
396 </dd>
397 </dl>
398 <dl>
399 <dt><strong>class</strong> <cite id="werkzeug.wrappers.Response">Response</cite></dt>
400 <dd><p class="first">Full featured response object implementing the following mixins:</p>
401 <ul class="last simple">
402 <li><cite>ETagResponseMixin</cite> for etag and cache control handling</li>
403 <li><cite>ResponseStreamMixin</cite> to add support for the <cite>stream</cite> property</li>
404 <li><cite>CommonResponseDescriptorsMixin</cite> for various HTTP descriptors</li>
405 <li><cite>WWWAuthenticateMixin</cite> for HTTP authentication support</li>
406 </ul>
407 </dd>
408 </dl>
409 <dl>
410 <dt><strong>class</strong> <cite id="werkzeug.wrappers.AcceptMixin">AcceptMixin</cite></dt>
411 <dd><p class="first">A mixin for classes with an <cite>environ</cite> attribute to get and all the HTTP
412 accept headers as <cite>Accept</cite> objects. This can be mixed in request objects
413 or any other object that has a WSGI environ available as <cite>environ</cite>.</p>
414 <dl>
415 <dt><cite id="werkzeug.wrappers.AcceptMixin.accept_mimetypes">accept_mimetypes</cite></dt>
416 <dd>List of mimetypes this client supports.</dd>
417 </dl>
418 <dl>
419 <dt><cite id="werkzeug.wrappers.AcceptMixin.accept_charsets">accept_charsets</cite></dt>
420 <dd>List of charsets this client supports.</dd>
421 </dl>
422 <dl>
423 <dt><cite id="werkzeug.wrappers.AcceptMixin.accept_encodings">accept_encodings</cite></dt>
424 <dd>List of encodings this client accepts. Encodings in a HTTP term
425 are compression encodings such as gzip. For charsets have a look at
426 <cite>accept_charset</cite>.</dd>
427 </dl>
428 <dl>
429 <dt><cite id="werkzeug.wrappers.AcceptMixin.accept_languages">accept_languages</cite></dt>
430 <dd>List of languages this client accepts.</dd>
431 </dl>
432 <p class="last">All this properties store <cite>Accept</cite> objects as documented in the
433 <a class="reference" href="utils.html">utils</a> section.</p>
434 </dd>
435 </dl>
436 <dl>
437 <dt><strong>class</strong> <cite id="werkzeug.wrappers.AuthorizationMixin">AuthorizationMixin</cite></dt>
438 <dd><p class="first">Adds an <cite>authorization</cite> property that represents the parsed value of
439 the <cite>Authorization</cite> header as <cite>Authorization</cite> object.</p>
440 <dl class="last">
441 <dt><cite id="werkzeug.wrappers.AuthorizationMixin.authorization">authorization</cite></dt>
442 <dd>The <cite>Authorization</cite> object in parsed form.</dd>
443 </dl>
444 </dd>
445 </dl>
446 <dl>
447 <dt><strong>class</strong> <cite id="werkzeug.wrappers.ETagRequestMixin">ETagRequestMixin</cite></dt>
448 <dd><p class="first">Add entity tag and cache descriptors to a request object or object with
449 an WSGI environment available as <cite>environ</cite>. This not only provides
450 access to etags but also to the cache control header.</p>
451 <dl>
452 <dt><cite id="werkzeug.wrappers.ETagRequestMixin.cache_control">cache_control</cite></dt>
453 <dd>A <cite>CacheControl</cite> object for the incoming cache control headers.</dd>
454 </dl>
455 <dl>
456 <dt><cite id="werkzeug.wrappers.ETagRequestMixin.if_match">if_match</cite></dt>
457 <dd>An object containing all the etags in the <cite>If-Match</cite> header.</dd>
458 </dl>
459 <dl>
460 <dt><cite id="werkzeug.wrappers.ETagRequestMixin.if_none_match">if_none_match</cite></dt>
461 <dd>An object containing all the etags in the <cite>If-None-Match</cite> header.</dd>
462 </dl>
463 <dl>
464 <dt><cite id="werkzeug.wrappers.ETagRequestMixin.if_modified_since">if_modified_since</cite></dt>
465 <dd>The parsed <cite>If-Modified-Since</cite> header as datetime object.</dd>
466 </dl>
467 <dl>
468 <dt><cite id="werkzeug.wrappers.ETagRequestMixin.if_unmodified_since">if_unmodified_since</cite></dt>
469 <dd>The parsed <cite>If-Unmodified-Since</cite> header as datetime object.</dd>
470 </dl>
471 <p class="last">All the used data structures are documented in the <a class="reference" href="utils.html">utils</a> section.</p>
472 </dd>
473 </dl>
474 <dl>
475 <dt><strong>class</strong> <cite id="werkzeug.wrappers.ETagResponseMixin">ETagResponseMixin</cite></dt>
476 <dd><p class="first">Adds extra functionality to a response object for etag and cache
477 handling. This mixin requires an object with at least a <cite>headers</cite>
478 object that implements a dict like interface similar to <cite>Headers</cite>.</p>
479 <dl>
480 <dt><cite id="werkzeug.wrappers.ETagResponseMixin.cache_control">cache_control</cite></dt>
481 <dd>The Cache-Control general-header field is used to specify
482 directives that MUST be obeyed by all caching mechanisms along the
483 request/response chain.</dd>
484 </dl>
485 <dl>
486 <dt><cite id="werkzeug.wrappers.ETagResponseMixin.make_conditional">make_conditional</cite> <tt class="func-signature docutils literal"><span class="pre">(request_or_environ)</span></tt></dt>
487 <dd><p class="first">Make the response conditional to the request. This method works
488 best if an etag was defined for the response already. The <cite>add_etag</cite>
489 method can be used to do that. If called without etag just the date
490 header is set.</p>
491 <p>This does nothing if the request method in the request or enviorn is
492 anything but GET or HEAD.</p>
493 <p>It does not remove the body of the response because that&#8217;s something
494 the <cite>__call__</cite> function does for us automatically.</p>
495 <p class="last">Returns self so that you can do <tt class="docutils literal"><span class="pre">return</span> <span class="pre">resp.make_conditional(req)</span></tt>
496 but modifies the object in-place.</p>
497 </dd>
498 </dl>
499 <dl>
500 <dt><cite id="werkzeug.wrappers.ETagResponseMixin.add_etag">add_etag</cite> <tt class="func-signature docutils literal"><span class="pre">(overwrite=False,</span> <span class="pre">weak=False)</span></tt></dt>
501 <dd>Add an etag for the current response if there is none yet.</dd>
502 </dl>
503 <dl>
504 <dt><cite id="werkzeug.wrappers.ETagResponseMixin.set_etag">set_etag</cite> <tt class="func-signature docutils literal"><span class="pre">(etag,</span> <span class="pre">weak=False)</span></tt></dt>
505 <dd>Set the etag, and override the old one if there was one.</dd>
506 </dl>
507 <dl>
508 <dt><cite id="werkzeug.wrappers.ETagResponseMixin.get_etag">get_etag</cite> <tt class="func-signature docutils literal"><span class="pre">()</span></tt></dt>
509 <dd>Return a tuple in the form <tt class="docutils literal"><span class="pre">(etag,</span> <span class="pre">is_weak)</span></tt>. If there is no
510 ETag the return value is <tt class="docutils literal"><span class="pre">(None,</span> <span class="pre">None)</span></tt>.</dd>
511 </dl>
512 <dl class="last">
513 <dt><cite id="werkzeug.wrappers.ETagResponseMixin.freeze">freeze</cite> <tt class="func-signature docutils literal"><span class="pre">(no_etag=False)</span></tt></dt>
514 <dd>Call this method if you want to make your response object ready for
515 pickeling. This buffers the generator if there is one. This also
516 sets the etag unless <cite>no_etag</cite> is set to <cite>True</cite>.</dd>
517 </dl>
518 </dd>
519 </dl>
520 <dl>
521 <dt><strong>class</strong> <cite id="werkzeug.wrappers.ResponseStreamMixin">ResponseStreamMixin</cite></dt>
522 <dd><p class="first">Mixin for <cite>BaseRequest</cite> subclasses. Classes that inherit from this
523 mixin will automatically get a <cite>stream</cite> property that provides a
524 write-only interface to the response iterable.</p>
525 <dl class="last">
526 <dt><cite id="werkzeug.wrappers.ResponseStreamMixin.stream">stream</cite></dt>
527 <dd>The response iterable as write-only stream.</dd>
528 </dl>
529 </dd>
530 </dl>
531 <dl>
532 <dt><strong>class</strong> <cite id="werkzeug.wrappers.CommonResponseDescriptorsMixin">CommonResponseDescriptorsMixin</cite></dt>
533 <dd><p class="first">A mixin for <cite>BaseResponse</cite> subclasses. Response objects that mix this
534 class in will automatically get descriptors for a couple of HTTP headers
535 with automatic type conversion.</p>
536 <dl>
537 <dt><cite id="werkzeug.wrappers.CommonResponseDescriptorsMixin.mimetype">mimetype</cite></dt>
538 <dd>The mimetype (content type without charset etc.)</dd>
539 </dl>
540 <dl>
541 <dt><cite id="werkzeug.wrappers.CommonResponseDescriptorsMixin.location">location</cite></dt>
542 <dd>The Location response-header field is used to redirect the recipient
543 to a location other than the Request-URI for completion of the request
544 or identification of a new resource.</dd>
545 </dl>
546 <dl>
547 <dt><cite id="werkzeug.wrappers.CommonResponseDescriptorsMixin.age">age</cite></dt>
548 <dd><p class="first">The Age response-header field conveys the sender&#8217;s estimate of the
549 amount of time since the response (or its revalidation) was
550 generated at the origin server.</p>
551 <p class="last">Age values are non-negative decimal integers, representing time in
552 seconds.</p>
553 </dd>
554 </dl>
555 <dl>
556 <dt><cite id="werkzeug.wrappers.CommonResponseDescriptorsMixin.content_type">content_type</cite></dt>
557 <dd>The Content-Type entity-header field indicates the media type of the
558 entity-body sent to the recipient or, in the case of the HEAD method,
559 the media type that would have been sent had the request been a GET.</dd>
560 </dl>
561 <dl>
562 <dt><cite id="werkzeug.wrappers.CommonResponseDescriptorsMixin.content_length">content_length</cite></dt>
563 <dd>The Content-Length entity-header field indicates the size of the
564 entity-body, in decimal number of OCTETs, sent to the recipient or,
565 in the case of the HEAD method, the size of the entity-body that would
566 have been sent had the request been a GET.</dd>
567 </dl>
568 <dl>
569 <dt><cite id="werkzeug.wrappers.CommonResponseDescriptorsMixin.content_location">content_location</cite></dt>
570 <dd>The Content-Location entity-header field MAY be used to supply the
571 resource location for the entity enclosed in the message when that
572 entity is accessible from a location separate from the requested
573 resource&#8217;s URI.</dd>
574 </dl>
575 <dl>
576 <dt><cite id="werkzeug.wrappers.CommonResponseDescriptorsMixin.content_encoding">content_encoding</cite></dt>
577 <dd>The Content-Encoding entity-header field is used as a modifier to the
578 media-type. When present, its value indicates what additional content
579 codings have been applied to the entity-body, and thus what decoding
580 mechanisms must be applied in order to obtain the media-type
581 referenced by the Content-Type header field.</dd>
582 </dl>
583 <dl>
584 <dt><cite id="werkzeug.wrappers.CommonResponseDescriptorsMixin.content_language">content_language</cite></dt>
585 <dd>The Content-Language entity-header field describes the natural
586 language(s) of the intended audience for the enclosed entity. Note
587 that this might not be equivalent to all the languages used within
588 the entity-body.</dd>
589 </dl>
590 <dl>
591 <dt><cite id="werkzeug.wrappers.CommonResponseDescriptorsMixin.content_md5">content_md5</cite></dt>
592 <dd>The Content-MD5 entity-header field, as defined in RFC 1864, is an
593 MD5 digest of the entity-body for the purpose of providing an
594 end-to-end message integrity check (MIC) of the entity-body. (Note:
595 a MIC is good for detecting accidental modification of the
596 entity-body in transit, but is not proof against malicious attacks.)</dd>
597 </dl>
598 <dl>
599 <dt><cite id="werkzeug.wrappers.CommonResponseDescriptorsMixin.date">date</cite></dt>
600 <dd>The Date general-header field represents the date and time at which
601 the message was originated, having the same semantics as orig-date
602 in RFC 822.</dd>
603 </dl>
604 <dl>
605 <dt><cite id="werkzeug.wrappers.CommonResponseDescriptorsMixin.expires">expires</cite></dt>
606 <dd>The Expires entity-header field gives the date/time after which the
607 response is considered stale. A stale cache entry may not normally be
608 returned by a cache.</dd>
609 </dl>
610 <dl>
611 <dt><cite id="werkzeug.wrappers.CommonResponseDescriptorsMixin.last_modified">last_modified</cite></dt>
612 <dd>The Last-Modified entity-header field indicates the date and time at
613 which the origin server believes the variant was last modified.</dd>
614 </dl>
615 <dl>
616 <dt><cite id="werkzeug.wrappers.CommonResponseDescriptorsMixin.retry_after">retry_after</cite></dt>
617 <dd><p class="first">The Retry-After response-header field can be used with a 503 (Service
618 Unavailable) response to indicate how long the service is expected
619 to be unavailable to the requesting client.</p>
620 <p class="last">Time in seconds until expiration or date.</p>
621 </dd>
622 </dl>
623 <dl>
624 <dt><cite id="werkzeug.wrappers.CommonResponseDescriptorsMixin.vary">vary</cite></dt>
625 <dd>The Vary field value indicates the set of request-header fields that
626 fully determines, while the response is fresh, whether a cache is
627 permitted to use the response to reply to a subsequent request
628 without revalidation.</dd>
629 </dl>
630 <dl class="last">
631 <dt><cite id="werkzeug.wrappers.CommonResponseDescriptorsMixin.allow">allow</cite></dt>
632 <dd>The Allow entity-header field lists the set of methods supported
633 by the resource identified by the Request-URI. The purpose of this
634 field is strictly to inform the recipient of valid methods
635 associated with the resource. An Allow header field MUST be
636 present in a 405 (Method Not Allowed) response.</dd>
637 </dl>
638 </dd>
639 </dl>
640 <dl>
641 <dt><strong>class</strong> <cite id="werkzeug.wrappers.WWWAuthenticateMixin">WWWAuthenticateMixin</cite></dt>
642 <dd><p class="first">Adds a <cite>www_authenticate</cite> property to a response object.</p>
643 <dl class="last">
644 <dt><cite id="werkzeug.wrappers.WWWAuthenticateMixin.www_authenticate">www_authenticate</cite></dt>
645 <dd>The <tt class="docutils literal"><span class="pre">WWW-Authenticate</span></tt> header in a parsed form.</dd>
646 </dl>
647 </dd>
648 </dl>
649 </div>
650 <div class="section">
651 <h3 id="note-on-file-uploads">Note on File Uploads</h3>
652 <p>File uploads are a tricky thing in general. Per default all the file uploads
653 go into temporary files on the filesystem and not the memory of the current
654 process to avoid high memory usage. You could also change that to store the
655 data somewhere else by implementing an object that implements the python IO
656 protocol (see <cite>StringIO</cite>) for both writing and reading.</p>
657 <p>Then you have to subclass a request object and override <cite>_get_file_stream</cite>:</p>
658 <dl>
659 <dt><cite id="werkzeug.wrappers.BaseRequest._get_file_stream">_get_file_stream</cite> <tt class="func-signature docutils literal"><span class="pre">()</span></tt></dt>
660 <dd><p class="first">Called to get a stream for the file upload.</p>
661 <p>This must provide a file-like class with <cite>read()</cite>, <cite>readline()</cite>
662 and <cite>seek()</cite> methods that is both writeable and readable.</p>
663 <p class="last">The default implementation returns a temporary file.</p>
664 </dd>
665 </dl>
666 </div>
667
668 <div style="clear:both"></div>
669 </div>
670 <div class="footer">
671 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
672 BSD license.
673 </div>
674 </div>
675 </body>
676 </html>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1 <html>
2 <head>
3 <title>How WSGI Works // Werkzeug Documentation</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <link rel="stylesheet" type="text/css" href="style.css">
6 <link rel="stylesheet" type="text/css" href="pygments.css">
7 <link rel="stylesheet" type="text/css" media="print" href="print.css">
8 <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
9 </head>
10 <body>
11 <div class="page">
12 <div class="header">
13 <h1><a href="index.html"><span>Werkzeug</span></a></h1>
14 <p><span>The Swiss Army Knife For Python Web Developers</span></p>
15 </div>
16 <div class="body">
17 <h2>How WSGI Works</h2>
18
19 <div class="toc">
20 <h4>Table of Contents</h4>
21 <ul><li><a class="reference" href="#what-is-wsgi" id="id1" name="id1">What is WSGI?</a></li>
22 <li><a class="reference" href="#writing-a-wsgi-application" id="id2" name="id2">Writing a WSGI Application</a></li>
23 <li><a class="reference" href="#adding-interactive-elements" id="id3" name="id3">Adding Interactive Elements</a></li>
24 <li><a class="reference" href="#that-sucks" id="id4" name="id4">That Sucks!</a></li>
25 <li><a class="reference" href="#debugging-it" id="id5" name="id5">Debugging It</a></li>
26 </ul>
27 </div>
28
29 <p>New to Werkzeug or even <a class="reference" href="http://www.python.org/dev/peps/pep-0333/">WSGI</a>? This part of the documentation covers the
30 low-level parts of Werkzeug that are in fact just plain WSGI.</p>
31 <div class="section">
32 <h3 id="what-is-wsgi">What is WSGI?</h3>
33 <p>Basically WSGI is an interface between web servers and web applications.
34 We&#8217;ll explain the mechanics of WSGI below, but to sum it up WSGI lets you
35 develop rich web application without the need to know about your server
36 environment. A WSGI application runs on standalone servers, on any
37 webserver out there, via <cite>mod_python</cite>, <cite>FastCGI</cite>, <cite>SCGI</cite>, <cite>CGI</cite>, basically
38 everything that runs Python.</p>
39 <p>But there&#8217;s much more; WSGI is more than just HTTP! You and other developers
40 can extend WSGI by adding new features to it, filtering input or output,
41 adding rich debugging systems, automatically send e-mails to your mailer as
42 soon as an unhandled application error occurs. You can add session features to
43 it etc.</p>
44 <p>There are few things you cannot do with WSGI, and Werkzeug itself is just a
45 tiny wrapper around WSGI, thus you don&#8217;t lose any functionally unlike some
46 other implementations that abstract more. If you don&#8217;t require all the power
47 of WSGI you can choose a higher-level implementation like Django.</p>
48 </div>
49 <div class="section">
50 <h3 id="writing-a-wsgi-application">Writing a WSGI Application</h3>
51 <p>The first part is about how to use WSGI without Werkzeug. You can read
52 the PEP (:pep:333) that defines it, but we&#8217;ll do a very brief summary:</p>
53 <ul>
54 <li><p class="first">A WSGI application is just a callable object that is passed an <cite>environ</cite> -
55 a dict that contains request data, and a <cite>start_response</cite> function that is
56 called to start sending the response.</p>
57 <p>In order to send data to the server, all you have to do is to call
58 <cite>start_response</cite> and return an iterable.</p>
59 </li>
60 <li><p class="first"><cite>environ</cite> is a plain old CGI environment (contains values like <cite>PATH_INFO</cite>)
61 in form of a Python dict with some extensions like <cite>wsgi.input</cite> that
62 represent the input stream.</p>
63 </li>
64 <li><p class="first">Middlewares (we&#8217;ll cover them later) can add additional data to the
65 <cite>environ</cite>.</p>
66 </li>
67 </ul>
68 <p>So, here&#8217;s a simple application:</p>
69 <div class="syntax"><pre><span class="k">def</span> <span class="nf">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
70 <span class="n">start_response</span><span class="p">(</span><span class="s">&#39;200 OK&#39;</span><span class="p">,</span> <span class="p">[(</span><span class="s">&#39;Content-Type&#39;</span><span class="p">,</span> <span class="s">&#39;text/html&#39;</span><span class="p">)])</span>
71 <span class="k">return</span> <span class="p">[</span><span class="s">&#39;Hello World!&#39;</span><span class="p">]</span>
72 </pre></div>
73 <p>Now you have an application, but how can you start it? From Python 2.5, the
74 stdlib contains a WSGI server called <cite>wsgiref</cite>; for Python 2.4 and lower you
75 have to install that on your own. There are also other standalone
76 implementations which you can find at <a class="reference" href="http://www.wsgi.org/">wsgi.org</a>.</p>
77 <p>To start the application as standalone server, all you have to do is adding
78 this start hook to the file:</p>
79 <div class="syntax"><pre><span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&#39;__main__&#39;</span><span class="p">:</span>
80 <span class="k">from</span> <span class="nn">wsgiref.simple_server</span> <span class="k">import</span> <span class="n">make_server</span>
81 <span class="n">srv</span> <span class="o">=</span> <span class="n">make_server</span><span class="p">(</span><span class="s">&#39;localhost&#39;</span><span class="p">,</span> <span class="mf">5000</span><span class="p">,</span> <span class="n">application</span><span class="p">)</span>
82 <span class="n">srv</span><span class="o">.</span><span class="n">serve_forever</span><span class="p">()</span>
83 </pre></div>
84 <p>When you now start the application you will see <tt class="docutils literal"><span class="pre">Hello</span> <span class="pre">World!</span></tt> on
85 <tt class="docutils literal"><span class="pre">http://localhost:5000/</span></tt>.</p>
86 <p>The <cite>__name__</cite> hook is a good idea so that you can use the module for other
87 modules like flup (provides a bridge to Apache and other webservers via
88 FastCGI and similar protocols) without altering the code.</p>
89 </div>
90 <div class="section">
91 <h3 id="adding-interactive-elements">Adding Interactive Elements</h3>
92 <p>Let&#8217;s extend the example from above so that we say <tt class="docutils literal"><span class="pre">Hello</span> <span class="pre">John</span></tt> if the user
93 visits <tt class="docutils literal"><span class="pre">http://localhost:5000/?name=John</span></tt>:</p>
94 <div class="syntax"><pre><span class="k">from</span> <span class="nn">cgi</span> <span class="k">import</span> <span class="n">parse_qs</span><span class="p">,</span> <span class="n">escape</span>
95
96 <span class="k">def</span> <span class="nf">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
97 <span class="n">start_response</span><span class="p">(</span><span class="s">&#39;200 OK&#39;</span><span class="p">,</span> <span class="p">[(</span><span class="s">&#39;Content-Type&#39;</span><span class="p">,</span> <span class="s">&#39;text/html&#39;</span><span class="p">)])</span>
98 <span class="n">query</span> <span class="o">=</span> <span class="n">parse_qs</span><span class="p">(</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&#39;QUERY_STRING&#39;</span><span class="p">,</span> <span class="s">&#39;&#39;</span><span class="p">))</span>
99 <span class="k">return</span> <span class="p">[</span><span class="s">&#39;Hello </span><span class="si">%s</span><span class="s">!&#39;</span> <span class="o">%</span> <span class="n">escape</span><span class="p">(</span><span class="n">query</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&#39;name&#39;</span><span class="p">,</span> <span class="s">&#39;World&#39;</span><span class="p">))]</span>
100 </pre></div>
101 <p>Basically we just combined the python CGI module with our WSGI application.</p>
102 </div>
103 <div class="section">
104 <h3 id="that-sucks">That Sucks!</h3>
105 <p>Indeed it does. It&#8217;s neither fun to work with nor does it look clean and
106 simple. And that&#8217;s where utilities like Werkzeug come into play. Werkzeug,
107 for example, lets you easily create unicode-aware applications (in fact it
108 forces you to use unicode internally) and avoid repetitive work.</p>
109 <p>In this example we want to connect <cite>/</cite> with an index function and <cite>/about</cite>
110 with an about function, both returning different content. To keep the example
111 simple we just want to use a dict that maps to that and take advantage or some
112 of the Werkzeug features:</p>
113 <div class="syntax"><pre><span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">Request</span><span class="p">,</span> <span class="n">Response</span><span class="p">,</span> <span class="n">escape</span>
114
115 <span class="k">def</span> <span class="nf">index</span><span class="p">(</span><span class="n">req</span><span class="p">):</span>
116 <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="s">u&#39;&#39;&#39;&lt;h1&gt;Index Page&lt;/h1&gt;</span>
117 <span class="s"> &lt;p&gt;What</span><span class="se">\&#39;</span><span class="s">s your name?&lt;/p&gt;</span>
118 <span class="s"> &lt;form action=&quot;hello&quot; method=&quot;post&quot;&gt;</span>
119 <span class="s"> &lt;input type=&quot;text&quot; name=&quot;name&quot; value=&quot;My Name&quot;&gt;</span>
120 <span class="s"> &lt;input type=&quot;submit&quot; value=&quot;Greet Me!&quot;&gt;</span>
121 <span class="s"> &lt;/form&gt;</span>
122 <span class="s"> &#39;&#39;&#39;</span><span class="p">,</span> <span class="n">mimetype</span><span class="o">=</span><span class="s">&#39;text/html&#39;</span><span class="p">)</span>
123
124 <span class="k">def</span> <span class="nf">hello</span><span class="p">(</span><span class="n">req</span><span class="p">):</span>
125 <span class="n">name</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="n">form</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&#39;name&#39;</span><span class="p">)</span> <span class="ow">or</span> <span class="s">&#39;Nobody&#39;</span>
126 <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="s">u&#39;&lt;h1&gt;Hello </span><span class="si">%s</span><span class="s">!&lt;/h1&gt;&#39;</span> <span class="o">%</span> <span class="n">escape</span><span class="p">(</span><span class="n">name</span><span class="p">),</span>
127 <span class="n">mimetype</span><span class="o">=</span><span class="s">&#39;text/html&#39;</span><span class="p">)</span>
128
129 <span class="k">def</span> <span class="nf">about</span><span class="p">(</span><span class="n">req</span><span class="p">):</span>
130 <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="s">u&#39;&#39;&#39;&lt;h1&gt;About This Page&lt;/h1&gt;</span>
131 <span class="s"> &lt;p&gt;This page is just a small example page for Werkzeug&lt;/p&gt;</span>
132 <span class="s"> &#39;&#39;&#39;</span><span class="p">,</span> <span class="n">mimetype</span><span class="o">=</span><span class="s">&#39;text/html&#39;</span><span class="p">)</span>
133
134 <span class="k">def</span> <span class="nf">not_found</span><span class="p">(</span><span class="n">req</span><span class="p">):</span>
135 <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="s">u&#39;&lt;h1&gt;Page Not Found&lt;/h1&gt;&#39;</span><span class="p">,</span> <span class="n">status</span><span class="o">=</span><span class="mf">404</span><span class="p">,</span>
136 <span class="n">mimetype</span><span class="o">=</span><span class="s">&#39;text/html&#39;</span><span class="p">)</span>
137
138 <span class="n">views</span> <span class="o">=</span> <span class="p">{</span>
139 <span class="s">&#39;/&#39;</span><span class="p">:</span> <span class="n">index</span><span class="p">,</span>
140 <span class="s">&#39;/hello&#39;</span><span class="p">:</span> <span class="n">hello</span><span class="p">,</span>
141 <span class="s">&#39;/about&#39;</span><span class="p">:</span> <span class="n">about</span>
142 <span class="p">}</span>
143
144 <span class="k">def</span> <span class="nf">application</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span>
145 <span class="n">req</span> <span class="o">=</span> <span class="n">Request</span><span class="p">(</span><span class="n">environ</span><span class="p">)</span>
146 <span class="k">if</span> <span class="n">req</span><span class="o">.</span><span class="n">path</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">views</span><span class="p">:</span>
147 <span class="n">resp</span> <span class="o">=</span> <span class="n">not_found</span><span class="p">(</span><span class="n">req</span><span class="p">)</span>
148 <span class="k">else</span><span class="p">:</span>
149 <span class="n">resp</span> <span class="o">=</span> <span class="n">views</span><span class="p">[</span><span class="n">req</span><span class="o">.</span><span class="n">path</span><span class="p">](</span><span class="n">req</span><span class="p">)</span>
150 <span class="k">return</span> <span class="n">resp</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">)</span>
151
152 <span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&#39;__main__&#39;</span><span class="p">:</span>
153 <span class="k">from</span> <span class="nn">werkzeug</span> <span class="k">import</span> <span class="n">run_simple</span>
154 <span class="n">run_simple</span><span class="p">(</span><span class="s">&#39;localhost&#39;</span><span class="p">,</span> <span class="mf">5000</span><span class="p">,</span> <span class="n">application</span><span class="p">)</span>
155 </pre></div>
156 <p>Alright. That&#8217;s quite a lot of code but let&#8217;s go through it step-by-step.</p>
157 <p>The first thing we do is importing the stuff we use. In that example we need
158 the <cite>escape</cite> function that XML/SGML escapes strings. This is required to
159 avoid XSS attacks to the application. Then we need the <cite>Request</cite> and
160 <cite>Response</cite> objects which ecapsulate the WSGI environ and responses.</p>
161 <p>Then we define a bunch of callback functions that just return HTML responses.
162 One of them - <cite>not_found</cite> - is called when we don&#8217;t have a matching URL,
163 the others are bound to urls in the <cite>views</cite> dict.</p>
164 <p>The WSGI application itself just creates a request object and looks up the
165 callbacks or proceeds with the <cite>not_found</cite> function if there is no matching
166 URL. The return value of the response is then called as WSGI application.</p>
167 <p>This is possible because <cite>Response</cite> objects behave like WSGI applications.</p>
168 <p>Of course, this example is still bad code; in real applications you would use
169 one of the template engines that are available for Python. But for the simple
170 example that should be enough.</p>
171 </div>
172 <div class="section">
173 <h3 id="debugging-it">Debugging It</h3>
174 <p>You probably have had a typo in one of the examples above. In that case you
175 don&#8217;t have to use the error output from the <cite>wsgiref</cite> server. There are some
176 excellent debugging systems for WSGI, and one of them is shipped with Werkzeug
177 and explained in the <a class="reference" href="debug.html">debug system</a> docs.</p>
178 </div>
179
180 <div style="clear:both"></div>
181 </div>
182 <div class="footer">
183 Werkzeug is a <a href="http://pocoo.org/">Pocoo</a> project licensed under the
184 BSD license.
185 </div>
186 </div>
187 </body>
188 </html>
0 =============
1 API Stability
2 =============
3
4 Werkzeug has not yet reached 1.0 and as a matter of fact some things might
5 change over time. The following modules will very likely change in one of
6 the next release versions:
7
8 `werkzeug.contrib`
9 The community-contributed modules are yet undocumented and we expect
10 some upcoming changes there.
11
12
13 Backwards Incompatible Changes
14 ==============================
15
16 `0.3`
17 - Werkzeug 0.3 will be the last release with Python 2.3 compatibility.
18 - The `environ_property` is now read-only by default. This decision was
19 made because the request in general should be considered read-only.
20
21 `0.2`
22 - The `BaseReporterStream` is now part of the contrib module, the
23 new module is `werkzeug.contrib.reporterstream`. Starting with
24 `0.3`, the old import will not work any longer.
25 - `RequestRedirect` now uses a 301 status code. Previously a 302
26 status code was used incorrectly. If you want to continue using
27 this 302 code, use ``response = redirect(e.new_url, 302)``.
28 - `lazy_property` is now called `cached_property`. The alias for
29 the old name will disappear in Werkzeug 0.3.
30 - `match` can now raise `MethodNotAllowed` if configured for
31 methods and there was no method for that request.
32 - The `response_body` attribute on the response object is now called
33 `data`. With Werkzeug 0.3 the old name will not work any longer.
34 - The file-like methods on the response object are deprecated. If
35 you want to use the response object as file like object use the
36 `Response` class or a subclass of `BaseResponse` and mix the new
37 `ResponseStreamMixin` class and use `response.stream`.
0 ================
1 Debugging System
2 ================
3
4 Depending on the WSGI gateway/server, exceptions are handled differently. But
5 most of the time, exceptions go to stderr or the error log.
6
7 Since this is not the best debugging environment, Werkzeug provides a WSGI
8 middleware that renders nice debugging tracebacks, optionally with an AJAX
9 based debugger (which allows to execute code in the context of the
10 traceback's frames).
11
12 Usage:
13
14 .. sourcecode:: python
15
16 from werkzeug import DebuggedApplication, run_simple
17 from myapplication import application
18
19 application = DebuggedApplication(application, evalex=True)
20
21 run_simple('localhost', 5000, application)
22
23 This code spawns a debugging server on `localhost:5000` with the debugger
24 enabled. If you set `evalex` to `False`, the debugger is disabled.
25
26 .. warning::
27
28 Don't ever use the debugging middleware in a production environment since it
29 can leak internal information that is part of the variable debug table.
30 Even worse is a debugger with the `evalex` feature enabled, since it can be
31 used to execute code on the server!
0 ===========================
1 Deploying WSGI Applications
2 ===========================
3
4 The best thing about WSGI is that there are so many gateways for productive
5 usage. If you want to switch from `Apache`_ 2 with `mod_wsgi`_ to `lighttpd`_
6 with `FastCGI`_, it's a matter of a few minutes. If you want to distribute
7 your application on an USB stick for presentation, you can bundle everything
8 into an executable using `py2exe`_ and the built-in `wsgiref` module.
9
10 The following list shows the most often used server configurations and how
11 you can serve your WSGI applications.
12
13
14 Generic Gateways
15 ================
16
17 The following gateways are webserver agnostic and will work with any webserver
18 out there that suppors the underlaying interface:
19
20
21 `FastCGI`
22 For FastCGI, two well known python libraries exist. One is implemented in
23 Python and handles pretty every FastCGI configuration. It's called `flup`_
24 and supports also some other interfaces besides FastCGI. The other one is
25 called `python-fastcgi`_ and is implemented in C. However, the latter has
26 a much smaller feature set.
27
28 `SCGI`
29 `SCGI`_ is a simple protocol with the same benefits of FastCGI (persistent
30 interpreters). Like `FastCGI`, this is supported by `flup`_.
31
32 `CGI`
33 CGI is known to work on every major webserver out there, but has the
34 disadvantage of being slow. With Python 2.5 and onwards, you can use
35 `wsgiref` as CGI gateway. In older Python versions, you can either install
36 `wsgiref` yourself or use the CGI wrapper code from `PEP 333`_.
37
38
39 Apache Centric Gateways
40 =======================
41
42 The following gateways are either written especially for the Apache webserver
43 or work best with it.
44
45 `mod_wsgi`
46 Without doubt the best deployment platform for the Apache webserver is
47 `mod_wsgi`_. Even though it's an Apache module, there are also ways to
48 switch the underlaying interpreter into another user context. This allows
49 you to mass host Python applications like you would do with
50 `suexec`/`suphp`.
51
52 `mod_python`
53 For a long time, `mod_python`_ was the preferred way to deploy Python
54 applications on the Apache webserver, and frameworks like `Django`_ still
55 recommend this setup. However, some bugs in mod_python make it hard to
56 deploy WSGI applications, especially the undefined behavior of
57 `SCRIPT_NAME` makes routing hard.
58
59 If you want to use mod_python or have to use it, you can try the
60 mod_python WSGI wrapper from this `blog post about mod_python and WSGI`_.
61
62 `AJP`
63 `AJP`_ is the Apache JServ Protocol and is used by Tomcat. `flup`_
64 provides ways to talk it.
65
66
67 Example Configuration
68 =====================
69
70 Here is a small example configuration for Apache and mod_wsgi:
71
72 .. sourcecode:: python
73
74 from yourapplicaiton import YourWSGIApplication
75
76 # mod_wsgi just wants an object called `application` it will use
77 # for dispatching. Pretty simple, huh?
78 application = YourWSGIApplication(configuration='probably', goes='here')
79
80 Save it as `yourapplication.wsgi` and add this to your virtual host config:
81
82 .. sourcecode:: apache
83
84 WSGIScriptAlias / /path/to/yourapplication.wsgi/
85
86 (Note the trailing slash). Or, if you want to have the application in a
87 subfolder:
88
89 .. sourcecode:: apache
90
91 WSGIScriptAlias /foo /path/to/yourapplication.wsgi
92
93 (Without the trailing slash). Detailed usage examples for the WSGI gateways
94 usually come with the gateways or can be found in their wikis.
95
96
97 Selecting the Best Gateway
98 ==========================
99
100 Selecting the best gateway is mainly a matter of what you have available on
101 your server and how your application is written. Some applications might not
102 be thread safe, others work better with threads etc.
103
104 The following IRC channels on `freenode`_ deal a lot with WSGI and you might
105 be able to find some help there:
106
107 - ``#wsgi``
108 - ``#pylons``
109 - ``#pocoo``
110 - ``#pythonpaste``
111
112
113 .. _Apache: http://httpd.apache.org/
114 .. _mod_wsgi: http://code.google.com/p/modwsgi/
115 .. _lighttpd: http://www.lighttpd.net/
116 .. _FastCGI: http://www.fastcgi.com/
117 .. _py2exe: http://www.py2exe.org/
118 .. _flup: http://cheeseshop.python.org/pypi/flup
119 .. _python-fastcgi: http://cheeseshop.python.org/pypi/python-fastcgi
120 .. _SCGI: http://www.mems-exchange.org/software/scgi/
121 .. _PEP 333: http://www.python.org/dev/peps/pep-0333/
122 .. _mod_python: http://www.modpython.org/
123 .. _Django: http://www.djangoproject.com/
124 .. _blog post about mod_python and WSGI: http://www.aminus.org/blogs/index.php/fumanchu/2005/11/06/wsgi_wrapper_for_mod_python
125 .. _AJP: http://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html
126 .. _freenode: http://freenode.net/
0 ===============
1 HTTP Exceptions
2 ===============
3
4 .. module:: werkzeug.exceptions
5
6 All the exceptions must be imported from the `werkzeug.exceptions` module
7 unless you are using the `abort` callable which is available in `werkzeug`
8 too.
9
10
11 Error Classes
12 =============
13
14 The following error classes exist in Werkzeug:
15
16 .. class:: werkzeug.exceptions.BadRequest
17 .. class:: werkzeug.exceptions.Unauthorized
18 .. class:: werkzeug.exceptions.Forbidden
19 .. class:: werkzeug.exceptions.NotFound
20 .. class:: werkzeug.exceptions.MethodNotAllowed
21 .. class:: werkzeug.exceptions.NotAcceptable
22 .. class:: werkzeug.exceptions.RequestTimeout
23 .. class:: werkzeug.exceptions.Gone
24 .. class:: werkzeug.exceptions.LengthRequired
25 .. class:: werkzeug.exceptions.PreconditionFailed
26 .. class:: werkzeug.exceptions.RequestEntityTooLarge
27 .. class:: werkzeug.exceptions.RequestURITooLarge
28 .. class:: werkzeug.exceptions.UnsupportedMediaType
29 .. class:: werkzeug.exceptions.InternalServerError
30 .. class:: werkzeug.exceptions.NotImplemented
31 .. class:: werkzeug.exceptions.BadGateway
32 .. class:: werkzeug.exceptions.ServiceUnavailable
33
34
35 Baseclass
36 =========
37
38 All the exceptions implement this common interface:
39
40 .. class:: werkzeug.exceptions.HTTPException
41
42 .. def:: werkzeug.exceptions.HTTPException.get_response
43 .. def:: werkzeug.exceptions.HTTPException.__call__
44
45
46 Special HTTP Exceptions
47 =======================
48
49 Starting with Werkzeug 0.3 some of the builtin classes raise exceptions that
50 look like regular python exceptions (eg `KeyError`) but are `BadRequest`
51 HTTP exceptions at the same time. This decision was made to simplify a
52 common pattern where you want to abort if the client tampered with the
53 submitted form data in a way that the application can't recover properly and
54 should abort with ``400 BAD REQUEST``.
55
56 Assuming the application catches all HTTP exceptions and reacts to them
57 properly a view function could do the following savely and doesn't have to
58 check if the keys exist::
59
60 def new_post(request):
61 post = Post(title=request.form['title'], body=request.form['body'])
62 post.save()
63 return redirect(post.url)
64
65 If `title` or `body` are missing in the form a `MultiDict.KeyError` will be
66 raised which behaves like a `KeyError` but also a `BadRequest` exception.
67
68
69 Simple Aborting
70 ===============
71
72 Sometimes it's convenient to just raise an exception by the error code,
73 without importing the exception and looking up the name etc. For this
74 purpose there is the `abort` function.
75
76 It can be passed a WSGI application or a status code. If a status code
77 is given it's looked up in the list of exceptions from above and will
78 raise that exception, if passed a WSGI application it will wrap it in
79 a proxy WSGI exception and raise that::
80
81 abort(404)
82 abort(Response('Hello World'))
83
84 If you want to use this functionality with custom excetions you can
85 create an instance of the aborter class:
86
87 .. class:: werkzeug.exceptions.Aborter
88
89
90 Custom Errors
91 =============
92
93 As you can see from the list above not all status codes are available as
94 errors. Especially redirects and ather non 200 status codes that
95 represent do not represent errors are missing. For redirects you can use
96 the `redirect` function from the utilities.
97
98 If you want to add an error yourself you can subclass `HTTPException`::
99
100 from werkzeug.exceptions import HTTPException
101
102 class PaymentRequred(HTTPException):
103 code = 402
104 description = '<p>Payment required.</p>'
105
106 This is the minimal code you need for your own exception. If you want to
107 add more logic to the errors you can override the `get_description()`,
108 `get_body()`, `get_headers()` and `get_response()` methods. In any case
109 you should have a look at the sourcecode of the exceptions module.
110
111 You can override the default description in the constructor with the
112 `description` parameter (it's the first argument for all exceptions
113 except of the `MethodNotAllowed` which accepts a list of allowed methods
114 as first argument)::
115
116 raise BadRequest('Request failed because X was not present')
0 ======================
1 Documentation Overview
2 ======================
3
4 Welcome to the Werkzeug documentation.
5
6
7 Reference
8 =========
9
10 Documentation regarding the Werkzeug modules:
11
12 - `Installing Werkzeug <installation.txt>`_
13
14 - `Important Terms <terms.txt>`_ --- read this before reading the
15 documentation
16
17 - `Tutorial <tutorial.txt>`_ --- getting started with Werkzeug
18
19 - `Unicode <unicode.txt>`_ --- short introduction into unicode in python
20 and how Werkzeug encodes and decodes data.
21
22 - `Wrappers <wrappers.txt>`_ --- request and response objects
23
24 - `Routing System <routing.txt>`_ --- a powerful URL dispatcher
25 and builder
26
27 - `Mini Templates <templates.txt>`_ --- a minimal templating system
28
29 - `Management Script Utilities <script.txt>`_ --- tools to write simple
30 management scripts
31
32 - `Test Utilities <test.txt>`_ --- unittest support tools
33
34 - `HTTP Exceptions <exceptions.txt>`_ --- exceptions for HTTP
35
36 - `Utilities <utils.txt>`_ --- useful classes and functions
37
38 - `Context Locals <local.txt>`_ --- a WSGI centric version of `thread.local`.
39
40 - `Debugging System <debug.txt>`_ --- an interactive debugger.
41
42 - `Serving Applications <serving.txt>`_ --- serving WSGI applications.
43
44 - `API Stability <api_stability.txt>`_ --- API stability
45
46
47 General Development Information
48 ===============================
49
50 This part of the documentation mainly explains how to develop WSGI
51 applications. This is also interesting if you don't want to use
52 Werkzeug but other WSGI utilities; the ideas are the same.
53
54 - `How WSGI Works <wsgihowto.txt>`_ --- short introduction to WSGI and
55 Werkzeug.
56
57 - `Organizing Code <organizing.txt>`_ --- gives you an idea how you can
58 organize your code when using Werkzeug.
59
60 - `Other Libraries <libraries.txt>`_ --- links to other libraries you
61 can use with Werkzeug.
62
63 - `Deploying WSGI Applications <deploying.txt>`_ --- ready for production?
64 This page covers all the details you have to know to deploy your
65 application on various webservers.
66
67
68 .. _TinyURL: http://tinyurl.com/
0 ============
1 Installation
2 ============
3
4 Werkzeug requires at least Python 2.3 to work correctly.
5
6
7 Installing a released version
8 =============================
9
10 As a Python egg (via easy_install)
11 ----------------------------------
12
13 You can install the most recent Werkzeug version using `easy_install`_::
14
15 sudo easy_install Werkzeug
16
17 This will install a Werkzeug egg in your Python installation's `site-packages`
18 directory.
19
20 From the tarball release
21 -------------------------
22
23 1. Download the most recent tarball from the `download page`_.
24 2. Unpack the tarball.
25 3. ``sudo python setup.py install``
26
27 Note that the last command will automatically download and install
28 `setuptools`_ if you don't already have it installed. This requires a working
29 Internet connection.
30
31 This will install Werkzeug into your Python installation's `site-packages`
32 directory.
33
34
35 Installing the development version
36 ==================================
37
38 If you want to play around with the code
39 ----------------------------------------
40
41 1. Install `Mercurial`_
42 2. ``hg clone http://dev.pocoo.org/hg/werkzeug-main werkzeug``
43 3. ``cd werkzeug``
44 4. ``ln -s werkzeug /usr/lib/python2.X/site-packages``
45
46 As an alternative to step 4 you can also do ``python setup.py develop`` which
47 will install the package via setuptools in development mode.
48
49 If you just want the latest features and use them
50 -------------------------------------------------
51
52 ::
53
54 sudo easy_install Werkzeug==dev
55
56 This will install a Werkzeug egg containing the latest mercurial tip in
57 your Python installation's `site-packages` directory. Every time the
58 command is run, the sources are updated from the mercurial repository.
59
60
61 Documentation
62 =============
63
64 The egg builds include a documentation which is available in the `docs` folder
65 of the egg. If you're running Linux you will find the documentation here::
66
67 file:///usr/lib/python2.X/site-packages/Werkzeug-Y.Z-py2.X.egg/docs/index.html
68
69 where `X`, `Y` and `Z` must be replaced by the Python/Werkzeug version number.
70
71
72 .. _download page: http://werkzeug.pocoo.org/download.html
73 .. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools
74 .. _easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall
75 .. _Mercurial: http://selenic.com/mercurial/
0 ===============
1 Other Libraries
2 ===============
3
4 Because Werkzeug is just a thin layer over WSGI, it's very easy to use WSGI
5 middlewares together with Werkzeug-powered applications. It's also possible
6 to just use small parts of Werkzeug like the URL mapper etc. with complete
7 different implementations or frameworks.
8
9 Here a small list of libraries you may want to try out.
10
11
12 Database Layers
13 ===============
14
15 If you want to use relational databases in your application.
16
17 `SQLAlchemy <http://www.sqlalchemy.org/>`_
18 SQLAlchemy is a great database layer and object relational mapper that
19 lets you construct SQL queries using Python expressions. It also provides
20 connection pools and plays nicely with the WSGI standard.
21
22 `Elixir <http://elixir.ematia.de/>`_
23 Elixir is a declarative layer on top of the SQLAlchemy library. It is a
24 fairly thin wrapper, which provides the ability to create simple Python
25 classes that map directly to relational database tables (this pattern is
26 often referred to as the Active Record design pattern), providing many of
27 the benefits of traditional databases without losing the convenience of
28 Python objects.
29
30 `Storm <https://storm.canonical.com/>`_
31 Storm is an object-relational mapper (ORM) for Python developed at
32 Canonical. It has been in development for more than a year for use in
33 Canonical projects such as Launchpad, and has been released as an
34 open-source product.
35
36 Template Engines
37 ================
38
39 Bigger applications deserve something better than minitmpl :)
40
41 `Genshi <http://genshi.edgewall.org/>`_
42 If you like XML template engines, check out Genshi. Ass-kicking
43 template engine, but unfortunately not the fastest.
44
45 `Mako <http://www.makotemplates.org/>`_
46 The fastest designer friendly template engine for Python. Similar
47 to ERB `<http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/>`_ in terms of
48 syntax, but with a powerful template inheritance system and multiple
49 namespaces.
50
51 `Jinja <http://jinja.pocoo.org/>`_
52 Sandboxed, Django-/Smarty-like template engine, but with inline
53 expressions that let you execute a subset of Python expressions in
54 your templates.
55
56
57 Form Validation
58 ===============
59
60 Here some form validation packages for WSGI applications:
61
62 `What The Forms <http://dev.simplecodes.com/projects/wtforms>`_
63 WTForms is a HTTP/HTML forms handling library, written in Python. It
64 handles definition, validation and rendering in a flexible and i18n
65 friendly way. It heavily reduces boilerplate and is completely unicode
66 aware.
67
68 `Newforms Extracted <http://code.google.com/p/newforms-extracted/>`_
69 This is a project to extract Django's newforms and make that package
70 usable by other projects, since Django doesn't seem interested in making
71 this code framework independent.
72
73 `FormEncode <http://formencode.org/>`_
74 FormEncode is a validation and form generation package. The validation
75 can be used separately from the form generation. The validation works on
76 compound data structures, with all parts being nestable. It is separate
77 from HTTP or any other input mechanism.
78
79
80 Tools and Utilities
81 ===================
82
83 Something's missing? Check here first:
84
85 `wsgitools <http://subdivi.de/~helmut/wsgitools/>`_
86 Various small WSGI utilities like a minimal traceback or auth
87 middleware. It also includes an SCGI server.
88
89 `Paste <http://pythonpaste.org/>`_
90 Many tools for use with WSGI: dispatching, composition, simple
91 applications (e.g., file serving), and more.
92
93 `Routes <http://routes.groovie.org/>`_
94 A port of the Rails URL mapping system.
95
96 `Python OpenID <http://openidenabled.com/python-openid/>`_
97 The OpenID library with batteries included.
98
99 `AuthKit <http://authkit.org/>`_
100 WSGI Authentication and Authorization Tools. Built in support for
101 HTTP basic, HTTP digest, form, cookie and OpenID authentication methods
102 plus others.
103
104 You can find a more complete list on the `wsgi.org`_ webpage.
105
106
107 .. _wsgi.org: http://wsgi.org/wsgi/Middleware_and_Utilities
0 ==============
1 Context Locals
2 ==============
3
4 .. module:: werkzeug.local
5
6
7 Objects
8 =======
9
10 .. class:: werkzeug.local.LocalManager
11
12 .. def:: werkzeug.local.LocalManager.cleanup
13 .. def:: werkzeug.local.LocalManager.make_middleware
14 .. def:: werkzeug.local.LocalManager.middleware
15 .. def:: werkzeug.local.LocalManager.get_ident
16
17 .. class:: werkzeug.local.LocalProxy
18
19 .. def:: werkzeug.local.LocalProxy._get_current_object
20
21 Keep in mind that ``repr()`` is also forwarded, so if you want to find
22 out if you are dealing with a proxy you can do an ``isinstance()`` check:
23
24 .. sourcecode:: pycon
25
26 >>> from werkzeug import LocalProxy
27 >>> isinstance(request, LocalProxy)
28 True
29
30 You can also create proxy objects by hand:
31
32 .. sourcecode:: python
33
34 from werkzeug import Local, LocalProxy
35 local = Local()
36 request = LocalProxy(local, 'request')
0 ===============
1 Organizing Code
2 ===============
3
4 Werkzeug doesn't limit you in what you do; thus there is no general rule where
5 to locate your modules, what to do with your data etc.
6
7 However, there is a general pattern which is used in many web applications,
8 the "Model View Controller" pattern (`MVC`), which makes sense for many
9 applications.
10
11 The idea is that you have "models" in one file, the "views" in another and the
12 "controllers" in the third file or folder. The Django framework came up with
13 a new idea of naming those components which is called "Model Template View"
14 (`MTV`) which probably makes more sense for web applications although it means
15 nearly the same. We will use the latter naming in this file.
16
17 So here is what those terms mean:
18
19 Model
20 A model is an abstraction of your data. For example if you have a
21 database you can use the excellent `SQLAlchemy`_ library that maps
22 database tables to classes or just provides a thin layer that lets you
23 write your own classes.
24
25 Template
26 The templates contain the actual HTML markup with placeholders for data
27 passed to them. Although there is a `minimal template language
28 <minitmpl.txt>`_ in the Werkzeug package we don't recommend using it for
29 larger applications. There are many good and powerful template engines
30 out there that support debugging, template inheritance, XML output etc.
31
32 The integrated templating language is a good idea if you have one or two
33 templates and don't want to install one of the big template engines, but
34 it is not suitable for more complex tasks!
35
36 View
37 The view is the code that processes data, connects the model with
38 templates, and then outputs the result. This is the part where Werkzeug
39 helps you.
40
41
42 Entry Point
43 ===========
44
45 There should be one central file that provides the WSGI application and
46 dispatches the requests. It's also a good idea to assemble the final URL map
47 there if you use the Werkzeug `routing system`_.
48
49 It's also a good idea to call the file something like ``main.py`` or
50 ``application.py`` and locate it in the root of the package. This is what it
51 *could* look like:
52
53 .. sourcecode:: python
54
55 from werkzeug import Request, Response, import_string
56 from werkzeug.exceptions import HTTPException
57 from werkzeug.routing import RequestRedirect
58 from mypackage.urls import url_map
59
60 def application(environ, start_response):
61 url_adapter = url_map.bind_to_environ(environ)
62 req = Request(environ)
63 try:
64 endpoint, values = url_adapter.match()
65 view = import_string('mypackage.views.' + endpoint)
66 resp = view(req, **values)
67 except (RequestRedirect, HTTPException), e:
68 resp = e
69 return resp(environ, start_response)
70
71 You can even further simplify the dispatching by using ``urls.dispatch`` as
72 explained in the routing documentation.
73
74 This will look for the controller functions in `mypackage.views.module`.
75 If the URL is configured with an endpoint of ``'static.index'``, the module
76 `mypackage.views.static` is loaded and `index(req)` is called.
77
78 The URL rule parameters are passed to the function as keyword arguments then.
79
80 **Note**: This is just one idea of how things can look like. This doesn't
81 necessarily have to represent your application. You can, for example, save
82 the request object in a thread-local storage or have your views as methods
83 of a controller class you instantiate with the request as argument for each
84 incoming request. You can also use other request objects or no request object
85 at all etc.
86
87
88 Utils
89 =====
90
91 If we continue the example above we need an utils module with the request and
92 response objects. This would also contain other utilities and a bridge to a
93 template engine. In this example we will use the `Jinja`_ template engine -
94 basically because it's something we use, too - but you can of course use any
95 template engine:
96
97 .. sourcecode:: python
98
99 from jinja import Environment, PackageLoader
100 from werkzeug import Response
101
102 env = Environment(loader=PackageLoader('mypackage', 'templates'))
103
104 class TemplateResponse(Response):
105
106 def __init__(self, template_name, **values):
107 tmpl = env.get_template(template_name)
108 output = tmpl.render(values)
109 Response.__init__(self, output, mimetype='text/html')
110
111 **Note**: Templates in this example are saved in the `templates` folder inside
112 the `mypackage` package. The way template loading and rendering works depends
113 on the template engine, so have a look at it's documentation regarding that.
114
115 We just subclass request and response classes for our needs and provide a
116 second response subclass that is used to render templates. It's used in the
117 example view below.
118
119
120 URLs
121 ====
122
123 Because we use the Werkzeug URL routing system in this example and the URLs
124 are stored in `urls.py`, we need that file:
125
126 .. sourcecode:: python
127
128 from werkzeug.routing import Map, Rule
129
130 url_map = Map([
131 Rule('/', 'static.index')
132 ])
133
134 This is just one small URL rule for one view.
135
136
137 View
138 ====
139
140 Here is the view defined above. It must be saved in
141 `myprojects/views/static.py` as defined above:
142
143 .. sourcecode:: python
144
145 from myproject.utils import TemplateResponse
146
147 def index(req):
148 return TemplateResponse('index.html', title='Welcome')
149
150
151 Models and Templates
152 ====================
153
154 Models and templates are out of the scope for this documentation, but you
155 should have gotten an idea on how you can organize your WSGI application built
156 with the help of Werkzeug.
157
158
159 .. _SQLAlchemy: http://www.sqlalchemy.org/
160 .. _routing system: routing.txt
161 .. _Jinja: http://jinja.pocoo.org/
0 ==============
1 Routing System
2 ==============
3
4 When it comes to combining multiple controller or view functions (however
5 you want to call them), you need a dispatcher. A simple way would be
6 applying regular expression tests on ``PATH_INFO`` and call registered
7 callback functions that return the value.
8
9 Werkzeug provides a much more powerful system, similar to `Routes`_. All the
10 objects mentioned on this page must be imported from `werkzeug.routing`, not
11 from `werkzeug`!
12
13
14 Quickstart
15 ==========
16
17 Here a simple example which could be the URL definition for a blog:
18
19 .. sourcecode:: python
20
21 from werkzeug.routing import Map, Rule, NotFound, RequestRedirect
22
23 url_map = Map([
24 Rule('/', endpoint='blog/index'),
25 Rule('/<int:year>/', endpoint='blog/archive'),
26 Rule('/<int:year>/<int:month>/', endpoint='blog/archive'),
27 Rule('/<int:year>/<int:month>/<int:day>/', endpoint='blog/archive'),
28 Rule('/<int:year>/<int:month>/<int:day>/<slug>',
29 endpoint='blog/show_post'),
30 Rule('/about', endpoint='blog/about_me'),
31 Rule('/feeds/', endpoint='blog/feeds'),
32 Rule('/feeds/<feed_name>.rss', endpoint='blog/show_feed')
33 ])
34
35 def application(environ, start_response):
36 urls = url_map.bind_to_environ(environ)
37 try:
38 endpoint, args = urls.match()
39 except HTTPException, e:
40 return e(environ, start_response)
41 start_response('200 OK', [('Content-Type', 'text/plain')])
42 return ['Rule points to %r with arguments %r' % (endpoint, args)]
43
44 So what does that do? First of all we create a new `Map` which stores a
45 bunch of URL rules. Then we pass it a list of `Rule` objects.
46
47 Each `Rule` object is instantiated with a string that represents a rule and
48 an endpoint which will be the alias for what view the rule represents.
49 Multiple rules can have the same endpoint but should have different arguments
50 to allow URL construction.
51
52 The format for the URL rules is straightforward, but explained in detail below.
53
54 Inside the WSGI application we bind the url_map to the current request which will
55 return a new `MapAdapter`. This url_map adapter can then be used to match or
56 build domains for the current request.
57
58 The `match` method can then either return a tuple in the form
59 ``(endpoint, args)`` or raise one of the three exceptions `NotFound`,
60 `MethodNotAllowed`, or `RequestRedirect`. For more details about those
61 exceptions have a look at the documentation of the `match` method.
62
63
64 Rule Format
65 ===========
66
67 Rule strings basically are just normal URL paths with placeholders in the
68 format ``<converter(arguments):name>``, where converter and the arguments
69 are optional. If no converter is defined, the `default` converter is used
70 (which means `string` in the normal configuration).
71
72 URL rules that end with a slash are branch URLs, others are leaves. If you
73 have `strict_slashes` enabled (which is the default), all branch URLs that are
74 visited without a trailing slash will trigger a redirect to the same URL with
75 that slash appended.
76
77 The list of converters can be extended, the default converters are explained
78 below.
79
80
81 Builtin Converters
82 ==================
83
84 Here a list of converters that come with Werkzeug:
85
86 `string`
87 .. docstring:: werkzeug.routing.UnicodeConverter
88
89 `path`
90 .. docstring:: werkzeug.routing.PathConverter
91
92 `any`
93 .. docstring:: werkzeug.routing.AnyConverter
94
95 `int`
96 .. docstring:: werkzeug.routing.IntegerConverter
97
98 `float`
99 .. docstring:: werkzeug.routing.FloatConverter
100
101
102 .. admonition:: Important
103
104 Werkzeug evaluates converter arguments as if they are Python method calls.
105 Thus, you should **never** create rules from user submitted data since
106 they could insert arbitrary Python code in the parameters part.
107
108 As a matter of fact this is a legal definition and sets fixed_digits to 2:
109
110 .. sourcecode:: python
111
112 url_map = Map([
113 Rule('/picture/<int(fixed_digits=1 + 1):id>.png',
114 endpoint='view_image')
115 ])
116
117 However, evaluating Python expressions is currently an implementation
118 detail and might be unavailable in the future.
119
120
121 Maps, Rules and Adapters
122 ========================
123
124 .. class:: werkzeug.routing.Map
125
126 .. def:: werkzeug.routing.Map.__init__
127 .. def:: werkzeug.routing.Map.is_endpoint_expecting
128 .. def:: werkzeug.routing.Map.iter_rules
129 .. def:: werkzeug.routing.Map.add
130 .. def:: werkzeug.routing.Map.bind
131 .. def:: werkzeug.routing.Map.bind_to_environ
132
133 .. class:: werkzeug.routing.MapAdapter
134
135 .. def:: werkzeug.routing.MapAdapter.match
136 .. def:: werkzeug.routing.MapAdapter.test
137 .. def:: werkzeug.routing.MapAdapter.dispatch
138 .. def:: werkzeug.routing.MapAdapter.build
139
140 .. class:: werkzeug.routing.Rule
141
142 .. def:: werkzeug.routing.Rule.empty
143 .. def:: werkzeug.routing.Rule.bind
144
145
146 Rule Factories
147 ==============
148
149 .. class:: werkzeug.routing.RuleFactory
150
151 .. def:: werkzeug.routing.RuleFactory.get_rules
152
153 .. class:: werkzeug.routing.Subdomain
154 .. class:: werkzeug.routing.Submount
155 .. class:: werkzeug.routing.EndpointPrefix
156
157
158 Rule Templates
159 ==============
160
161 .. class:: werkzeug.routing.RuleTemplate
162
163
164 Custom Converters
165 =================
166
167 You can easily add custom converters. The only thing you have to do is to
168 subclass `BaseConverter` and pass that new converter to the url_map. A converter
169 has to provide two public methods: `to_python` and `to_url`, as well as a
170 member that represents a regular expression. Here is a small example:
171
172 .. sourcecode:: python
173
174 from random import randrange
175 from werkzeug.routing import Rule, Map, BaseConverter, ValidationError
176
177 class BooleanConverter(BaseConverter):
178
179 def __init__(self, url_map, randomify=False):
180 super(BooleanConverter, self).__init__(url_map)
181 self.randomify = randomify
182 self.regex = '(?:yes|no|maybe)'
183
184 def to_python(self, value):
185 if value == 'maybe':
186 if self.randomify:
187 return not randrange(2)
188 raise ValidationError()
189 return value == 'yes'
190
191 def to_url(self, value):
192 return value and 'yes' or 'no'
193
194 url_map = Map([
195 Rule('/vote/<bool:werkzeug_rocks>', endpoint='vote'),
196 Rule('/vote/<bool(randomify=True):foo>', endpoint='foo')
197 ], converters={'bool': BooleanConverter})
198
199 If you want that converter to be the default converter, name it ``'default'``.
200
201
202 .. _Routes: http://routes.groovie.org/
0 ===========================
1 Management Script Utilities
2 ===========================
3
4
5 .. module:: werkzeug.script
6
7
8 Writing Actions
9 ===============
10
11 Writing new action functions is pretty straightforward. All you have to do is
12 to name the function `action_COMMAND` and it will be available as
13 `./manage.py COMMAND`. The docstring of the function is used for the help
14 screen and all arguments must have defaults which the `run` function can
15 inspect. As a matter of fact you cannot use ``*args`` or ``**kwargs``
16 constructs.
17
18 An additional feature is the definition of tuples as defaults. The first item
19 in the tuple could be a short name for the command and the second the default
20 value::
21
22 def action_add_user(username=('u', ''), password=('p', '')):
23 """Docstring goes here."""
24 ...
25
26
27 Action Discovery
28 ================
29
30 Per default, the `run` function looks up variables in the current locals.
31 That means if no arguments are provided, it implicitly assumes this call::
32
33 script.run(locals(), 'action_')
34
35 If you don't want to use an action discovery, you can set the prefix to an
36 empty string and pass a dict with functions::
37
38 script.run(dict(
39 runserver=script.make_runserver(make_app, use_reloader=True),
40 shell=script.make_shell(lambda: {'app': make_app()}),
41 initdb=on_initdb
42 ), '')
43
44
45 Example Scripts
46 ===============
47
48 In the Werkzeug `example folder`_ there are some ``./manage-APP.py`` scripts
49 using `werkzeug.script`.
50
51
52 .. _example folder: http://dev.pocoo.org/projects/werkzeug/browser/examples
0 =========================
1 Serving WSGI Applications
2 =========================
3
4 There are many ways to serve a WSGI application. While you're developing it,
5 you usually don't want to have a full-blown webserver like Apache up and
6 running, but instead a simple standalone one. With Python 2.5 and onwards,
7 there is the `wsgiref`_ server in the standard library. If you're using older
8 versions of Python you can download the package from the `Cheeseshop`_.
9
10 However, there are some caveats. Sourcecode won't reload itself when changed,
11 and each time you kill the server using ``^C`` you get a `KeyboardInterrupt`
12 error. While the latter is easy to solve, the first one can be a pain in the
13 ass in some situations.
14
15 Because of that Werkzeug ships a small wrapper over `wsgiref` that spawns the
16 WSGI application in a subprocess and automatically reloads the application if
17 a module was changed.
18
19 The easiest way is creating a small ``start-myproject.py`` file that runs the
20 application:
21
22 .. sourcecode:: python
23
24 #!/usr/bin/env python
25 # -*- coding: utf-8 -*-
26
27 from werkzeug import run_simple
28 from myproject import make_app
29
30 app = make_app(...)
31 run_simple('localhost', 8080, app, use_reloader=True)
32
33 You can also pass it the `extra_files` keyword argument with a list of
34 additional files (like configuration files) you want to observe.
35
36 .. def:: werkzeug.serving.run_simple
37
38
39 .. _wsgiref: http://cheeseshop.python.org/pypi/wsgiref
40 .. _Cheeseshop: http://pypi.python.org/
0 ==============
1 Mini Templates
2 ==============
3
4 Werkzeug ships a **minimal** templating system which is useful for small
5 scripts where you just want to generate some HTML and don't want another
6 dependency or full blown template engine system.
7
8 It it however not recommended to use this template system for anything else
9 than simple content generation. The `Template` class can be directly imported
10 from the `werkzeug` module.
11
12 .. module:: werkzeug.templates
13
14
15 The Template Class
16 ==================
17
18 .. class:: werkzeug.templates.Template
19
20 .. def:: werkzeug.templates.Template.render
21 .. def:: werkzeug.templates.Template.from_file
22
23 Besides the normal global functions and objects, the following functions
24 are added to every namespace: `escape`, `url_encode`, `url_quote`, and
25 `url_quote_plus`. You can change those by subclassing `Template` and
26 overriding the `default_context` dict::
27
28 class MyTemplate(Template):
29 default_namespace = {
30 'ueber_func': ueber_func
31 }
32 # Now add the old functions, too, because they are useful.
33 default_namespace.update(Template.default_namespace)
0 ===============
1 Important Terms
2 ===============
3
4 This page covers important terms used in the documentation and Werkzeug
5 itself.
6
7
8 Response Object
9 ---------------
10
11 For Werkzeug, a response object is an object that works like a WSGI
12 application but does not do any request processing. Usually you have a view
13 function or controller method that processes the request and assambles a
14 response object.
15
16 A response object is *not* necessarily the `BaseResponse` object or a subclass
17 thereof.
18
19
20 View Function
21 -------------
22
23 Often people speak of MVC (Model, View, Controller) when developing web
24 applications. However, the Django framework coined MTV (Model, Template,
25 View) which basically means the same but reduces the concept to the data
26 model, a function that processes data from the request and the database and
27 renders a template.
28
29 Werkzeug itself does not tell you how you should develop applications, but the
30 documentation often speaks of view functions that work roughly the same. The
31 idea of a view function is that it's called with a request object (and
32 optionally some parameters from an URL rule) and returns a response object.
0 ==============
1 Test Utilities
2 ==============
3
4 Quite often you want to unit-test your application or just check the output
5 from an interactive Python session. In theory that is pretty simple because
6 you can fake a WSGI environment and call the application with a dummy
7 `start_response` and iterate over the application iterator, but there are
8 argumentably better ways to interact with an application.
9
10
11 Diving In
12 =========
13
14 Werkzeug provides a `Client` object which you can pass a WSGI application (and
15 optionally a response wrapper) which you can use to send virtual requests to
16 the application.
17
18 A response wrapper is a callable that takes three arguments: the application
19 iterator, the status and finally a list of headers. The default response
20 wrapper returns a tuple. Because response objects have the same signature,
21 you can use them as response wrapper, ideally by subclassing them and hooking
22 in test functionality.
23
24 >>> from werkzeug import Client, BaseResponse, test_app
25 >>> c = Client(test_app, BaseResponse)
26 >>> resp = c.get('/')
27 >>> resp.status_code
28 200
29 >>> resp.headers
30 Headers([('Content-Type', 'text/html; charset=utf-8')])
31 >>> resp.response_body.splitlines()[:2]
32 ['<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"',
33 ' "http://www.w3.org/TR/html4/loose.dtd">']
34
35 Or without a wrapper defined:
36
37 >>> c = Client(test_app)
38 >>> app_iter, status, headers = c.get('/')
39 >>> status
40 '200 OK'
41 >>> headers
42 [('Content-Type', 'text/html; charset=utf-8')]
43 >>> ''.join(app_iter).splitlines()[:2]
44 ['<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"',
45 ' "http://www.w3.org/TR/html4/loose.dtd">']
46
47 The Client
48 ==========
49
50 .. class:: werkzeug.test.Client
51
52 .. def:: werkzeug.test.Client.__init__
53 .. def:: werkzeug.test.Client.open
54 .. def:: werkzeug.test.Client.get
55 .. def:: werkzeug.test.Client.head
56 .. def:: werkzeug.test.Client.post
57 .. def:: werkzeug.test.Client.put
58 .. def:: werkzeug.test.Client.delete
0 =================
1 Werkzeug Tutorial
2 =================
3
4 .. admonition:: Translations
5
6 This tutorial is available in the following languages:
7
8 - **English**
9 - `Deutsch <tutorial_de.txt>`_
10
11 Welcome to the Werkzeug 0.2 tutorial in which we will create a `TinyURL`_ clone
12 that stores URLs in a database. The libraries we will use for this
13 applications are `Jinja`_ for the templates, `SQLAlchemy`_ for the database
14 layer and, of course, Werkzeug for the WSGI layer.
15
16 The reasons why we've decided on these libraries for the tutorial application
17 is that we want to stick to some of the design decisions `Django`_ took in the
18 past. One of them is using view functions instead of controller classes with
19 action methods, which is common in `Rails`_ and `Pylons`_, the other one is
20 designer-friendly templates.
21
22 The Werkzeug `example folder`_ contains a couple of applications that use other
23 template engines, too, so you may want to have a look at them. There is also
24 the source code of this application.
25
26 You can use `easy_install`_ to install the required libraries::
27
28 sudo easy_install Jinja
29 sudo easy_install SQLAlchemy
30
31 If you're on Windows, omit the "sudo" (and make sure, `setuptools`_ is
32 installed); if you're on OS X, you can check if the libraries are also
33 available in port; or on Linux, you can check out your package manager for
34 packages called ``python-jinja`` and ``python-sqlalchemy``.
35
36 If you're curious, check out the `online demo`_ of the application.
37
38 Small disclaimer: This tutorial requires Python 2.4 or later.
39
40 .. _TinyURL: http://tinyurl.com/
41 .. _Django: http://www.djangoproject.com/
42 .. _Jinja: http://jinja.pocoo.org/
43 .. _SQLAlchemy: http://sqlalchemy.org/
44 .. _Rails: http://www.rubyonrails.org/
45 .. _Pylons: http://pylonshq.com/
46 .. _example folder: http://dev.pocoo.org/projects/werkzeug/browser/examples
47 .. _easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall
48 .. _setuptools: http://pypi.python.org/pypi/setuptools
49 .. _online demo: http://werkzeug.pocoo.org/e/shorty/
50
51
52 Part 0: The Folder Structure
53 ============================
54
55 Before we can get started we have to create a Python package for our Werkzeug
56 application and the folders for the templates and static files.
57
58 This tutorial application is called `shorty` and the initial directory layout
59 we will use looks like this::
60
61 manage.py
62 shorty/
63 __init__.py
64 templates/
65 static/
66
67 The ``__init__.py`` and ``manage.py`` files should be empty for the time
68 being. The first one makes ``shorty`` a Python package, the second one will
69 hold our management utilities later.
70
71
72 Part 1: The WSGI Application
73 ============================
74
75 Unlike Django or other frameworks, Werkzeug operates directly on the WSGI
76 layer. There is no fancy magic that implements the central WSGI application
77 for you. As a result of that the first thing you will do every time you write
78 a Werkzeug application is implementing this basic WSGI application object.
79 This can now either be a function or, even better, a callable class.
80
81 A callable class has huge advantages over a function. For one you can pass
82 it some configuration parameters and furthermore you can use inline WSGI
83 middlewares. Inline WSGI middlewares are basically middlewares applied
84 "inside" of our application object. This is a good idea for middlewares that
85 are essential for the application (session middlewares, serving of media
86 files etc.).
87
88 Here the initial code for our ``shorty/application.py`` file which implements
89 the WSGI application:
90
91 .. sourcecode:: python
92
93 from sqlalchemy import create_engine
94 from werkzeug import Request, ClosingIterator
95 from werkzeug.exceptions import HTTPException
96
97 from shorty.utils import session, metadata, local, local_manager, url_map
98 from shorty import views
99 import shorty.models
100
101
102 class Shorty(object):
103
104 def __init__(self, db_uri):
105 local.application = self
106 self.database_engine = create_engine(db_uri, convert_unicode=True)
107
108 def init_database(self):
109 metadata.create_all(self.database_engine)
110
111 def __call__(self, environ, start_response):
112 local.application = self
113 request = Request(environ)
114 local.url_adapter = adapter = url_map.bind_to_environ(environ)
115 try:
116 endpoint, values = adapter.match()
117 handler = getattr(views, endpoint)
118 response = handler(request, **values)
119 except HTTPException, e:
120 response = e
121 return ClosingIterator(response(environ, start_response),
122 [session.remove, local_manager.cleanup])
123
124 That's a lot of code for the beginning! Let's go through it step by step.
125 First we have a couple of imports: From SQLAlchemy we import a factory
126 function that creates a new database engine for us. A database engine holds
127 a pool of connections for us and manages them. The next few imports pull some
128 objects into the namespace Werkzeug provides: a request object, a special
129 iterator class that helps us cleaning up stuff at the request end and finally
130 the base class for all HTTP exceptions.
131
132 The next five imports are not working because we don't have the utils module
133 written yet. However we should cover some of the objects already. The
134 `session` object pulled from there is not a PHP-like session object but a
135 SQLAlchemy database session object. Basically a database session object keeps
136 track of yet uncommited objects for the database. Unlike Django, an
137 instantiated SQLAlchemy model is already tracked by the session! The metadata
138 object is also an SQLAlchemy thing which is used to keep track of tables. We
139 can use the metadata object to easily create all tables for the database and
140 SQLAlchemy uses it to look up foreign keys and similar stuff.
141
142 The `local` object is basically a thread local object created in the utility
143 module for us. Every attribute on this object is bound to the current request
144 and we can use this to implicitly pass objects around in a thread-safe way.
145
146 The `local_manager` object ensures that all local objects it keeps track of
147 are properly deleted at the end of the request.
148
149 The last thing we import from there is the URL map which holds the URL routing
150 information. If you know Django you can compare that to the url patterns you
151 specify in the ``urls.py`` module, if you have used PHP so far it's comparable
152 with some sort of built-in "mod_rewrite".
153
154 We import our views module which holds the view functions and then we import
155 the models module which holds all of our models. Even if it looks like we
156 don't use that import it's there so that all the tables are registered on the
157 metadata properly.
158
159 So let's have a look at the application class. The constructor of this class
160 takes a database URI which is basically the type of the database and the login
161 credentials or location of the database. For SQLite this is for example
162 ``'sqlite:////tmp/shorty.db'`` (note that these are **four** slashes).
163
164 In the constructor we create a database engine for that database URI and use
165 the `convert_unicode` parameter to tell SQLAlchemy that our strings are all
166 unicode objects.
167
168 Another thing we do here is binding the application to the local object. This
169 is not really required but useful if we want to play with the application in
170 a python shell. On application instanciation we have it bound to the current
171 thread and all the database functions will work as expected. If we don't do
172 that Werkzeug will complain that it's unable to find the database when it's
173 creating a session for SQLAlchemy.
174
175 The `init_database` function defined below can be used to create all the
176 tables we use.
177
178 And then comes the request dispatching function. In there we create a new
179 request object by passing the environment to the `Request` constructor.
180 Once again we bind the application to the local object, this time, however,
181 we have to do this, otherwise things will break soon.
182
183 Then we create a new URL map adapter by binding the URL map to the current
184 WSGI environment. This basically looks at the environment of the incoming
185 request information and fetches the information from the environment it
186 requires. This is for example the name of the server for external URLs, the
187 location of the script so that it can generate absolute paths if we use the
188 URL builder. We also bind the adapter to the local object so that we can
189 use it for URL generation in the utils module.
190
191 After that we have a `try`/`except` that catches HTTP exceptions that could
192 occur while matching or in the view function. When the adapter does not find
193 a valid endpoint for our current request it will raise a `NotFound` exception
194 which we can use like a response object. An endpoint is basically the name
195 of the function we want to handle our request with. We just get the
196 function with the name of the endpoint and pass it the request and the URL
197 values.
198
199 At the end of the function we call the response object as WSGI application
200 and pass the return value of this function (which will be an iterable) to
201 the closing iterator class along with our cleanup callbacks (which remove
202 the current SQLAlchemy session and clean up the data left in the local
203 objects).
204
205 As next step create two empty files ``shorty/views.py`` and
206 ``shorty/models.py`` so that our imports work. We will fill the modules with
207 useful code later.
208
209
210 Part 2: The Utilities
211 =====================
212
213 Now we have basically finished the WSGI application itself but we have to add
214 some more code into our utiliy module so that the imports work. For the time
215 being we just add the objects which we need for the application to work. All
216 the following code goes into the ``shorty/utils.py`` file:
217
218 .. sourcecode:: python
219
220 from sqlalchemy import MetaData
221 from sqlalchemy.orm import create_session, scoped_session
222 from werkzeug import Local, LocalManager
223 from werkzeug.routing import Map, Rule
224
225 local = Local()
226 local_manager = LocalManager([local])
227 application = local('application')
228
229 metadata = MetaData()
230 session = scoped_session(lambda: create_session(application.database_engine,
231 transactional=True), local_manager.get_ident)
232
233 url_map = Map()
234 def expose(rule, **kw):
235 def decorate(f):
236 kw['endpoint'] = f.__name__
237 url_map.add(Rule(rule, **kw))
238 return f
239 return decorate
240
241 def url_for(endpoint, _external=False, **values):
242 return local.url_adapter.build(endpoint, values, force_external=_external)
243
244 First we again import a bunch of stuff, then we create the local objects and
245 the local manager we already discussed in the section above. The new thing
246 here is that calling a local object with a string returns a proxy object. This
247 returned proxy object always points to the attribute with that name on the
248 local object. For example `application` now points to `local.application`
249 all the time. If you, however, try to do something with it and there is
250 no object bound to `local.application` you will get a `RuntimeError`.
251
252 The next three lines are basically everything we need to get SQLAlchemy 0.4
253 or higher running in a Werkzeug application. We create a new metadata for all
254 of our tables and then a new scoped session using the `scoped_session` factory
255 function. This basically tells SQLAlchemy to use the same algorithm to
256 determine the current context as werkzeug local does and use the database
257 engine of the current application.
258
259 If we don't plan to add support for multiple instances of the application in
260 the same python interpreter we can also simplify that code by not looking up
261 the application on the current local object but somewhere else. This approach
262 is for example used by Django but makes it impossible to combine multiple such
263 applications.
264
265 The rest of the module is code we will use in our views. Basically the idea
266 there is to use decorators to specify the URL dispatching rule for a view
267 function rather than a central ``urls.py`` file like you could do in Django
268 or a ``.htaccess`` for URL rewrites like you would do in PHP. This is
269 **one** way to do it and there are countless of other ways to handle rule
270 definitions.
271
272 The `url_for` function, which we have there too, provides a simple way to
273 generate URLs by endpoint. We will use it in the views and our model later.
274
275
276 Intermission: And Now For Something Completely Different
277 ========================================================
278
279 Now that we have finished the foundation for the application we could relax
280 and do something completely different: management scripts. Most of the time
281 you do similar tasks while developing. One of them is firing up a
282 development server (If you're used to PHP: Werkzeug does not rely on Apache
283 for development, it's perfectly fine and also recommended to use the wsgiref
284 server that comes with python for development purposes), starting a python
285 interpreter to play with the database models, initializing the database etc.
286
287 Werkzeug makes it incredible easy to write such management scripts. The
288 following piece of code implements a fully featured management script. Put
289 it into the `manage.py` file you have created in the beginning:
290
291 .. sourcecode:: python
292
293 #!/usr/bin/env python
294 from werkzeug import script
295
296 def make_app():
297 from shorty.application import Shorty
298 return Shorty('sqlite:////tmp/shorty.db')
299
300 def make_shell():
301 from shorty import models, utils
302 application = make_app()
303 return locals()
304
305 action_runserver = script.make_runserver(make_app, use_reloader=True)
306 action_shell = script.make_shell(make_shell)
307 action_initdb = lambda: make_app().init_database()
308
309 script.run()
310
311 `werkzeug.script` is explained in detail in the `script documentation`_ and
312 we won't cover it here, most of the code should be self explaining anyway.
313
314 What's important is that you should be able to run ``python manage.py shell``
315 to get an interactive Python interpreter without traceback. If you get an
316 exception check the line number and compare your code with the code we have
317 in the code boxes above.
318
319 Now that the script system is running we can start writing our database models.
320
321 .. _script documentation: script.txt
322
323
324 Part 3: Database Models
325 =======================
326
327 Now we can create the models. Because the application is pretty simple we
328 just have one model and table:
329
330 .. sourcecode:: python
331
332 from datetime import datetime
333 from sqlalchemy import Table, Column, String, Boolean, DateTime
334 from shorty.utils import session, metadata, url_for, get_random_uid
335
336 url_table = Table('urls', metadata,
337 Column('uid', String(140), primary_key=True),
338 Column('target', String(500)),
339 Column('added', DateTime),
340 Column('public', Boolean)
341 )
342
343 class URL(object):
344
345 def __init__(self, target, public=True, uid=None, added=None):
346 self.target = target
347 self.public = public
348 self.added = added or datetime.utcnow()
349 if not uid:
350 while True:
351 uid = get_random_uid()
352 if not URL.query.get(uid):
353 break
354 self.uid = uid
355
356 @property
357 def short_url(self):
358 return url_for('link', uid=self.uid, _external=True)
359
360 def __repr__(self):
361 return '<URL %r>' % self.uid
362
363 session.mapper(URL, url_table)
364
365 This module is pretty straightforward. We import all the stuff we need from
366 SQLAlchemy and create a table. Then we add a class for this table and we map
367 them both together. For detailed explanations regarding SQLAlchemy you should
368 have a look at the `excellent tutorial`_.
369
370 In the constructor we generate a unique ID until we find an id which is still
371 free to use.
372 What's missing is the `get_random_uid` function we have to add to the utils
373 module:
374
375 .. sourcecode:: python
376
377 from random import sample, randrange
378
379 URL_CHARS = 'abcdefghijkmpqrstuvwxyzABCDEFGHIJKLMNPQRST23456789'
380
381 def get_random_uid():
382 return ''.join(sample(URL_CHARS, randrange(3, 9)))
383
384 Once that is done we can use ``python manage.py initdb`` to initialize the
385 database and play around with the stuff using ``python manage.py shell``:
386
387 .. sourcecode:: pycon
388
389 Interactive Werkzeug Shell
390 >>> from shorty.models import session, URL
391
392 Now we can add some URLs to the database:
393
394 .. sourcecode:: pycon
395
396 >>> urls = [URL('http://example.org/'), URL('http://localhost:5000/')]
397 >>> URL.query.all()
398 []
399 >>> session.commit()
400 >>> URL.query.all()
401 [<URL '5cFbsk'>, <URL 'mpugsT'>]
402
403 As you can see we have to commit in order to send the urls to the database.
404 Let's create a private item with a custom uid:
405
406 .. sourcecode:: pycon
407
408 >>> URL('http://werkzeug.pocoo.org/', False, 'werkzeug-webpage')
409 >>> session.commit()
410
411 And query them all:
412
413 .. sourcecode:: pycon
414
415 >>> URL.query.filter_by(public=False).all()
416 [<URL 'werkzeug-webpage'>]
417 >>> URL.query.filter_by(public=True).all()
418 [<URL '5cFbsk'>, <URL 'mpugsT'>]
419 >>> URL.query.get('werkzeug-webpage')
420 <URL 'werkzeug-webpage'>
421
422 Now that we have some data in the database and we are somewhat familiar with
423 the way SQLAlchemy works, it's time to create our views.
424
425 .. _excellent tutorial: http://www.sqlalchemy.org/docs/04/ormtutorial.html
426
427
428 Part 4: The View Functions
429 ==========================
430
431 Now after some playing with SQLAlchemy we can go back to Werkzeug and start
432 creating our view functions. The term "view function" is derived from Django
433 which also calls the functions that render templates "view functions". So
434 our example is MTV (Model, View, Template) and not MVC (Model, View,
435 Controller). They are probably the same but it's a lot easier to use the
436 Django way of naming those things.
437
438 For the beginning we just create a view function for new URLs and a function
439 that displays a message about a new link. All that code goes into our still
440 empty ``views.py`` file:
441
442 .. sourcecode:: python
443
444 from werkzeug import redirect
445 from werkzeug.exceptions import NotFound
446 from shorty.utils import session, render_template, expose, validate_url, \
447 url_for
448 from shorty.models import URL
449
450 @expose('/')
451 def new(request):
452 error = url = ''
453 if request.method == 'POST':
454 url = request.form.get('url')
455 alias = request.form.get('alias')
456 if not validate_url(url):
457 error = "I'm sorry but you cannot shorten this URL."
458 elif alias:
459 if len(alias) > 140:
460 error = 'Your alias is too long'
461 elif '/' in alias:
462 error = 'Your alias might not include a slash'
463 elif URL.query.get(alias):
464 error = 'The alias you have requested exists already'
465 if not error:
466 uid = URL(url, 'private' not in request.form, alias).uid
467 session.commit()
468 return redirect(url_for('display', uid=uid))
469 return render_template('new.html', error=error, url=url)
470
471 @expose('/display/<uid>')
472 def display(request, uid):
473 url = URL.query.get(uid)
474 if not url:
475 raise NotFound()
476 return render_template('display.html', url=url)
477
478 @expose('/u/<uid>')
479 def link(request, uid):
480 url = URL.query.get(uid)
481 if not url:
482 raise NotFound()
483 return redirect(url.target, 301)
484
485 @expose('/list/', defaults={'page': 1})
486 @expose('/list/<int:page>')
487 def list(request, page):
488 pass
489
490 Quite a lot of code again, but most of it is just plain old form validation.
491 Basically we specify two functions: `new` and `display` and
492 decorate them with our `expose` decorator from the utils. This decorator
493 adds a new URL rule to the map by passing all parameters to the constructor
494 of a rule object and setting the endpoint to the name of the function. So we
495 can easily build URLs to those functions by using their name as endpoint.
496
497 Keep in mind that this is not necessarily a good idea for bigger applications.
498 In such cases it's encouraged to use the full import name with a common prefix
499 as endpoint or something similar. Otherwise it becomes pretty confusing.
500
501 The form validation in the `new` method is pretty straightforward. We check
502 if the current method is `POST`, if yes we get the data from the request and
503 validate it. If there is no error we create a new `URL` object, commit it to
504 the database and redirect to the display page.
505
506 The `display` function is not much more complex. The URL rule expects a
507 parameter called `uid`, which the function accepts. Then we look up the URL
508 rule with the given uid and render a template by passing the URL object to it.
509
510 If the URL does not exist we raise a `NotFound` exception which displays a
511 generic "404 Page Not Found" page. We can later replace it by a custom error
512 page by catching that exception before the generic `HTTPException` in
513 our WSGI application.
514
515 The `link` view function is used by our models in the `short_url` property
516 and is the short URL we provide. So if the URL uid is ``foobar`` the URL
517 will be available as ``http://localhost:5000/u/foobar``.
518
519 The `list` view function has not yet been written, we will do that later. But
520 what's important is that this function takes a URL parameter which is
521 optional. The first decorator tells Werkzeug that if just ``/page/`` is
522 requested it will assume that the page equals 1. Even more important is the
523 fact that Werkzeug also normalizes the URLs. So if you requested ``/page`` or
524 ``/page/1``, you will be redirected to ``/page/`` in both cases.
525 This makes Google happy and comes for free. If you don't like that behavior,
526 you can also disable it.
527
528 And again we have imported two objects from the utils module that
529 don't exist yet. One of those should render a jinja template into a response
530 object, the other one validates a URL. So let's add those to ``utils.py``:
531
532 .. sourcecode:: python
533
534 from os import path
535 from urlparse import urlparse
536 from werkzeug import Response
537 from jinja import Environment, FileSystemLoader
538
539 ALLOWED_SCHEMES = frozenset(['http', 'https', 'ftp', 'ftps'])
540 TEMPLATE_PATH = path.join(path.dirname(__file__), 'templates')
541 jinja_env = Environment(loader=FileSystemLoader(TEMPLATE_PATH))
542 jinja_env.globals['url_for'] = url_for
543
544 def render_template(template, **context):
545 return Response(jinja_env.get_template(template).render(**context),
546 mimetype='text/html')
547
548 def validate_url(url):
549 return urlparse(url)[0] in ALLOWED_SCHEMES
550
551 That's it, basically. The validation function checks if our URL looks like an
552 HTTP or FTP URL. We do this whitelisting to ensure nobody submits any
553 dangerous JavaScript or similar URLs. The `render_template` function is not
554 much more complicated either, it basically looks up a template on the file
555 system in the `templates` folder and renders it as response.
556
557 Another thing we do here is passing the `url_for` function into the global
558 template context so that we can build URLs in the templates too.
559
560 Now that we have our first two view functions it's time to add the templates.
561
562
563 Part 5: The Templates
564 =====================
565
566 We have decided to use Jinja templates in this example. If you are used to
567 Django templates you should feel at home, if you have worked with PHP so far
568 you can compare the Jinja templates with smarty. If you have used PHP as
569 templating language until now you should have a look at `Mako`_ for your next
570 project.
571
572 **Security Warning**: We are using Jinja here which is a text based template
573 engine. As a matter of fact, Jinja has no idea what it is dealing with, so
574 if you want to create HTML template it's your responsibility to escape *all*
575 values that might include, at some point, any of the following characters: ``>``,
576 ``<`` or ``&``. Inside attributes you also have to escape double quotes.
577 You can use the jinja ``|e`` filter for basic escapes, if you pass it `true`
578 as argument it will also escape quotes (``|e(true)``). As you can see from
579 the examples below we don't escape URLs. The reason is that we won't have
580 any ampersands in the URL and as such it's safe to omit it.
581
582 For simplicity we will use HTML 4 in our templates. If you have already
583 some experience with XHTML you can adopt the templates to XHTML. But keep
584 in mind that the example stylesheet from below does not work with XHTML.
585
586 One of the cool things Jinja inherited from Django is template inheritance.
587 Template inheritance means that we can put often used elements into a base
588 template and fill in placeholders. For example all the doctype and HTML base
589 frame goes into a file called ``templates/layout.html``:
590
591 .. sourcecode:: html+jinja
592
593 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
594 "http://www.w3.org/TR/html4/strict.dtd">
595 <html>
596 <head>
597 <title>Shorty</title>
598 </head>
599 <body>
600 <h1><a href="{{ url_for('new') }}">Shorty</a></h1>
601 <div class="body">{% block body %}{% endblock %}</div>
602 <div class="footer">
603 <a href="{{ url_for('new') }}">new</a> |
604 <a href="{{ url_for('list') }}">list</a> |
605 use shorty for good, not for evil
606 </div>
607 </body>
608 </html>
609
610 And we can inherit from this base template in our ``templates/new.html``:
611
612 .. sourcecode:: html+jinja
613
614 {% extends 'layout.html' %}
615 {% block body %}
616 <h2>Create a Shorty-URL!</h2>
617 {% if error %}<div class="error">{{ error }}</div>{% endif -%}
618 <form action="" method="post">
619 <p>Enter the URL you want to shorten</p>
620 <p><input type="text" name="url" id="url" value="{{ url|e(true) }}"></p>
621 <p>Optionally you can give the URL a memorable name</p>
622 <p><input type="text" id="alias" name="alias">{#
623 #}<input type="submit" id="submit" value="Do!"></p>
624 <p><input type="checkbox" name="private" id="private">
625 <label for="private">make this URL private, so don't list it</label></p>
626 </form>
627 {% endblock %}
628
629 If you're wondering about the comment between the two input elements, this is
630 a neat trick to keep the templates clean but not create whitespace between
631 those two. We've prepared a stylesheet you can use which depends on not having
632 a whitespace there.
633
634 And then a second template for the display page (``templates/display.html``):
635
636 .. sourcecode:: html+jinja
637
638 {% extends 'layout.html' %}
639 {% block body %}
640 <h2>Shortened URL</h2>
641 <p>
642 The URL {{ url.target|urlize(40, true) }}
643 was shortened to {{ url.short_url|urlize }}.
644 </p>
645 {% endblock %}
646
647 The `urlize` filter is provided by Jinja and translates a URL(s) in a
648 text into clickable links. If you pass it an integer it will shorten the
649 captions of those links to that number of characters, passing it true as
650 second parameter adds a `nofollow` flag.
651
652 Now that we have our first two templates it's time to fire up the server and
653 look at those part of the application that work already: adding new URLs and
654 getting redirected.
655
656
657 Intermission: Adding The Design
658 ===============================
659
660 Now it's time to do something different: adding a design. Design elements are
661 usually in static CSS stylesheets so we have to put some static files
662 somewhere. But that's a little big tricky. If you have worked with PHP so
663 far you have probably noticed that there is no such thing as translating the
664 URL to filesystem paths and accessing static files right from the URL. You
665 have to explicitly tell the webserver or our development server that some
666 path holds static files.
667
668 Django even recommends a separate subdomain and standalone server for the
669 static files which is a terribly good idea for heavily loaded environments but
670 somewhat of an overkill for this simple application.
671
672 So here is the deal: We let our application host the static files, but in
673 production mode you should probably tell the apache to serve those files by
674 using an `Alias` directive in the apache config:
675
676 .. sourcecode:: apache
677
678 Alias /static /path/to/static/files
679
680 This will be a lot faster.
681
682 But how do we tell our application that we want it to share the static folder
683 from our application package as ``/static``?. Fortunately that's pretty
684 simple because Werkzeug provides a WSGI middleware for that. Now there are
685 two ways to hook that middleware in. One way is to wrap the whole application
686 in that middleware (we really don't recommend this one) and the other is to
687 just wrap the dispatching function (much better because we don't lose the
688 reference to the application object). So head back to ``application.py``
689 and do some code refactoring there.
690
691 First of all you have to add a new import and calculate the path to the
692 static files:
693
694 .. sourcecode:: python
695
696 from os import path
697 from werkzeug import SharedDataMiddleware
698
699 STATIC_PATH = path.join(path.dirname(__file__), 'static')
700
701 It may be better to put the path calculation into the ``utils.py`` file
702 because we already calculate the path to the templates there. But it doesn't
703 really matter and for simplicity we can leave it in the application module.
704
705 So how do we wrap our dispatching function? In theory we just have to say
706 ``self.__call__ = wrap(self.__call__)`` but unfortunately that doesn't work in
707 python. But it's not much harder. Just rename `__call__` to `dispatch` and
708 add a new `__call__` method:
709
710 .. sourcecode:: python
711
712 def __call__(self, environ, start_response):
713 return self.dispatch(environ, start_response)
714
715 Now we can go into our `__init__` function and hook in the middleware by
716 wrapping the `dispatch` method:
717
718 .. sourcecode:: python
719
720 self.dispatch = SharedDataMiddleware(self.dispatch, {
721 '/static': STATIC_PATH
722 })
723
724 Now that wasn't that hard. This way you can now hook in WSGI middlewares
725 inside the application class!
726
727 Another good idea now is to tell our `url_map` in the utils module the
728 location of our static files by adding a rule. This way we can generate URLs
729 to the static files in the templates:
730
731 .. sourcecode:: python
732
733 url_map = Map([Rule('/static/<file>', endpoint='static', build_only=True)])
734
735 Now we can open our ``templates/layout.html`` file again and add a link to the
736 stylesheet ``style.css``, which we are going to create afterwards:
737
738 .. sourcecode:: html+jinja
739
740 <link rel="stylesheet" type="text/css" href="{{ url_for('static', file='style.css') }}">
741
742 This of course goes into the `<head>` tag where currently just the title is.
743
744 You can now design a nice layout for it or use the `example stylesheet`_ if
745 you want. In both cases the file you have to create is called
746 ``static/style.css``
747
748 .. _Mako: http://www.makotemplates.org/
749 .. _example stylesheet: http://dev.pocoo.org/projects/werkzeug/browser/examples/shorty/static/style.css
750
751
752 Part 6: Listing Public URLs
753 ===========================
754
755 Now we want to list all of the public URLs on the list page. That shouldn't
756 be a big problem but we will have to do some sort of pagination. Because if
757 we print all URLs at once we have sooner or later an endless page that takes
758 minutes to load.
759
760 So let's start by adding a `Pagination` class into our utils module:
761
762 .. sourcecode:: python
763
764 from werkzeug import cached_property
765
766 class Pagination(object):
767
768 def __init__(self, query, per_page, page, endpoint):
769 self.query = query
770 self.per_page = per_page
771 self.page = page
772 self.endpoint = endpoint
773
774 @cached_property
775 def count(self):
776 return self.query.count()
777
778 @cached_property
779 def entries(self):
780 return self.query.offset((self.page - 1) * self.per_page) \
781 .limit(self.per_page).all()
782
783 has_previous = property(lambda x: x.page > 1)
784 has_next = property(lambda x: x.page < x.pages)
785 previous = property(lambda x: url_for(x.endpoint, page=x.page - 1))
786 next = property(lambda x: url_for(x.endpoint, page=x.page + 1))
787 pages = property(lambda x: max(0, x.count - 1) // x.per_page + 1)
788
789 This is a very simple class that does most of the pagination for us. We
790 can pass at an unexecuted SQLAlchemy query, the number of items per page,
791 the current page and the endpoint, which will be used for URL generation. The
792 `cached_property` decorator you see works pretty much like the normal
793 `property` decorator, just that it memorizes the result. We won't cover
794 that class in detail but basically the idea is that accessing
795 `pagination.entries` returns the items for the current page and that the
796 other properties return meaningful values so that we can use them in the
797 template.
798
799 Now we can import the `Pagination` class into our views module and add some
800 code to the `list` function:
801
802 .. sourcecode:: python
803
804 from shorty.utils import Pagination
805
806 @expose('/list/', defaults={'page': 1})
807 @expose('/list/<int:page>')
808 def list(request, page):
809 query = URL.query.filter_by(public=True)
810 pagination = Pagination(query, 30, page, 'list')
811 if pagination.page > 1 and not pagination.entries:
812 raise NotFound()
813 return render_template('list.html', pagination=pagination)
814
815 The if condition in this function basically ensures that status code 404 is
816 returned if we are not on the first page and there aren't any entries to display
817 (Accessing something like ``/list/42`` without entries on that page and not
818 returning a 404 status code would be considered bad style.)
819
820 And finally the template:
821
822 .. sourcecode:: html+jinja
823
824 {% extends 'layout.html' %}
825 {% block body %}
826 <h2>List of URLs</h2>
827 <ul>
828 {%- for url in pagination.entries %}
829 <li><a href="{{ url.short_url|e }}">{{ url.uid|e }}</a> &raquo;
830 <small>{{ url.target|urlize(38, true) }}</small></li>
831 {%- else %}
832 <li><em>no URls shortened yet</em></li>
833 {%- endfor %}
834 </ul>
835 <div class="pagination">
836 {%- if pagination.has_previous %}<a href="{{ pagination.previous
837 }}">&laquo; Previous</a>
838 {%- else %}<span class="inactive">&laquo; Previous</span>{% endif %}
839 | {{ pagination.page }} |
840 {% if pagination.has_next %}<a href="{{ pagination.next }}">Next &raquo;</a>
841 {%- else %}<span class="inactive">Next &raquo;</span>{% endif %}
842 </div>
843 {% endblock %}
844
845
846 Bonus: Styling 404 Error Pages
847 ==============================
848
849 Now that we've finished our application we can do some small improvements such
850 as custom 404 error pages. That's pretty simple. The first thing we have to
851 do is creating a new function called `not_found` in the view that renders a
852 template:
853
854 .. sourcecode:: python
855
856 def not_found(request):
857 return render_template('not_found.html')
858
859 Then we have to go into our application module and import the `NotFound`
860 exception:
861
862 .. sourcecode:: python
863
864 from werkzeug.exceptions import NotFound
865
866 Finally we have to catch it and translate it into a response. This except
867 block goes right **before** the except block of the `HTTPException`:
868
869 .. sourcecode:: python
870
871 try:
872 # this stays the same
873 except NotFound, e:
874 response = views.not_found(request)
875 response.status_code = 404
876 except HTTPException, e:
877 # this stays the same
878
879 Now add a template ``templates/not_found.html`` and you're done:
880
881 .. sourcecode:: html+jinja
882
883 {% extends 'layout.html' %}
884 {% block body %}
885 <h2>Page Not Found</h2>
886 <p>
887 The page you have requested does not exist on this server. What about
888 <a href="{{ url_for('new') }}">adding a new URL</a>?
889 </p>
890 {% endblock %}
891
892
893 Outro
894 =====
895
896 This tutorial covers everything you need to get started with Werkzeug,
897 SQLAlchemy and Jinja and should help you find the best solution for your
898 application. For some more complex examples that also use different setups
899 and ideas for dispatching have a look at the `examples folder`_.
900
901 Have fun with Werkzeug!
902
903 .. _examples folder: http://dev.pocoo.org/projects/werkzeug/browser/examples
0 =================
1 Werkzeug Tutorial
2 =================
3
4 .. admonition:: Hinweis
5
6 Dies ist die deutsche Übersetzung des `Tutorials <tutorial.txt>`_. Die
7 Entwicklung rund um Werkzeug steht nie still, und Verbesserungen an der
8 Library wirken sich oft auch auf das Tutorial aus -- deshalb ist die
9 Originalversion möglicherweise aktueller.
10
11 Willkommen zum Tutorial für Werkzeug 0.2. Wir werden einen einfachen
12 `TinyURL`_-Klon programmieren, der die URLs in einer Datenbank speichert. Die
13 Die verwendeten Bibliotheken für diese Anwendung sind `Jinja`_ für die
14 Templates, `SQLAlchemy`_ für die Datenbank-Anbindung und natürlich Werkzeug
15 für WSGI.
16
17 Wir haben uns hier für diese Komponenten entschieden, weil wir einen
18 `Django`_-ähnlichen Grundaufbau nachstellen wollen. Dazu zählen wir zum
19 Beispiel View-Funktionen anstelle der in `Rails`_ und `Pylons`_ gängigen
20 Controller-Klassen mit Action-Methoden, sowie designerfreundliche Templates.
21
22 In Werkzeugs `Beispiel-Ordner`_ befinden sich einige Anwendungen, die andere
23 Konzepte verfolgen, Template-Engines einsetzen etc. Dort liegt auch der
24 Quellcode der Anwendung, die wir in diesem Tutorial erstellen werden.
25
26 Du kannst `easy_install`_ verwenden, um Jinja und SQLAlchemy zu installieren,
27 falls diese nicht bereits installiert sind::
28
29 sudo easy_install Jinja
30 sudo easy_install SQLAlchemy
31
32 Diese Befehle funktionieren auch auf einem Windows-System (mit
33 Administratorrechten), sofern die `setuptools` installiert sind, allerdings
34 musst du das `sudo` weglassen. Als OS X-Benutzer könntest du die Libraries
35 auch via port installieren, Linux-Benutzer finden diese Pakete möglicherweise
36 auch in ihrem Paketmanager.
37
38 Wenn du neugierig bist, kannst du dir auch die `Online-Demo`_ der Anwendung
39 ansehen.
40
41 Noch ein kleiner Hinweis: Dieses Tutorial erfordert Python 2.4.
42
43 .. _TinyURL: http://tinyurl.com/
44 .. _Django: http://www.djangoproject.com/
45 .. _Jinja: http://jinja.pocoo.org/
46 .. _SQLAlchemy: http://sqlalchemy.org/
47 .. _Rails: http://www.rubyonrails.org/
48 .. _Pylons: http://pylonshq.com/
49 .. _Beispiel-Ordner: http://dev.pocoo.org/projects/werkzeug/browser/examples
50 .. _easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall
51 .. _setuptools: http://pypi.python.org/pypi/setuptools
52 .. _Online-Demo: http://werkzeug.pocoo.org/e/shorty/
53
54
55 Teil 0: Die Ordnerstruktur
56 ==========================
57
58 Bevor wir beginnen können, müssen wir ein Python-Paket für unsere
59 Werkzeug-Anwendung erstellen. Dort werden wir die Anwendung, die Templates
60 und die statischen Dateien ablegen.
61
62 Die Anwendung dieses Tutorials nennen wir `shorty` und die Struktur für unsere
63 Anwendung sieht etwa so aus::
64
65 manage.py
66 shorty/
67 __init__.py
68 static/
69 templates/
70
71 Die Dateien ``__init__.py`` und ``manage.py`` lassen wir für den Moment einmal
72 leer. Die erste dieser Dateien macht aus dem Ordner ``shorty`` ein
73 Python-Paket, die zweite werden wir später für unsere Verwaltungsfunktionen
74 nutzen.
75
76
77 Teil 1: Die WSGI-Anwendung
78 ==========================
79
80 Im Gegensatz zu Django oder ähnlichen Frameworks arbeitet Werkzeug direkt auf
81 der WSGI-Schicht. Es gibt keine schicke Magie, die die zentrale WSGI-Anwendung
82 für uns implementiert. Das bedeutet, dass wir als Erstes eben diese
83 programmieren müssen. Eine WSGI-Anwendung ist eine Funktion oder, noch besser,
84 eine Klasse mit einer Methode ``__call__``.
85
86 Eine aufrufbare Klasse hat große Vorteile gegenüber einer Funktion: Zum Einen
87 kann man Konfigurationsparameter direkt an den Konstruktor übergeben, zum
88 Anderen können wir WSGI-Middlewares innerhalb der WSGI-Anwendung hinzufügen.
89 Das ist nützlich für Middlewares, die entscheidend für die Funktion der
90 Anwendung sind (z.B. eine Session-Middleware).
91
92 Hier zunächst einmal der Quellcode für unsere Datei ``shorty/application.py``,
93 in der wir die WSGI-Anwendung ablegen::
94
95 from sqlalchemy import create_engine
96 from werkzeug import Request, ClosingIterator
97 from werkzeug.exceptions import HTTPException
98
99 from shorty.utils import session, metadata, local, local_manager, url_map
100 from shorty import views
101 import shorty.models
102
103
104 class Shorty(object):
105
106 def __init__(self, db_uri):
107 local.application = self
108 self.database_engine = create_engine(db_uri, convert_unicode=True)
109
110 def init_database(self):
111 metadata.create_all(self.database_engine)
112
113 def __call__(self, environ, start_response):
114 local.application = self
115 request = Request(environ)
116 local.url_adapter = adapter = url_map.bind_to_environ(environ)
117 try:
118 endpoint, values = adapter.match()
119 handler = getattr(views, endpoint)
120 response = handler(request, **values)
121 except HTTPException, e:
122 response = e
123 return ClosingIterator(response(environ, start_response),
124 [session.remove, local_manager.cleanup])
125
126 Ziemlich viel für Code für den Anfang ... gehen wir ihn mal Schritt für Schritt
127 durch. Zunächst sehen wir einige Imports: Aus dem Paket `sqlalchemy` holen wir
128 uns eine Factory-Funktion, die eine neue Datenbank-Engine für uns erstellt, die
129 wiederum einen Connection-Pool bereithält. Die nächsten Imports holen einige
130 Objekte in den Namensraum, die uns Werkzeug zur Verfügung stellt: ein
131 Request-Objekt; ein spezieller Iterator, der uns hilft am Ende eines Requests
132 einige Dinge aufzuräumen; und schließlich die Basisklasse für alle
133 HTTP-Exceptions.
134
135 Die nächsten fünf Imports funktionieren noch nicht, weil wir das `utils`-Modul
136 noch nicht erstellt haben. Doch wir werden trotzdem schon ein wenig über diese
137 Objekte sprechen. Das Objekt `session` ist kein aus PHP bekanntes
138 Session-Array, sondern eine Datenbank-Session von SQLAlchemy. Alle
139 Datenbank-Models, die im Kontext eines Requests erstellt werden, sind auf
140 diesem Objekt zwischengespeichert, so dass man Änderungen mit einem Schlag zum
141 Server senden kann. Im Gegensatz zu Django wird ein instantiiertes
142 Datenbank-Model automatisch von der Session verwaltet und ist in dieser Session
143 ein Singleton. Es kann also niemals zwei Instanzen des selben
144 Datenbankeintrags in einer Session geben. Das Objekt `metadata` stammt
145 ebenfalls aus SQLAlchemy und speichert Informationen über die Tabellen der
146 Datenbank. Es stellt zum Beispiel eine Funktion bereit, die alle im Model
147 definierten Tabellen in der Datenbank erstellt.
148
149 Das Objekt `local` ist ein kontext-lokales Objekt, erstellt vom Modul `utility`.
150 Attributzugriffe auf dieses Objekt sind an den aktuellen Request gebunden,
151 d.h. jeder Request bekommt ein anderes Objekt zurück und kann verschiedene
152 Objekte ablegen, ohne Threadingprobleme zu bekommen. Der `local_manager` wird
153 genutzt, um am Ende des Requests alle auf dem `local`-Objekt gespeicherten Daten
154 wieder freizugeben.
155
156 Der letzte Import von dort ist die URL-Map, die alle URL-Routen verwaltet.
157 Solltest du bereits mit Django gearbeitet haben, ist dies vergleichbar mit den
158 regulären Ausdrücken in der jeweiligen ``urls.py``. Kennst du PHP, ist die
159 URL-Map ähnlich einem eingebauten `mod_rewrite`.
160
161 Zusätzlich importieren wir hier unser `views`-Modul, das die View-Funktionen
162 enthält, sowie das `models`-Modul, in welchem unsere Models definiert sind.
163 Auch wenn es so aussieht, als ob wir diesen Import nicht nutzen, ist er
164 wichtig. Nur dadurch werden unsere Tabellen auf dem `metadata`-Objekt
165 registriert.
166
167 Schauen wir auf die Anwendungsklasse. Der Konstruktor dieser Klasse nimmt die
168 Datenbank-URI entgegen, die -- einfach gesagt -- den Typ der Datenbank und die
169 Verbindungsdaten enthält. Für SQLite das wäre zum Beispiel
170 ``sqlite:////tmp/shorty.db`` (die vier Slashes sind **kein** Tippfehler).
171
172 Im Konstruktor erstellen wir auch gleich eine Datenbank-Engine für diese URI und
173 aktivieren das automatische Umwandeln von Bytestrings nach Unicode. Das ist
174 nützlich, weil sowohl Jinja als auch Werkzeug intern nur Unicode verwenden.
175
176 Des Weiteren binden wir die Anwendung an das `local`-Objekt. Das ist
177 eigentlich nicht nötig, aber nützlich, wenn wir mit der Anwendung in der
178 Python-Shell spielen wollen. Damit werden direkt nach dem Instantiieren der
179 Anwendung die Datenbankfunktionen testen. Wenn wir das nicht tun, wird der
180 Python-Interpreter einen Fehler werfen, wenn außerhalb eines Requests versucht
181 wird, eine SQLAlchemy-Session zu erstellen.
182
183 Die Methode `init_database` können wir später im Managementscript verwenden, um
184 alle Tabellen zu erstellen, die wir definiert haben.
185
186 Nun zur eigentlichen WSGI-Anwendung, der `__call__`-Methode. Dort passiert das
187 so genannte "Request Dispatching", also das Weiterleiten von eingehenden
188 Anfragen zu den richtigen Funktionen. Als Erstes erstellen wir dort ein neues
189 Request-Objekt, um nicht direkt mit `environ`, dem Dictionary mit den
190 Umgebungsvariablen, arbeiten zu müssen. Dann binden wir die Anwendung an das
191 `local`-Objekt für den aktuellen Kontext.
192
193 Anschließend erstellen wir einen URL-Adapter, indem wir die URL-Map an die
194 aktuelle WSGI-Umgebung binden. Der Adapter weiß dann, wie die aktuelle URL
195 aussieht, wo die Anwendung eingebunden ist etc. Diesen Adapter können wir
196 nutzen, um URLs zu erzeugen oder gegen den aktuellen Request zu matchen. Wir
197 binden diesen Adapter auch an das `local`-Objekt, damit wir im `utils`-Modul
198 auf ihn zugreifen können.
199
200 Danach kommt ein `try`/`except`-Konstrukt, das HTTP-Fehler abfängt, die während
201 des Matchings oder in einer View-Funktion auftreten können. Wenn der Adapter
202 keinen Endpoint für die aktuelle URL findet, wird er eine `NotFound`-Exception
203 werfen, die wir wie ein Response Objekt aufrufen können. Der Endpoint ist in
204 unserem Fall der Name der Funktion im `views`-Modul, die wir aufrufen möchten.
205 Wir suchen uns einfach mit `getattr` die Funktion dem Namen nach heraus und
206 rufen sie mit dem Request-Objekt und den URL-Werten auf.
207
208 Am Schluss rufen wir das gewonnene Response-Objekt (oder die Exception) als
209 WSGI-Anwendung auf und übergeben den Rückgabewert dieser Funktion an den
210 `ClosingIterator`, zusammen mit zwei Funktionen fürs Aufräumen. Dies schließt
211 die SQLAlchemy-Session und leert das `local`-Objekt für diesen Request.
212
213 Nun müssen wir zwei leere Dateien ``shorty/views.py`` und ``shorty/models.py``
214 erstellen, damit die Imports nicht fehlschlagen. Den tatsächlichen Code für
215 diese Module werden wir ein wenig später erstellen.
216
217
218 Teil 2: Die Utilities
219 =====================
220
221 Nun haben wir die eigentliche WSGI-Applikation fertig gestellt, aber wir müssen
222 das Utility-Modul noch um Code ergänzen, damit die Imports klappen. Fürs Erste
223 fügen wir nur die Objekte hinzu, die wir brauchen, damit die Applikation
224 funktioniert. Der folgende Code landet in der Datei ``shorty/utils.py``:
225
226 .. sourcecode:: python
227
228 from sqlalchemy import MetaData
229 from sqlalchemy.orm import create_session, scoped_session
230 from werkzeug import Local, LocalManager
231 from werkzeug.routing import Map, Rule
232
233 local = Local()
234 local_manager = LocalManager([local])
235 application = local('application')
236
237 metadata = MetaData()
238 session = scoped_session(lambda: create_session(application.database_engine,
239 transactional=True), local_manager.get_ident)
240
241 url_map = Map()
242 def expose(rule, **kw):
243 def decorate(f):
244 kw['endpoint'] = f.__name__
245 url_map.add(Rule(rule, **kw))
246 return f
247 return decorate
248
249 def url_for(endpoint, _external=False, **values):
250 return local.url_adapter.build(endpoint, values, force_external=_external)
251
252 Zunächst importieren wir wieder eine Menge, dann erstellen wir das
253 `local`-Objekt und den Manager dafür, wie bereits im vorherigen Abschnitt
254 besprochen. Neu ist hier, dass der Aufruf eines `local`-Objekts mit einem
255 String ein Proxy-Objekt zurück gibt. Dieses zeigt stets auf die gleichnamigen
256 Attribute des `local`-Objekts. Beispielsweise verweist nun `application`
257 dauerhaft auf `local.application`. Wenn du jedoch darauf zugreifst und kein
258 Objekt an `local.application` gebunden ist, erhältst du einen `RuntimeError`.
259
260 Die folgenden drei Zeilen sind im Prinzip alles, um SQLAlchemy 0.4 oder höher in
261 eine Werkzeug-Anwendung einzubinden. Wir erstellen ein Metadaten-Objekt für all
262 unsere Tabellen sowie eine "scoped session" über die
263 `scoped_session`-Factory-Funktion. Dadurch wird SQLAlchemy angewiesen,
264 praktisch denselben Algorithmus zur Ermittlung des aktuellen Kontextes zu
265 verwenden, wie es auch Werkzeug für die `local`-Objekte tut, und die
266 Datenbank-Engine der aktuellen Applikation zu benutzen.
267
268 Wenn wir nicht vorhaben, mehrere Instanzen der Applikation in derselben Instanz
269 des Python-Interpreters zu unterstützen, können wir den Code einfach halten,
270 indem wir nicht über das aktuelle `local`-Objekt auf die Applikation zugreifen,
271 sondern einen anderen Weg nehmen. Dieser Ansatz wird etwa von Django verfolgt,
272 macht es allerdings unmöglich, mehrere solcher Applikationen zu kombinieren.
273
274 Der restliche Code des Moduls wird für unsere Views benutzt. Die Idee besteht
275 darin, Dekoratoren zu benutzen, um die URL-Dispatching-Regeln für
276 View-Funktionen festzulegen, anstatt ein zentrales Modul ``urls.py`` zu
277 verwenden, wie es Django tut, oder über eine ``.htaccess``-Datei URLs
278 umzuschreiben, wie man es in PHP machen würde. Dies ist **eine** Möglichkeit,
279 dies zu tun, und es gibt unzählige andere Wege der Handhabung von
280 URL-Regeldefinitionen.
281
282 Die Funktion `url_for`, die wir ebenfalls definieren, bietet einen einfachen
283 Weg, URLs anhand des Endpointes zu generieren. Wir werden sie später in den
284 Views als auch unserem Model verwenden.
285
286
287 Unterbrechung: Und nun etwas komplett anderes
288 =============================================
289
290 Da wir nun das Grundgerüst für unsere Anwendung fertig gestellt haben, können
291 wir jetzt erst einmal relaxen und uns etwas komplett anderem zuwenden: den
292 Verwaltungs-Scripts. Während der Entwicklung erledigt man häufig immer
293 wiederkehrende Aufgaben, wie zum Beispiel das Starten eines Entwicklungs-Servers
294 (im Gegensatz zu PHP benötigt Werkzeug keinen Apache-Server; der in Python
295 integrierte `wsgiref`-Server ist völlig ausreichend und für die Entwicklung auf
296 jeden Fall empfehlenswert), das Starten eines Python-Interpreters (um mit den
297 Datenbankobjekten herumzuspielen oder die Datenbank zu initialisieren) etc.
298
299 Werkzeug macht es unglaublich einfach, solche Verwaltungs-Scripts zu schreiben.
300 Der folgende Code implementiert ein voll funktionsfähiges Verwaltungs-Script
301 und gehört in die `manage.py`-Datei, welche du am Anfang erstellt hast:
302
303 .. sourcecode:: python
304
305 #!/usr/bin/env python
306 from werkzeug import script
307
308 def make_app():
309 from shorty.application import Shorty
310 return Shorty('sqlite:////tmp/shorty.db')
311
312 def make_shell():
313 from shorty import models, utils
314 application = make_app()
315 return locals()
316
317 action_runserver = script.make_runserver(make_app, use_reloader=True)
318 action_shell = script.make_shell(make_shell)
319 action_initdb = lambda: make_app().init_database()
320
321 script.run()
322
323 `werkzeug.script` ist genauer in der `Script-Dokumentation`_ beschrieben, und da
324 der Großteil des Codes verständlich sein sollte, werden wir hier nicht näher
325 darauf eingehen.
326
327 Es ist aber wichtig, dass du ``python manage.py shell`` ausführen kannst, um
328 eine interaktive Python-Shell zu starten. Solltest du einen Traceback
329 bekommen, kontrolliere bitte die darin genannte Code-Zeile und vergleiche sie
330 mit dem entsprechenden Code in dieser Anleitung.
331
332 Sobald das Script läuft, können wir mit dem Schreiben der Datenbank-Models
333 beginnen.
334
335 .. _Script-Dokumentation: script.txt
336
337
338 Teil 3: Datenbank-Models
339 ========================
340
341 Jetzt können wir die Models erstellen. Da die Anwendung ziemlich einfach ist,
342 haben wir nur ein Model und eine Tabelle:
343
344 .. sourcecode:: python
345
346 from datetime import datetime
347 from sqlalchemy import Table, Column, String, Boolean, DateTime
348 from shorty.utils import session, metadata, url_for, get_random_uid
349
350 url_table = Table('urls', metadata,
351 Column('uid', String(140), primary_key=True),
352 Column('target', String(500)),
353 Column('added', DateTime),
354 Column('public', Boolean)
355 )
356
357 class URL(object):
358
359 def __init__(self, target, public=True, uid=None, added=None):
360 self.target = target
361 self.public = public
362 self.added = added or datetime.utcnow()
363 if not uid:
364 while True:
365 uid = get_random_uid()
366 if not URL.query.get(uid):
367 break
368 self.uid = uid
369
370 @property
371 def short_url(self):
372 return url_for('link', uid=self.uid, _external=True)
373
374 def __repr__(self):
375 return '<URL %r>' % self.uid
376
377 session.mapper(URL, url_table)
378
379
380 Dieses Modul ist gut überschaubar. Wir importieren alles, was wir von
381 SQLAlchemy benötigen, und erstellen die Tabelle. Dann fügen wir eine Klasse
382 für diese Tabelle hinzu und verbinden beide miteinander. Für eine
383 detailliertere Erklärung bezüglich SQLAlchemy solltest du dir das
384 `exzellente Tutorial`_ anschauen.
385
386 Im Konstruktor generieren wir solange eine eindeutige ID, bis wir eine finden,
387 die noch nicht belegt ist. Die `get_random_uid`-Funktion fehlt -- wir müssen
388 sie noch in unser `utils`-Modul einfügen:
389
390 .. sourcecode:: python
391
392 from random import sample, randrange
393
394 URL_CHARS = 'abcdefghijkmpqrstuvwxyzABCDEFGHIJKLMNPQRST23456789'
395
396 def get_random_uid():
397 return ''.join(sample(URL_CHARS, randrange(3, 9)))
398
399 Wenn das getan ist, können wir ``python manage.py initdb`` ausführen, um die
400 Datenbank zu erstellen und ``python manage.py shell``, um damit herumzuspielen:
401
402 .. sourcecode:: pycon
403
404 Interactive Werkzeug Shell
405 >>> from shorty.models import session, URL
406
407 Jetzt können wir einige URLs zu der Datenbank hinzufügen:
408
409 .. sourcecode:: pycon
410
411 >>> urls = [URL('http://example.org/'), URL('http://localhost:5000/')]
412 >>> URL.query.all()
413 []
414 >>> session.commit()
415 >>> URL.query.all()
416 [<URL '5cFbsk'>, <URL 'mpugsT'>]
417
418 Wie du sehen kannst, müssen wir ``session.commit()`` aufrufen, um die Änderungen
419 in der Datenbank zu speichern. Nun erstellen wir ein privates Element mit einer
420 eigenen UID:
421
422 .. sourcecode:: pycon
423
424 >>> URL('http://werkzeug.pocoo.org/', False, 'werkzeug-webpage')
425 >>> session.commit()
426
427 Dann fragen wir alle ab:
428
429 .. sourcecode:: pycon
430
431 >>> URL.query.filter_by(public=False).all()
432 [<URL 'werkzeug-webpage'>]
433 >>> URL.query.filter_by(public=True).all()
434 [<URL '5cFbsk'>, <URL 'mpugsT'>]
435 >>> URL.query.get('werkzeug-webpage')
436 <URL 'werkzeug-webpage'>
437
438 Jetzt haben wir einige Datensätze in der Datenbank und wissen ungefähr, auf
439 welche Weise SQLAlchemy funktioniert. Zeit, unsere Views zu erstellen.
440
441 .. _exzellente Tutorial: http://www.sqlalchemy.org/docs/04/ormtutorial.html
442
443
444 Teil 4: Die View-Funktionen
445 ===========================
446
447 Nachdem wir mit SQLAlchemy herumgespielt haben, können wir zurück zu Werkzeug
448 gehen und anfangen, unsere View-Funktionen zu erstellen. Der Begriff
449 "View-Funktion" kommt von Django. Dort werden die Funktionen, die Templates
450 befüllen und ausgeben, so genannt. Deshalb ist unser Beispiel eine Umsetzung
451 von MVT (Model, View, Template) und nicht etwa MVC (Model, View, Controller).
452 Die beiden Bezeichnungen bedeuten dasselbe, aber es ist viel einfacher,
453 dieselbe Benennung wie Django zu nutzen.
454
455 Als Anfang erstellen wir einfach eine View-Funktion für neue URLs und eine
456 Funktion, die eine Nachricht über einen neuen Link darstellt. Das wird der
457 Inhalt unserer noch leeren ``views.py``-Datei:
458
459 .. sourcecode:: python
460
461 from werkzeug import redirect
462 from werkzeug.exceptions import NotFound
463 from shorty.utils import session, render_template, expose, validate_url, \
464 url_for
465 from shorty.models import URL
466
467 @expose('/')
468 def new(request):
469 error = url = ''
470 if request.method == 'POST':
471 url = request.form.get('url')
472 alias = request.form.get('alias')
473 if not validate_url(url):
474 error = u"Entschuldigung, aber ich kann die angegebene " \
475 u"URL nicht kürzen."
476 elif alias:
477 if len(alias) > 140:
478 error = 'Dein Alias ist zu lang'
479 elif '/' in alias:
480 error = 'Dein Alias darf keinen Slash beinhalten'
481 elif URL.query.get(alias):
482 error = 'Der angegeben Alias existiert bereits'
483 if not error:
484 uid = URL(url, 'private' not in request.form, alias).uid
485 session.commit()
486 return redirect(url_for('display', uid=uid))
487 return render_template('new.html', error=error, url=url)
488
489 @expose('/display/<uid>')
490 def display(request, uid):
491 url = URL.query.get(uid)
492 if not url:
493 raise NotFound()
494 return render_template('display.html', url=url)
495
496 @expose('/u/<uid>')
497 def link(request, uid):
498 url = URL.query.get(uid)
499 if not url:
500 raise NotFound()
501 return redirect(url.target, 301)
502
503 @expose('/list/', defaults={'page': 1})
504 @expose('/list/<int:page>')
505 def list(request, page):
506 pass
507
508 Wieder einmal ziemlich viel Code, aber das meiste ist normale
509 Formularvalidierung. Wir erstellen zwei Funktionen, `new` und `display`, und
510 dekorieren sie mit unserem `expose`-Dekorator aus dem `utils`-Modul. Dieser
511 Dekorator fügt eine neue Regel zur URL-Map hinzu, indem er alle Parameter zum
512 Konstruktor eines Rule-Objekts übergibt und den Endpoint auf den Namen der
513 Funktion setzt. Damit können wir einfach URLs zu den Funktionen erzeugen --
514 wir nutzen ihre Funktionsnamen als Endpoint.
515
516 Denke daran, dass dieser Code nicht unbedingt eine gute Idee für größere
517 Anwendungen ist. In solchen Fällen ist es besser, den vollen Importnamen mit
518 einem allgemeinen Prefix oder etwas Ähnlichem als Endpoint zu nutzen. Sonst
519 wird es ziemlich verwirrend.
520
521 Die Formularvalidierung in der `new`-Methode ist ziemlich simpel. Wir
522 kontrollieren, ob die aktuelle HTTP-Methode `POST` ist. Falls ja nehmen wir
523 die Daten vom Request und validieren sie. Wenn dort kein Fehler auftritt,
524 erstellen wir ein neues `URL`-Objekt, übergeben es der Datenbank und leiten auf
525 die Anzeige-Seite um.
526
527 Die `display`-Funktion ist nicht viel komplizierter. Die URL-Rule erwartet
528 einen `uid`-Parameter, welchen die Funktion entsprechend akzeptiert. Danach
529 holen wir das `URL`-Objekt mit der angegebenen UID und geben das Template aus,
530 dem wir wiederum das `URL`-Objekt übergeben.
531
532 Wenn die URL nicht existiert, werfen wir eine `NotFound`-Exception, welche eine
533 statische "404 Seite nicht gefunden"-Seite anzeigt. Wir können diese später
534 mit einer speziellen Fehlerseite ersetzen, indem wir die Exception auffangen,
535 bevor die `HTTPException` geworfen wird.
536
537 Die View-Funktion `link` wird von unseren Models in der `short_url`-Eigenschaft
538 genutzt und ist die kurze URL, die wir vermitteln. Wenn also die URL-UID
539 ``foobar`` ist, wird die URL unter ``http://localhost:5000/u/foobar``
540 erreichbar sein.
541
542 Die View-Funktion `list` wurde noch nicht geschrieben, das machen wir später.
543 Wichtig ist allerdings, dass die Funktion einen optionalen URL-Parameter
544 akzeptiert. Der erste Dekorator sagt Werkzeug, dass für den Request-Pfad
545 ``/page/`` die erste Seite angezeigt wird (da der Parameter `page`
546 standardmäßig auf 1 gesetzt wird). Wichtiger ist die Tatsache, dass Werkzeug
547 URLs normalisiert. Wenn du also ``/page`` oder ``/page/1`` aufrufst, wirst du
548 in beiden Fällen zu ``/page/`` umgeleitet. Das geschieht automatisch und macht
549 Google glücklich. Wenn du dieses Verhalten nicht magst, kannst du es
550 abschalten.
551
552 Und wieder einmal müssen wir zwei Objekte aus dem `utils`-Modul importieren,
553 die jetzt noch nicht existieren. Eines von diesen soll ein Jinja-Template in
554 ein Response-Objekt verwandeln, das andere prüft eine URL. Fügen wir also
555 diese zu ``utils.py`` hinzu:
556
557 .. sourcecode:: python
558
559 from os import path
560 from urlparse import urlparse
561 from werkzeug import Response
562 from jinja import Environment, FileSystemLoader
563
564 ALLOWED_SCHEMES = frozenset(['http', 'https', 'ftp', 'ftps'])
565 TEMPLATE_PATH = path.join(path.dirname(__file__), 'templates')
566 jinja_env = Environment(loader=FileSystemLoader(TEMPLATE_PATH))
567 jinja_env.globals['url_for'] = url_for
568
569 def render_template(template, **context):
570 return Response(jinja_env.get_template(template).render(**context),
571 mimetype='text/html')
572
573 def validate_url(url):
574 return urlparse(url)[0] in ALLOWED_SCHEMES
575
576 Im Grunde ist das alles. Die Validierungsfunktion prüft, ob deine URL wie eine
577 HTTP- oder FTP-URL aussieht. Wir machen dies, um uns zu versichern, dass
578 niemand potentiell gefährliches JavaScript oder ähnliche URLs abschickt. Die
579 `render_template`-Funktion ist auch nicht viel komplizierter, sie schaut nach
580 einem Template im Dateisystem im `templates`-Ordner und gibt es als Response
581 aus.
582
583 Weiterhin fürgen wir die `url_for`-Funktion in den globalen Kontext des
584 Templates ein, so dass wir auch in Templates URLs erzeugen können.
585
586 Da wir jetzt unsere beiden ersten View-Funktionen haben, ist es Zeit, die
587 Templates hinzuzufügen.
588
589
590 Teil 5: Die Templates
591 =====================
592
593 Wir haben beschlossen, in diesem Beispiel Jinja-Templates zu nutzen. Wenn du
594 weißt, wie man Django-Templates nutzt, sollte es dir bekannt vorkommen; wenn du
595 bis jetzt mit PHP gearbeitet hast, kannst du Jinja-Templates mit Smarty
596 vergleichen. Wenn du bis jetzt PHP als Templatesprache genutzt hast, solltest
597 du für dein nächstes Projekt mal `Mako`_ ansehen.
598
599 **Sicherheitswarnung**: Wir nutzen hier Jinja, welches eine textbasierte
600 Template-Engine ist. Da Jinja nicht weiß, womit es arbeitet, musst du, wenn du
601 HTML-Templates erstellst, *alle* Werte maskieren, die irgendwann an
602 irgendeinem Punkt irgendeines der folgenden Zeichen enthalten können: ``>``,
603 ``<`` oder ``&``. Innerhalb von Attributen musst du außerdem Anführungszeichen
604 maskieren. Du kannst Jinjas ``|e``-Filter für normales Escaping benutzen.
605 Wenn du `true` als Argument übergibst, maskiert es außerdem Anführungszeichen
606 (``|e(true)``). Wie du in den Beispielen unterhalb sehen kannst, maskieren wir
607 die URLs nicht. Der Grund dafür ist, dass wir keine ``&`` in den URLs haben
608 und deshalb ist es sicher, auf Escaping zu verzichten.
609
610 Der Einfachheit halber werden wir HTML 4 in unseren Templates nutzen. Wenn du
611 etwas Erfahrung mit XHTML hast, kannst du sie in XHTML schreiben. Aber beachte,
612 dass das Beispiel-Stylesheet unten nicht mit XHTML funktioniert.
613
614 Eine coole Sache, die Jinja von Django übernommen hat, ist Templatevererbung.
615 Das bedeutet, dass wir oft genutzte Stücke in ein Basistemplate auslagern und
616 es mit Platzhaltern füllen können. Beispielsweise landen der Doctype und der
617 HTML-Rahmen in der Datei ``templates/layout.html``:
618
619 .. sourcecode:: html+jinja
620
621 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
622 "http://www.w3.org/TR/html4/strict.dtd">
623 <html>
624 <head>
625 <title>Shorty</title>
626 </head>
627 <body>
628 <h1><a href="{{ url_for('new') }}">Shorty</a></h1>
629 <div class="body">{% block body %}{% endblock %}</div>
630 <div class="footer">
631 <a href="{{ url_for('new') }}">Neu</a> |
632 <a href="{{ url_for('list') }}">Liste</a> |
633 Benutze Shorty für Gutes, nicht für Böses
634 </div>
635 </body>
636 </html>
637
638 Von diesem Template können wir in unserer ``templates/new.html`` erben:
639
640 .. sourcecode:: html+jinja
641
642 {% extends 'layout.html' %}
643 {% block body %}
644 <h2>Erstelle eine Shorty-URL!</h2>
645 {% if error %}<div class="error">{{ error }}</div>{% endif -%}
646 <form action="" method="post">
647 <p>Gebe die URL an, die du kürzen willst</p>
648 <p><input type="text" name="url" id="url" value="{{ url|e(true) }}"></p>
649 <p>Optional kannst du der URL einen merkbaren Namen geben</p>
650 <p><input type="text" id="alias" name="alias">{#
651 #}<input type="submit" id="submit" value="Mach!"></p>
652 <p><input type="checkbox" name="private" id="private">
653 <label for="private">mache diese URL privat, also zeige sie
654 nicht auf der Liste</label></p>
655 </form>
656 {% endblock %}
657
658 Wenn du dich über den Kommentar zwischen den beiden `input`-Elementen wunderst,
659 das ist ein sauberer Trick, die Templates sauber zu halten ohne Leerzeichen
660 zwischen beide Elemente zu setzen. Wir haben ein Stylesheet vorbereitet,
661 welches dort keine Leerzeichen erwartet.
662
663 Ein zweites Template für die Anzeige-Seite (``templates/display.html``):
664
665 .. sourcecode:: html+jinja
666
667 {% extends 'layout.html' %}
668 {% block body %}
669 <h2>Verkürzte URL</h2>
670 <p>
671 Die URL {{ url.target|urlize(40, true) }}
672 wurde gekürzt zu {{ url.short_url|urlize }}.
673 </p>
674 {% endblock %}
675
676 Jinjas `urlize`-Filter übersetzt eine URL in einem Text in einen klickbaren
677 Link. Wenn du ihm einen Integer übergibst, wird er den angezeigten Text auf
678 diese Anzahl Zeichen kürzen; wenn du `true` als zweiten Parameter übergibst,
679 wird ein `nofollow`-Flag hinzugefügt.
680
681 Da wir jetzt unsere ersten beiden Templates fertig haben, ist es Zeit, den
682 Server zu starten und auf den Teil der Anwendung zu schauen, der bereits
683 funktioniert: neue URLs hinzufügen und weitergeleitet werden.
684
685
686 Zwischenschritt: Das Design hinzufügen
687 ======================================
688
689 Jetzt ist es Zeit, etwas anderes zu machen: ein Design hinzufügen.
690 Designelemente sind normalerweise in statischen CSS-Stylesheets definiert.
691 Also müssen wir einige statische Dateien irgendwo ablegen -- aber das ist ein
692 wenig kompliziert. Wenn du mit bis jetzt mit PHP gearbeitet hast, wirst du
693 gemerkt haben, dass es hier nichts gibt, was eine URL zum Dateisystempfad
694 übersetzt und so direkt auf statische Dateien zugreift. Du musst dem Webserver
695 oder unserem Entwicklungsserver explizit sagen, dass es einen Pfad gibt, der
696 die statischen Dateien beinhaltet.
697
698 Django empfiehlt eine separate Subdomain und einen eigenen Server für die
699 statischen Dateien, was eine sehr gute Idee für Umgebungen mit hoher Serverlast
700 ist, aber zu viel des Guten für diese simple Anwendung.
701
702 Hier also folgendes Vorgehen: Wir lassen unsere Anwendung die statischen Dateien
703 ausliefern, aber im Produktionsmodus solltest du dem Apachen mitteilen, dass er
704 diese Dateien selbst ausliefern soll. Das geschieht mit Hilfe der
705 `Alias`-Direktive in der Konfiguration von Apache:
706
707 .. sourcecode:: apache
708
709 Alias /static /path/to/static/files
710
711 Das ist um einiges schneller.
712
713 Und wie sagen wir unserer Anwendung, dass sie den Ordner mit statischen Dateien
714 als ``/static`` verfügbar machen soll? Glücklicherweise ist das ziemlich
715 einfach, da Werkzeug dafür eine WSGI-Middleware liefert. Es gibt zwei
716 Möglichkeiten, diese zu integrieren: Entweder wrappen wir die ganze Anwendung
717 in diese Middleware (diesen Weg empfehlen wir wirklich nicht) oder wir wrappen
718 nur die Ausführungsfunktion (viel besser, weil wir die Referenz auf das
719 Anwendungsobjekt nicht verlieren). Also gehen wir zurück zur
720 ``application.py`` und passen den Code ein wenig an.
721
722 Als Erstes musst du einen neuen Import hinzufügen und den Pfad zu den
723 statischen Dateien ermitteln:
724
725 .. sourcecode:: python
726
727 from os import path
728 from werkzeug import SharedDataMiddleware
729
730 STATIC_PATH = path.join(path.dirname(__file__), 'static')
731
732 Es wäre besser, die Pfad-Manipulation in die ``utils.py``-Datei zu verschieben,
733 weil wir den Template-Pfad bereits dort ermittelt haben. Aber das ist nicht
734 wirklich von Interesse, und wegen der Einfachheit können wir es im
735 Anwendungsmodul lassen.
736
737 Wie können wir also die Ausführungsfunktion wrappen? Theoretisch müssen wir
738 einfach ``self.__call__ = wrap(self.__call__)`` schreiben, doch leider klappt
739 das so nicht in Python. Es ist aber nicht viel schwieriger: Benenne einfach
740 `__call__` in `dispatch` um und füge eine neue `__call__`-Methode hinzu:
741
742 .. sourcecode:: python
743
744 def __call__(self, environ, start_response):
745 return self.dispatch(environ, start_response)
746
747 Jetzt können wir in unsere `__init__`-Funktion gehen und die Middleware
748 zuschalten, indem wir die `dispatch`-Methode einschieben:
749
750 .. sourcecode:: python
751
752 self.dispatch = SharedDataMiddleware(self.dispatch, {
753 '/static': STATIC_PATH
754 })
755
756 Das war jetzt nicht schwer. Mit diesem Weg können wir WSGI-Middlewares in der
757 Anwendungsklasse einhaken!
758
759 Eine andere gute Idee ist es, unserer `url_map` im `utils`-Modul den Ort
760 unserer statischen Dateien mitzuteilen, indem wir eine Regel hinzufügen. Auf
761 diesem Weg können wir URLs zu den statischen Dateien in den Templates
762 generieren:
763
764 .. sourcecode:: python
765
766 url_map = Map([Rule('/static/<file>', endpoint='static', build_only=True)])
767
768 Jetzt können wir unsere ``templates/layout.html``-Datei wieder öffnen und einen
769 Link zum ``style.css``-Stylesheet hinzufügen, welches wir danach erstellen
770 werden:
771
772 .. sourcecode:: html+jinja
773
774 <link rel="stylesheet" type="text/css" href="{{ url_for('static',
775 file='style.css') }}">
776
777 Das geht natürlich in den `<head>`-Tag, wo zur Zeit nur der Titel festgelegt
778 ist.
779
780 Du kannst jetzt ein nettes Layout gestalten oder das `Beispiel-Stylesheet`_
781 nutzen. In beiden Fällen musst du es in die Datei ``static/style.css``
782 einfügen.
783
784 .. _Mako: http://www.makotemplates.org/
785 .. _Beispiel-Stylesheet: http://dev.pocoo.org/projects/werkzeug/browser/examples/shorty/static/style.css
786
787
788 Teil 6: Öffentliche URLs auflisten
789 ==================================
790
791 Jetzt wollen wir alle öffentlichen URLs auf der "List"-Seite auflisten. Das
792 sollte kein großes Problem sein, aber wir wollen auch eine Art Seitenumbruch
793 haben. Da wir alle URLs auf einmal ausgeben, haben wir früher oder später
794 eine endlose Seite, die Minuten zum Laden benötigt.
795
796 Beginnen wir also mit dem Hinzufügen einer `Pagination`-Klasse zu unserem
797 `utils`-Modul:
798
799 .. sourcecode:: python
800
801 from werkzeug import cached_property
802
803 class Pagination(object):
804
805 def __init__(self, query, per_page, page, endpoint):
806 self.query = query
807 self.per_page = per_page
808 self.page = page
809 self.endpoint = endpoint
810
811 @cached_property
812 def count(self):
813 return self.query.count()
814
815 @cached_property
816 def entries(self):
817 return self.query.offset((self.page - 1) * self.per_page) \
818 .limit(self.per_page).all()
819
820 has_previous = property(lambda x: x.page > 1)
821 has_next = property(lambda x: x.page < x.pages)
822 previous = property(lambda x: url_for(x.endpoint, page=x.page - 1))
823 next = property(lambda x: url_for(x.endpoint, page=x.page + 1))
824 pages = property(lambda x: max(0, x.count - 1) // x.per_page + 1)
825
826 Dies ist eine sehr einfache Klasse, die das meiste der Seitenumbrüche für uns
827 übernimmt. Wir können ihr eine unausgeführte SQLAlchemy-Abfrage (Query)
828 übergeben, die Anzahl der Elemente pro Seite, die aktuelle Seite und den
829 Endpoint, welcher für die URL-Generation benutzt wird. Der
830 `cached_property`-Dekorator funktioniert, wie du siehst, fast genau so wie der
831 normale `property`-Dekorator, mit der Ausnahme, dass er sich das Ergebnis merkt.
832 Wir werden diese Klasse nicht genau besprechen, aber das Grundprinzip ist, dass
833 ein Zugriff auf `pagination.entries` die Elemente für die aktuelle Seite ausgibt
834 und dass die anderen Eigenschaften Werte zurückgeben, sodass wir sie im Template
835 nutzen können.
836
837 Jetzt können wir die `Pagination`-Klasse in unser Views-Modul importieren und
838 etwas Code zu der `list`-Funktion hinzufügen:
839
840 .. sourcecode:: python
841
842 from shorty.utils import Pagination
843
844 @expose('/list/', defaults={'page': 1})
845 @expose('/list/<int:page>')
846 def list(request, page):
847 query = URL.query.filter_by(public=True)
848 pagination = Pagination(query, 30, page, 'list')
849 if pagination.page > 1 and not pagination.entries:
850 raise NotFound()
851 return render_template('list.html', pagination=pagination)
852
853 Die If-Bedingung in dieser Funktion versichert, dass ein Statuscode 404
854 zurückgegeben wird, wenn wir nicht auf der ersten Seite sind und es keine
855 Einträge zum Anzeigen gibt (einen Aufruf von ``/list/42`` ohne Einträge auf
856 dieser Seite nicht mit einem 404 zu quittieren, wäre schlechter Stil).
857
858 Und schließlich das Template:
859
860 .. sourcecode:: html+jinja
861
862 {% extends 'layout.html' %}
863 {% block body %}
864 <h2>URL Liste</h2>
865 <ul>
866 {%- for url in pagination.entries %}
867 <li><a href="{{ url.short_url|e }}">{{ url.uid|e }}</a> &raquo;
868 <small>{{ url.target|urlize(38, true) }}</small></li>
869 {%- else %}
870 <li><em>keine URLs bis jetzt gekürzt</em></li>
871 {%- endfor %}
872 </ul>
873 <div class="pagination">
874 {%- if pagination.has_previous %}<a href="{{ pagination.previous
875 }}">&laquo; Vorherige</a>
876 {%- else %}<span class="inactive">&laquo; Vorherige</span>{% endif %}
877 | {{ pagination.page }} |
878 {% if pagination.has_next %}<a href="{{ pagination.next
879 }}">Nächste &raquo;</a>
880 {%- else %}<span class="inactive">Nächste &raquo;</span>{% endif %}
881 </div>
882 {% endblock %}
883
884
885 Bonus: 404-Fehlerseiten gestalten
886 =================================
887
888 Jetzt, da wir unsere Anwendung fertig gestellt haben, können wir kleine
889 Verbesserungen vornehmen, zum Beispiel eigene 404-Fehlerseiten. Das ist
890 ziemlich einfach. Das Erste, was wir machen müssen, ist eine neue Funktion
891 namens `not_found` in den Views zu erstellen, welche ein Template ausgibt:
892
893 .. sourcecode:: python
894
895 def not_found(request):
896 return render_template('not_found.html')
897
898 Dann müssen wir in unser Anwendungsmodul wechseln und die `NotFound`-Exception
899 importieren:
900
901 .. sourcecode:: python
902
903 from werkzeug.exceptions import NotFound
904
905 Schließlich müssen wir sie auffangen und in eine Response umwandeln. Dieser
906 except-Block kommt **vor** den except-Bock mit `HTTPException`:
907
908 .. sourcecode:: python
909
910 try:
911 # das bleibt das gleiche
912 except NotFound, e:
913 response = views.not_found(request)
914 response.status_code = 404
915 except HTTPException, e:
916 # das bleibt das gleiche
917
918 Jetzt noch ``templates/not_found.html`` hinzufügen und du bist fertig:
919
920 .. sourcecode:: html+jinja
921
922 {% extends 'layout.html' %}
923 {% block body %}
924 <h2>Seite nicht gefunden</h2>
925 <p>
926 Die aufgerufene Seite existiert nicht auf dem Server. Vielleicht
927 willst du eine <a href="{{ url_for('new') }}">neue URL
928 hinzufügen</a>?
929 </p>
930 {% endblock %}
931
932
933 Abschluss
934 =========
935
936 Dieses Tutorial behandelt alles, was du brauchst, um mit Werkzeug, SQLAlchemy
937 und Jinja anzufangen und sollte dir helfen, die beste Lösung für deine Anwendung
938 zu finden. Für einige größere Beispiele, die außerdem einen anderen Aufbau und
939 Ideen zum Ausführen benutzen, solltest du mal einen Blick auf den
940 `Beispielordner`_ werfen.
941
942 In diesem Sinne: Viel Spaß mit Werkzeug!
943
944 .. _Beispielordner: http://dev.pocoo.org/projects/werkzeug/browser/examples
0 =======
1 Unicode
2 =======
3
4 Since early Python 2 days unicode was part of all default Python builds. It
5 allows developers to write applications that deal with non-ASCII characters
6 in a straightforward way. But working with unicode requires a basic knowledge
7 about that matter, especially when working with libraries that do not support
8 it.
9
10 Werkzeug uses unicode internally everywhere text data is assumed, even if the
11 HTTP standard is not unicode aware as it. Basically all incoming data is
12 decoded from the charset specified (per default `utf-8`) so that you don't
13 operate on bytestrings any more. Outgoing unicode data is then encoded into
14 the target charset again.
15
16 Unicode in Python
17 =================
18
19 In Python 2 there are two basic string types: `str` and `unicode`. `str` may
20 carry encoded unicode data but it's always represented in bytes whereas the
21 `unicode` type does not contain bytes but charpoints. What does this mean?
22 Imagine you have the German Umlaut `ö`. In ASCII you cannot represent that
23 character, but in the `latin-1` and `utf-8` character sets you can represent
24 it, but they look differently when encoded:
25
26 >>> u'ö'.encode('latin1')
27 '\xf6'
28 >>> u'ö'.encode('utf-8')
29 '\xc3\xb6'
30
31 So an `ö` might look totally different depending on the encoding which makes
32 it hard to work with it. The solution is using the `unicode` type (as we did
33 above, note the `u` prefix before the string). The unicode type does not
34 store the bytes for `ö` but the information, that this is a
35 ``LATIN SMALL LETTER O WITH DIAERESIS``.
36
37 Doing ``len(u'ö')`` will always give us the expected "1" but ``len('ö')``
38 might give different results depending on the encoding of ``'ö'``.
39
40 Unicode in HTTP
41 ===============
42
43 The problem with unicode is that HTTP does not know what unicode is. HTTP
44 is limited to bytes but this is not a big problem as Werkzeug decodes and
45 encodes for us automatically all incoming and outgoing data. Basically what
46 this means is that data sent from the browser to the web application is per
47 default decoded from an utf-8 bytestring into a `unicode` string. Data sent
48 from the application back to the browser that is not yet a bytestring is then
49 encoded back to utf-8.
50
51 Usually this "just works" and we don't have to worry about it, but there are
52 situations where this behavior is problematic. For example the Python 2 IO
53 layer is not unicode aware. This means that whenever you work with data from
54 the file system you have to properly decode it. The correct way to load
55 a text file from the file system looks like this::
56
57 f = file('/path/to/the_file.txt', 'r')
58 try:
59 text = f.decode('utf-8') # assuming the file is utf-8 encoded
60 finally:
61 f.close()
62
63 There is also the codecs module which provides an open function that decodes
64 automatically from the given encoding.
65
66 Error Handling
67 ==============
68
69 With Werkzeug 0.3 onwards you can further control the way Werkzeug works with
70 unicode. In the past Werkzeug ignored encoding errors silently on incoming
71 data. This decision was made to avoid internal server errors if the user
72 tampered with the submitted data. However there are situations where you
73 want to abort with a `400 BAD REQUEST` instead of silently ignoring the error.
74
75 All the functions that do internal decoding now accept an `errors` keyword
76 argument that behaves like the `errors` parameter of the builtin string method
77 `decode`. The following values are possible:
78
79 `ignore`
80 This is the default behavior and tells the codec to ignore characters that
81 it doesn't understand silently.
82
83 `replace`
84 The codec will replace unknown characters with a replacement character
85 (`U+FFFD` ``REPLACEMENT CHARACTER``)
86
87 `strict`
88 Raise an exception if decoding fails.
89
90 Unlike the regular python decoding Werkzeug does not raise an
91 `UnicodeDecodeError` if the decoding failed but an `HTTPUnicodeError` which
92 is a direct subclass of `UnicodeError` and the `BadRequest` HTTP exception.
93 The reason is that if this exception is not caught by the application but
94 a catch-all for HTTP exceptions exists a default `400 BAD REQUEST` error
95 page is displayed.
96
97 There is additional error handling available which is a Werkzeug extension
98 to the regular codec error handling which is called `fallback`. Often you
99 want to use utf-8 but support latin1 as legacy encoding too if decoding
100 failed. For this case you can use the `fallback` error handling. For
101 example you can specify ``'fallback:iso-8859-15'`` to tell Werkzeug it should
102 try with `iso-8859-15` if `utf-8` failed. If this decoding fails too (which
103 should not happen for most legacy charsets such as `iso-8859-15`) the error
104 is silently ignored as if the error handling was `ignore`.
105
106 Further details are available as part of the API documentation of the concrete
107 implementations of the functions or classes working with unicode.
108
109
110 Request and Response Objects
111 ============================
112
113 As request and response objects usually are the central entities of Werkzeug
114 powered applications you can change the default encoding Werkzeug operates on
115 by subclassing these two classes. For example you can easily set the
116 application to utf-7 and strict error handling::
117
118 from werkzeug import BaseRequest, BaseResponse
119
120 class Request(BaseRequest):
121 charset = 'utf-7'
122 encoding_errors = 'strict'
123
124 class Response(BaseResponse):
125 charset = 'utf-7'
126
127 Keep in mind that the error handling is only customizable for all decoding
128 but not encoding. If Werkzeug encounters an encoding error it will raise a
129 `UnicodeEncodeError`. It's your responsibility to not create data that is
130 not present in the target charset (a non issue with all unicode encodings
131 such as utf-8).
0 =========
1 Utilities
2 =========
3
4 Werkzeug comes with a bunch of utilties that can be useful for WSGI
5 applications. Most of the classes provided by this module are used by the
6 wrappers, you can however use them without the wrappers, too.
7
8 All the utilities can be directly imported from the `werkzeug` module.
9
10
11 Data Structures
12 ===============
13
14 .. class:: werkzeug.utils.MultiDict
15
16 The following additional methods exist that are used to get or set the
17 other values for a key or convert:
18
19 .. def:: werkzeug.utils.MultiDict.get
20 .. def:: werkzeug.utils.MultiDict.getlist
21 .. def:: werkzeug.utils.MultiDict.setlist
22 .. def:: werkzeug.utils.MultiDict.setlistdefault
23
24 These functions work like the functions with the same name without
25 list. Unlike the regular dict functions those operate on all the
26 values as lists, not only on the first one:
27
28 `lists`, `listvalues`, `iterlists`, `iterlistvalues`, `poplist`,
29 and `popitemlist`.
30
31 Also notable: `update` adds values and does not replace existing ones.
32
33
34 .. class:: werkzeug.utils.CombinedMultiDict
35
36 .. class:: werkzeug.utils.Headers
37
38 .. def:: werkzeug.utils.Headers.__init__
39 .. def:: werkzeug.utils.Headers.linked
40 .. def:: werkzeug.utils.Headers.get
41 .. def:: werkzeug.utils.Headers.getlist
42 .. def:: werkzeug.utils.Headers.add
43 .. def:: werkzeug.utils.Headers.set
44 .. def:: werkzeug.utils.Headers.extend
45 .. def:: werkzeug.utils.Headers.clear
46 .. def:: werkzeug.utils.Headers.to_list
47 .. def:: werkzeug.utils.Headers.__contains__
48 .. def:: werkzeug.utils.Headers.__iter__
49
50 All the other dict functions such as `iterkeys` are available and
51 work the same.
52
53
54 .. class:: werkzeug.utils.EnvironHeaders
55
56
57 Working with HTTP Headers
58 =========================
59
60 .. def:: werkzeug.http.parse_set_header
61 .. def:: werkzeug.http.parse_list_header
62 .. def:: werkzeug.http.parse_dict_header
63 .. def:: werkzeug.http.dump_header
64
65 .. class:: werkzeug.http.Accept
66
67 `provided`
68 `True` if the `Accept` object was created from a list, `False` if the
69 initial value was `None`. This is used by the request wrappers to
70 keep the information if the header was present or not.
71
72 `best`
73 The best value (does not return a tuple!).
74
75 .. def:: werkzeug.http.Accept.__getitem__
76 .. def:: werkzeug.http.Accept.find
77 .. def:: werkzeug.http.Accept.values
78 .. def:: werkzeug.http.Accept.itervalues
79
80 .. def:: werkzeug.http.parse_accept_header
81
82 .. class:: werkzeug.http.CacheControl
83 .. def:: werkzeug.http.parse_cache_control_header
84
85 .. class:: werkzeug.http.HeaderSet
86
87 .. def:: werkzeug.http.HeaderSet.add
88 .. def:: werkzeug.http.HeaderSet.remove
89 .. def:: werkzeug.http.HeaderSet.update
90 .. def:: werkzeug.http.HeaderSet.discard
91 .. def:: werkzeug.http.HeaderSet.find
92 .. def:: werkzeug.http.HeaderSet.index
93 .. def:: werkzeug.http.HeaderSet.clear
94 .. def:: werkzeug.http.HeaderSet.as_set
95 .. def:: werkzeug.http.HeaderSet.to_header
96
97 .. class:: werkzeug.useragents.UserAgent
98
99 .. def:: werkzeug.http.parse_date
100 .. def:: werkzeug.utils.cookie_date
101 .. def:: werkzeug.utils.http_date
102 .. def:: werkzeug.utils.parse_form_data
103
104 .. class:: werkzeug.http.ETags
105
106 .. def:: werkzeug.http.ETags.is_weak
107 .. def:: werkzeug.http.ETags.contains_weak
108 .. def:: werkzeug.http.ETags.contains
109 .. def:: werkzeug.http.ETags.contains_raw
110 .. def:: werkzeug.http.ETags.as_set
111 .. def:: werkzeug.http.ETags.to_header
112
113 .. def:: werkzeug.http.parse_etags
114 .. def:: werkzeug.http.quote_etag
115 .. def:: werkzeug.http.unquote_etag
116 .. def:: werkzeug.http.generate_etag
117 .. def:: werkzeug.http.is_resource_modified
118
119 .. class:: werkzeug.http.Authorization
120
121 .. def:: werkzeug.http.WWWAuthenticate.to_header
122 .. property:: werkzeug.http.Authorization.username
123 .. property:: werkzeug.http.Authorization.password
124 .. property:: werkzeug.http.Authorization.realm
125 .. property:: werkzeug.http.Authorization.nonce
126 .. property:: werkzeug.http.Authorization.uri
127 .. property:: werkzeug.http.Authorization.nc
128 .. property:: werkzeug.http.Authorization.cnonce
129 .. property:: werkzeug.http.Authorization.response
130 .. property:: werkzeug.http.Authorization.opaque
131 .. property:: werkzeug.http.Authorization.qop
132
133 .. class:: werkzeug.http.WWWAuthenticate
134
135 .. def:: werkzeug.http.WWWAuthenticate.set_basic
136 .. def:: werkzeug.http.WWWAuthenticate.set_digest
137 .. def:: werkzeug.http.WWWAuthenticate.to_header
138 .. property:: werkzeug.http.WWWAuthenticate.type
139 .. property:: werkzeug.http.WWWAuthenticate.realm
140 .. property:: werkzeug.http.WWWAuthenticate.stale
141 .. property:: werkzeug.http.WWWAuthenticate.domain
142 .. property:: werkzeug.http.WWWAuthenticate.nonce
143 .. property:: werkzeug.http.WWWAuthenticate.opaque
144 .. property:: werkzeug.http.WWWAuthenticate.algorithm
145 .. property:: werkzeug.http.WWWAuthenticate.qop
146
147 .. def:: werkzeug.http.parse_authorization_header
148 .. def:: werkzeug.http.parse_www_authenticate_header
149
150 `HTTP_STATUS_CODES`
151 A dict of status code -> default status message pairs. This is used
152 by the wrappers and other places where a integer status code is expanded
153 to a string throughout Werkzeug.
154
155
156 URL Helpers
157 ===========
158
159 .. def:: werkzeug.utils.url_decode
160 .. def:: werkzeug.utils.url_encode
161 .. def:: werkzeug.utils.url_quote
162 .. def:: werkzeug.utils.url_quote_plus
163 .. def:: werkzeug.utils.url_unquote
164 .. def:: werkzeug.utils.url_unquote_plus
165 .. def:: werkzeug.utils.url_fix
166 .. class:: werkzeug.utils.Href
167
168
169 HTML Helpers
170 ============
171
172 .. def:: werkzeug.utils.escape
173 .. def:: werkzeug.utils.unescape
174 .. class:: werkzeug.utils.HTMLBuilder
175
176
177 WSGI Helpers
178 ============
179
180 .. class:: werkzeug.utils.ClosingIterator
181 .. class:: werkzeug.utils.SharedDataMiddleware
182 .. class:: werkzeug.utils.DispatcherMiddleware
183 .. class:: werkzeug.utils.FileStorage
184
185 `name`
186 The name of the form field which represents the data.
187
188 `filename`
189 The incoming filename.
190
191 `content_type`
192 The mimetype of the file. E.g. ``'text/html'``.
193
194 `content_length / len()`
195 The expected length of the file.
196
197 `stream`
198 Gives you access to the underlaying stream. Note that the exact
199 methods of this stream and its type is not strictly specified. In
200 most situations it will be a `TemporaryFile` object.
201
202 .. def:: werkzeug.utils.FileStorage.__init__
203 .. def:: werkzeug.utils.FileStorage.save
204
205 .. def:: werkzeug.utils.get_host
206 .. def:: werkzeug.utils.get_current_url
207 .. def:: werkzeug.utils.responder
208 .. def:: werkzeug.utils.create_environ
209 .. def:: werkzeug.utils.run_wsgi_app
210
211
212 Helper Functions
213 ================
214
215 .. def:: werkzeug.utils.cached_property
216 .. def:: werkzeug.utils.environ_property
217 .. def:: werkzeug.utils.header_property
218 .. def:: werkzeug.utils.parse_cookie
219 .. def:: werkzeug.utils.dump_cookie
220 .. def:: werkzeug.utils.redirect
221 .. def:: werkzeug.utils.append_slash_redirect
222 .. def:: werkzeug.utils.import_string
223 .. def:: werkzeug.utils.find_modules
224 .. def:: werkzeug.utils.validate_arguments
225 .. def:: werkzeug.utils.bind_arguments
226 .. def:: werkzeug.testapp.test_app
0 ========
1 Wrappers
2 ========
3
4 .. module:: werkzeug.wrappers
5
6 You can import all these objects directly from `werkzeug`.
7
8 Base Wrappers
9 =============
10
11 These objects implement a common set of operations. They are missing fancy
12 addon functionality like user agent parsing or etag handling. These features
13 are available by mixing in various mixin classes or using `Request` and
14 `Response`.
15
16 .. class:: werkzeug.wrappers.BaseRequest
17
18 **Creating Request Objects**
19
20 .. def:: werkzeug.wrappers.BaseRequest.__init__
21 .. def:: werkzeug.wrappers.BaseRequest.from_values
22 .. def:: werkzeug.wrappers.BaseRequest.application
23
24 **Properties**
25
26 `path`
27 The current path requested, relative to the position where the WSGI
28 application is mounted (`PATH_INFO`). It will contain a leading slash and
29 will be at least a string with a single slash when accessing the URL root.
30
31 `script_root`
32 The root path for the script (`SCRIPT_NAME`). Does not contain a trailing
33 slash.
34
35 `url`
36 The full URL for the current request.
37
38 `base_url`
39 The current full URL without the query string.
40
41 `url_root`
42 The current URL up to the script root.
43
44 `host_url`
45 The current URL for the host.
46
47 `host`
48 The current hostname, without scheme.
49
50 `is_secure`
51 True if this is an HTTPS request.
52
53 `is_multithread`
54 True if this request was created in a multithreaded environment.
55
56 `is_multiprocess`
57 True if this request was created in a forking environment.
58
59 `is_run_once`
60 True if this request was created on the command line, a CGI script or a
61 similar environment.
62
63 `is_xhr`
64 True if the request was triggered via an JavaScript XMLHttpRequest.
65 This only works with libraries that support the X-Requested-With
66 header and set it to "XMLHttpRequest". Libraries that do that are
67 prototype, jQuery and Mochikit and probably some more.
68
69 `method`
70 The request method. `GET`, `POST` etc.
71
72 `args`
73 A dictionary-like object containing all given HTTP GET parameters. See
74 the `MultiDict` documentation in the `utils`_ section.
75
76 `form`
77 A dictionary-like object containing all given HTTP POST parameters. See
78 the `MultiDict` documentation in the `utils`_ section.
79
80 This dict does not contain uploaded files, see `files` regarding that.
81
82 `values`
83 An immutable dictionary-like object containing both the `args` and `form`
84 values. See the `CombinedMultiDict` documentation in the `utils`_
85 section.
86
87 `cookies`
88 A dictionary with the submitted cookie values.
89
90 `files`
91 A dictionary-like object containing all uploaded files. Each key in
92 `files` is the name from the ``<input type="file" name="" />``. Each
93 value in `files` is a Werkzeug `FileStorage` object with the following
94 members:
95
96 - `filename` - The name of the uploaded file, as a Python string.
97 - `type` - The content type of the uploaded file.
98 - `data` - The raw content of the uploaded file.
99 - `read()` - Read from the stream.
100
101 Note that `files` will only contain data if the request method was POST
102 and the ``<form>`` that posted to the request had
103 ``enctype="multipart/form-data"``. It will be empty otherwise.
104
105 See the `MultiDict` / `FileStorage` documentation in the `utils`_
106 section for more details about the used data structure.
107
108 `environ`
109 The WSGI environment used to create the request object.
110
111 `stream`
112 The buffered stream with incoming data from the webbrowser if the
113 submitted data was not multipart or URL-encoded form data.
114
115 `input_stream`
116 The input stream provided by the client. Used internally by the form
117 data parser. Reading from this stream must never be mixed with
118 accessing `stream`, `data`, `files`, or `form`, because then it's not
119 guaranteed that more data is requested from the client than expected.
120 Never read beyond ``environ['CONTENT_LENGTH']``.
121
122 `data`
123 Accessing this the first time reads the whole `stream` and stores it. Keep
124 in mind that this does not read the whole WSGI input stream like Django
125 does.
126
127 `remote_addr`
128 The remote address for the user that created this request. If the class
129 variable `is_behind_proxy` is set to `True` (either by subclassing the
130 process or overriding this variable on the instance) it will try to get
131 the value from the `X_HTTP_FORWARDED_FOR` header. Keep in mind that this
132 is disabled by default because unless you are really behind a proxy this
133 is a security problem.
134
135 `access_route`
136 If you are behind a proxy server this will list all the IP addresses that
137 take place in the request. The end user IP address is the first one in
138 the list, the last proxy server is the last item in the list. This also
139 works if the `is_behind_proxy` class variable is set to `False`.
140
141
142 .. class:: werkzeug.wrappers.BaseResponse
143
144 **Creating Response Objects**
145
146 .. def:: werkzeug.wrappers.BaseResponse.__init__
147 .. def:: werkzeug.wrappers.BaseResponse.force_type
148 .. def:: werkzeug.wrappers.BaseResponse.from_app
149
150 **Properties**
151
152 `response`
153 The application iterator. If constructed from a string this will
154 be a list, otherwise the object provided as application iterator.
155
156 `headers`
157 A `Headers` object representing the response headers.
158
159 `status`
160 The response status as string.
161
162 `status_code`
163 The response status as integer.
164
165 `data`
166 When accessed the response iterator is buffered and, encoded and
167 returned as bytestring.
168
169 `header_list`
170 Read only list that contains the current list for the headers in
171 the response encoding.
172
173 `is_streamed`
174 If the response is streamed (the response is not a sequence) this
175 property is `True`. In this case streamed means that there is no
176 information about the number of iterations. This is usully `True`
177 if a generator is passed to the response object.
178
179 This is useful for checking before applying some sort of post
180 filtering that should not take place for streamed responses.
181
182 **Methods**
183
184 .. def:: werkzeug.wrappers.BaseResponse.iter_encoded
185 .. def:: werkzeug.wrappers.BaseResponse.set_cookie
186 .. def:: werkzeug.wrappers.BaseResponse.delete_cookie
187 .. def:: werkzeug.wrappers.BaseResponse.fix_headers
188 .. def:: werkzeug.wrappers.BaseResponse.close
189 .. def:: werkzeug.wrappers.BaseResponse.freeze
190 .. def:: werkzeug.wrappers.BaseResponse.__call__
191
192
193 Mixin Classes
194 =============
195
196 Werkzeug also provides helper mixins for various HTTP related functionality
197 such as etags, cache control, user agents etc. When subclassing you can
198 mix those classes in to extend the functionality of the `BaseRequest` or
199 `BaseResponse` object. Here a small example for a request object that
200 parses accept headers::
201
202 from werkzeug import BaseRequest, AcceptMixin
203
204 class Request(BaseRequest, AcceptMixin):
205 pass
206
207 The `Request` and `Response` classes subclass the `BaseRequest` and
208 `BaseResponse` classes and implement all the mixins Werkzeug provides:
209
210 .. class:: werkzeug.wrappers.Request
211 .. class:: werkzeug.wrappers.Response
212
213 .. class:: werkzeug.wrappers.AcceptMixin
214
215 .. property:: werkzeug.wrappers.AcceptMixin.accept_mimetypes
216 .. property:: werkzeug.wrappers.AcceptMixin.accept_charsets
217 .. property:: werkzeug.wrappers.AcceptMixin.accept_encodings
218 .. property:: werkzeug.wrappers.AcceptMixin.accept_languages
219
220 All this properties store `Accept` objects as documented in the
221 `utils`_ section.
222
223 .. class:: werkzeug.wrappers.AuthorizationMixin
224
225 .. property:: werkzeug.wrappers.AuthorizationMixin.authorization
226
227
228 .. class:: werkzeug.wrappers.ETagRequestMixin
229
230 .. property:: werkzeug.wrappers.ETagRequestMixin.cache_control
231 .. property:: werkzeug.wrappers.ETagRequestMixin.if_match
232 .. property:: werkzeug.wrappers.ETagRequestMixin.if_none_match
233 .. property:: werkzeug.wrappers.ETagRequestMixin.if_modified_since
234 .. property:: werkzeug.wrappers.ETagRequestMixin.if_unmodified_since
235
236 All the used data structures are documented in the `utils`_ section.
237
238
239 .. class:: werkzeug.wrappers.ETagResponseMixin
240
241 .. property:: werkzeug.wrappers.ETagResponseMixin.cache_control
242 .. def:: werkzeug.wrappers.ETagResponseMixin.make_conditional
243 .. def:: werkzeug.wrappers.ETagResponseMixin.add_etag
244 .. def:: werkzeug.wrappers.ETagResponseMixin.set_etag
245 .. def:: werkzeug.wrappers.ETagResponseMixin.get_etag
246 .. def:: werkzeug.wrappers.ETagResponseMixin.freeze
247
248
249 .. class:: werkzeug.wrappers.ResponseStreamMixin
250
251 .. property:: werkzeug.wrappers.ResponseStreamMixin.stream
252
253
254 .. class:: werkzeug.wrappers.CommonResponseDescriptorsMixin
255
256 .. property:: werkzeug.wrappers.CommonResponseDescriptorsMixin.mimetype
257 .. property:: werkzeug.wrappers.CommonResponseDescriptorsMixin.location
258 .. property:: werkzeug.wrappers.CommonResponseDescriptorsMixin.age
259 .. property:: werkzeug.wrappers.CommonResponseDescriptorsMixin.content_type
260 .. property:: werkzeug.wrappers.CommonResponseDescriptorsMixin.content_length
261 .. property:: werkzeug.wrappers.CommonResponseDescriptorsMixin.content_location
262 .. property:: werkzeug.wrappers.CommonResponseDescriptorsMixin.content_encoding
263 .. property:: werkzeug.wrappers.CommonResponseDescriptorsMixin.content_language
264 .. property:: werkzeug.wrappers.CommonResponseDescriptorsMixin.content_md5
265 .. property:: werkzeug.wrappers.CommonResponseDescriptorsMixin.date
266 .. property:: werkzeug.wrappers.CommonResponseDescriptorsMixin.expires
267 .. property:: werkzeug.wrappers.CommonResponseDescriptorsMixin.last_modified
268 .. property:: werkzeug.wrappers.CommonResponseDescriptorsMixin.retry_after
269 .. property:: werkzeug.wrappers.CommonResponseDescriptorsMixin.vary
270 .. property:: werkzeug.wrappers.CommonResponseDescriptorsMixin.allow
271
272 .. class:: werkzeug.wrappers.WWWAuthenticateMixin
273
274 .. property:: werkzeug.wrappers.WWWAuthenticateMixin.www_authenticate
275
276
277 Note on File Uploads
278 ====================
279
280 File uploads are a tricky thing in general. Per default all the file uploads
281 go into temporary files on the filesystem and not the memory of the current
282 process to avoid high memory usage. You could also change that to store the
283 data somewhere else by implementing an object that implements the python IO
284 protocol (see `StringIO`) for both writing and reading.
285
286 Then you have to subclass a request object and override `_get_file_stream`:
287
288 .. def:: werkzeug.wrappers.BaseRequest._get_file_stream
289
290
291 .. _utils: utils.txt
0 ==============
1 How WSGI Works
2 ==============
3
4 New to Werkzeug or even `WSGI`_? This part of the documentation covers the
5 low-level parts of Werkzeug that are in fact just plain WSGI.
6
7 .. _WSGI: http://www.python.org/dev/peps/pep-0333/
8
9
10 What is WSGI?
11 =============
12
13 Basically WSGI is an interface between web servers and web applications.
14 We'll explain the mechanics of WSGI below, but to sum it up WSGI lets you
15 develop rich web application without the need to know about your server
16 environment. A WSGI application runs on standalone servers, on any
17 webserver out there, via `mod_python`, `FastCGI`, `SCGI`, `CGI`, basically
18 everything that runs Python.
19
20 But there's much more; WSGI is more than just HTTP! You and other developers
21 can extend WSGI by adding new features to it, filtering input or output,
22 adding rich debugging systems, automatically send e-mails to your mailer as
23 soon as an unhandled application error occurs. You can add session features to
24 it etc.
25
26 There are few things you cannot do with WSGI, and Werkzeug itself is just a
27 tiny wrapper around WSGI, thus you don't lose any functionally unlike some
28 other implementations that abstract more. If you don't require all the power
29 of WSGI you can choose a higher-level implementation like Django.
30
31
32 Writing a WSGI Application
33 ==========================
34
35 The first part is about how to use WSGI without Werkzeug. You can read
36 the PEP (:pep:333) that defines it, but we'll do a very brief summary:
37
38 - A WSGI application is just a callable object that is passed an `environ` -
39 a dict that contains request data, and a `start_response` function that is
40 called to start sending the response.
41
42 In order to send data to the server, all you have to do is to call
43 `start_response` and return an iterable.
44
45 - `environ` is a plain old CGI environment (contains values like `PATH_INFO`)
46 in form of a Python dict with some extensions like `wsgi.input` that
47 represent the input stream.
48
49 - Middlewares (we'll cover them later) can add additional data to the
50 `environ`.
51
52 So, here's a simple application:
53
54 .. sourcecode:: python
55
56 def application(environ, start_response):
57 start_response('200 OK', [('Content-Type', 'text/html')])
58 return ['Hello World!']
59
60 Now you have an application, but how can you start it? From Python 2.5, the
61 stdlib contains a WSGI server called `wsgiref`; for Python 2.4 and lower you
62 have to install that on your own. There are also other standalone
63 implementations which you can find at `wsgi.org`_.
64
65 To start the application as standalone server, all you have to do is adding
66 this start hook to the file:
67
68 .. sourcecode:: python
69
70 if __name__ == '__main__':
71 from wsgiref.simple_server import make_server
72 srv = make_server('localhost', 5000, application)
73 srv.serve_forever()
74
75 When you now start the application you will see ``Hello World!`` on
76 ``http://localhost:5000/``.
77
78 The `__name__` hook is a good idea so that you can use the module for other
79 modules like flup (provides a bridge to Apache and other webservers via
80 FastCGI and similar protocols) without altering the code.
81
82 .. _wsgi.org: http://www.wsgi.org/
83
84
85 Adding Interactive Elements
86 ===========================
87
88 Let's extend the example from above so that we say ``Hello John`` if the user
89 visits ``http://localhost:5000/?name=John``:
90
91 .. sourcecode:: python
92
93 from cgi import parse_qs, escape
94
95 def application(environ, start_response):
96 start_response('200 OK', [('Content-Type', 'text/html')])
97 query = parse_qs(environ.get('QUERY_STRING', ''))
98 return ['Hello %s!' % escape(query.get('name', 'World'))]
99
100 Basically we just combined the python CGI module with our WSGI application.
101
102
103 That Sucks!
104 ===========
105
106 Indeed it does. It's neither fun to work with nor does it look clean and
107 simple. And that's where utilities like Werkzeug come into play. Werkzeug,
108 for example, lets you easily create unicode-aware applications (in fact it
109 forces you to use unicode internally) and avoid repetitive work.
110
111 In this example we want to connect `/` with an index function and `/about`
112 with an about function, both returning different content. To keep the example
113 simple we just want to use a dict that maps to that and take advantage or some
114 of the Werkzeug features:
115
116 .. sourcecode:: python
117
118 from werkzeug import Request, Response, escape
119
120 def index(req):
121 return Response(u'''<h1>Index Page</h1>
122 <p>What\'s your name?</p>
123 <form action="hello" method="post">
124 <input type="text" name="name" value="My Name">
125 <input type="submit" value="Greet Me!">
126 </form>
127 ''', mimetype='text/html')
128
129 def hello(req):
130 name = req.form.get('name') or 'Nobody'
131 return Response(u'<h1>Hello %s!</h1>' % escape(name),
132 mimetype='text/html')
133
134 def about(req):
135 return Response(u'''<h1>About This Page</h1>
136 <p>This page is just a small example page for Werkzeug</p>
137 ''', mimetype='text/html')
138
139 def not_found(req):
140 return Response(u'<h1>Page Not Found</h1>', status=404,
141 mimetype='text/html')
142
143 views = {
144 '/': index,
145 '/hello': hello,
146 '/about': about
147 }
148
149 def application(environ, start_response):
150 req = Request(environ)
151 if req.path not in views:
152 resp = not_found(req)
153 else:
154 resp = views[req.path](req)
155 return resp(environ, start_response)
156
157 if __name__ == '__main__':
158 from werkzeug import run_simple
159 run_simple('localhost', 5000, application)
160
161 Alright. That's quite a lot of code but let's go through it step-by-step.
162
163 The first thing we do is importing the stuff we use. In that example we need
164 the `escape` function that XML/SGML escapes strings. This is required to
165 avoid XSS attacks to the application. Then we need the `Request` and
166 `Response` objects which ecapsulate the WSGI environ and responses.
167
168 Then we define a bunch of callback functions that just return HTML responses.
169 One of them - `not_found` - is called when we don't have a matching URL,
170 the others are bound to urls in the `views` dict.
171
172 The WSGI application itself just creates a request object and looks up the
173 callbacks or proceeds with the `not_found` function if there is no matching
174 URL. The return value of the response is then called as WSGI application.
175
176 This is possible because `Response` objects behave like WSGI applications.
177
178 Of course, this example is still bad code; in real applications you would use
179 one of the template engines that are available for Python. But for the simple
180 example that should be enough.
181
182
183 Debugging It
184 ============
185
186 You probably have had a typo in one of the examples above. In that case you
187 don't have to use the error output from the `wsgiref` server. There are some
188 excellent debugging systems for WSGI, and one of them is shipped with Werkzeug
189 and explained in the `debug system`_ docs.
190
191 .. _debug system: debug.txt
0 =================
1 Werkzeug Examples
2 =================
3
4 This directory contains various example applications and example code of
5 Werkzeug powered applications. Nearly all of them are written for Python 2.4
6 and higher so don't expect them to work on Python 2.3.
7
8 Beside the proof of concept applications and code snippets in the partial
9 folder they all have external depencencies for template engines or database
10 adapters (SQLAlchemy only so far).
11
12
13 Full Example Applications
14 =========================
15
16 The following example applications are application types you would actually
17 find in real life :-)
18
19
20 `simplewiki`
21 A simple Wiki implementation.
22
23 Requirements:
24 - SQLAlchemy
25 - Creoleparser >= 0.3.3
26 - genshi
27
28 You can obtain all packages in the Cheeseshop via easy_install. You have
29 to have at least version 0.3.3 of Creoleparser.
30
31 Usage::
32
33 ./manage-simplewiki.py initdb
34 ./manage-simplewiki.py runserver
35
36 Or of course you can just use the application object
37 (`simplewiki.SimpleWiki`) and hook that into your favourite WSGI gateway.
38 The constructor of the application object takes a single argument which is
39 the SQLAlchemy URI for the database.
40
41 The management script for the devserver looks up the an environment var
42 called `SIMPLEWIKI_DATABASE_URI` and uses that for the database URI. If
43 no such variable is provided "sqlite:////tmp/simplewiki.db" is assumed.
44
45 `plnt`
46 A planet called plnt, pronounce plant.
47
48 Requirements:
49 - SQLAlchemy
50 - Jinja
51 - feedparser
52
53 You can obtain all packages in the Cheeseshop via easy_install.
54
55 Usage::
56
57 ./manage-plnt.py initdb
58 ./manage-plnt.py sync
59 ./manage-plnt.py runserver
60
61 The WSGI application is called `plnt.Plnt` which, like the simple wiki,
62 accepts a database URI as first argument. The environment variable for
63 the database key is called `PLNT_DATABASE_URI` and the default is
64 "sqlite:////tmp/plnt.db".
65
66 Per default a few python related blogs are added to the database, you
67 can add more in a python shell by playing with the `Blog` model.
68
69 `shorty`
70 A tinyurl clone for the Werkzeug 0.2 tutorial.
71
72 Requirements:
73 - SQLAlchemy
74 - Jinja
75
76 You can obtain all packages in the Cheeseshop via easy_install.
77
78 Usage::
79
80 ./manage-shorty.py initdb
81 ./manage-shorty.py runserver
82
83 The WSGI application is called `shorty.application.Shorty` which, like the
84 simple wiki, accepts a database URI as first argument.
85
86 The source code of the application is explained in detail in the Werkzeug
87 tutorial.
88
89 `couchy`
90 Like shorty, but implemented using CouchDB.
91
92 Requirements :
93 - python 2.4 & sup
94 - werkzeug : http://werkzeug.pocoo.org
95 - jinja : http://jinja.pocoo.org
96 - couchdb 0.72 & above : http://www.couchdb.org
97
98 `cupoftee`
99 A `Teeworlds <http://www.teeworlds.com/>`_ server browser. This application
100 works best in a non forking environment and won't work for CGI.
101
102 Usage::
103
104 ./manage-cupoftee.py runserver
0 This folder includes example applications for werkzeug.contrib
0 # -*- coding: utf-8 -*-
1 """
2 Secure Cookie Example
3 ~~~~~~~~~~~~~~~~~~~~~
4
5 Stores session on the client.
6
7 :copyright: Copyright 2007 by Armin Ronacher.
8 :license: BSD.
9 """
10 from time import asctime
11 from werkzeug import run_simple, BaseRequest, BaseResponse
12 from werkzeug.contrib.securecookie import SecureCookie
13
14 SECRET_KEY = 'V\x8a$m\xda\xe9\xc3\x0f|f\x88\xbccj>\x8bI^3+'
15
16
17 class Request(BaseRequest):
18
19 def __init__(self, environ):
20 BaseRequest.__init__(self, environ)
21 self.session = SecureCookie.load_cookie(self, secret_key=SECRET_KEY)
22
23
24 def index(request):
25 return '<a href="set">Set the Time</a> or <a href="get">Get the time</a>'
26
27
28 def get_time(request):
29 return 'Time: %s' % request.session.get('time', 'not set')
30
31
32 def set_time(request):
33 request.session['time'] = time = asctime()
34 return 'Time set to %s' % time
35
36
37 def application(environ, start_response):
38 request = Request(environ)
39 response = BaseResponse({
40 'get': get_time,
41 'set': set_time
42 }.get(request.path.strip('/'), index)(request), mimetype='text/html')
43 request.session.save_cookie(response)
44 return response(environ, start_response)
45
46
47 if __name__ == '__main__':
48 run_simple('localhost', 5000, application)
0 #!/usr/bin/env python
1 # -*- coding: utf-8 -*-
2 from werkzeug.serving import run_simple
3 from werkzeug.contrib.sessions import SessionStore, SessionMiddleware
4
5
6 class MemorySessionStore(SessionStore):
7
8 def __init__(self, session_class=None):
9 SessionStore.__init__(self, session_class=None)
10 self.sessions = {}
11
12 def save(self, session):
13 self.sessions[session.sid] = session
14
15 def delete(self, session):
16 self.sessions.pop(session.id, None)
17
18 def get(self, sid):
19 if not self.is_valid_key(sid) or sid not in self.sessions:
20 return self.new()
21 return self.session_class(self.sessions[sid], sid, False)
22
23
24 def application(environ, start_response):
25 start_response('200 OK', [('Content-Type', 'text/html')])
26 session = environ['werkzeug.session']
27 yield '<title>Session Example</title><h1>Session Example</h1>'
28 if session.new:
29 session['visit_count'] = 0
30 yield '<p>This is a new session.</p>'
31 session['visit_count'] += 1
32 yield '<p>You visited this page %d times.</p>' % session['visit_count']
33
34
35 def make_app():
36 return SessionMiddleware(application, MemorySessionStore())
37
38
39 if __name__ == '__main__':
40 run_simple('localhost', 5000, make_app())
0 # -*- coding: utf-8 -*-
1 """
2 coolmagic
3 ~~~~~~~~~
4
5 Package description goes here.
6
7 :copyright: 2007 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 from coolmagic.application import make_app
0 # -*- coding: utf-8 -*-
1 """
2 coolmagic.application
3 ~~~~~~~~~~~~~~~~~~~~~
4
5 This module provides the WSGI application.
6
7 The WSGI middlewares are applied in the `make_app` factory function
8 that automatically wraps the application within the require
9 middlewares. Per default only the `SharedDataMiddleware` is applied.
10
11 :copyright: 2007 by Armin Ronacher.
12 :license: BSD, see LICENSE for more details.
13 """
14 from os import path, listdir
15 from coolmagic.utils import Request, local_manager, redirect
16 from werkzeug.routing import Map, Rule, RequestRedirect
17 from werkzeug.exceptions import HTTPException, NotFound
18
19
20 class CoolMagicApplication(object):
21 """
22 The application class. It's passed a directory with configuration values.
23 """
24
25 def __init__(self, config):
26 self.config = config
27
28 for fn in listdir(path.join(path.dirname(__file__), 'views')):
29 if fn.endswith('.py') and fn != '__init__.py':
30 __import__('coolmagic.views.' + fn[:-3])
31
32 from coolmagic.utils import exported_views
33 rules = [
34 # url for shared data. this will always be unmatched
35 # because either the middleware or the webserver
36 # handles that request first.
37 Rule('/public/<path:file>',
38 endpoint='shared_data')
39 ]
40 self.views = {}
41 for endpoint, (func, rule, extra) in exported_views.iteritems():
42 if rule is not None:
43 rules.append(Rule(rule, endpoint=endpoint, **extra))
44 self.views[endpoint] = func
45 self.url_map = Map(rules)
46
47 def __call__(self, environ, start_response):
48 urls = self.url_map.bind_to_environ(environ)
49 req = Request(environ, urls)
50 try:
51 endpoint, args = urls.match(req.path)
52 resp = self.views[endpoint](**args)
53 except NotFound, e:
54 resp = self.views['static.not_found']()
55 except (HTTPException, RequestRedirect), e:
56 resp = e
57 return resp(environ, start_response)
58
59
60 def make_app(config=None):
61 """
62 Factory function that creates a new `CoolmagicApplication`
63 object. Optional WSGI middlewares should be applied here.
64 """
65 config = config or {}
66 app = CoolMagicApplication(config)
67
68 # static stuff
69 from werkzeug.utils import SharedDataMiddleware
70 app = SharedDataMiddleware(app, {
71 '/public': path.join(path.dirname(__file__), 'public')
72 })
73
74 # clean up locals
75 app = local_manager.make_middleware(app)
76
77 return app
0 # -*- coding: utf-8 -*-
1 """
2 coolmagic.helpers
3 ~~~~~~~~~~~~~~~~~
4
5 The star-import module for all views.
6
7 :copyright: 2007 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 from coolmagic.utils import Response, TemplateResponse, ThreadedRequest, \
11 export, url_for, redirect
12 from werkzeug import escape
13
14
15 #: a thread local proxy request object
16 request = ThreadedRequest()
17 del ThreadedRequest
0 body {
1 margin: 0;
2 padding: 20px;
3 font-family: sans-serif;
4 font-size: 15px;
5 }
6
7 h1, a {
8 color: #a00;
9 }
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
1 "http://www.w3.org/TR/html4/loose.dtd">
2 <html>
3 <head>
4 <title>{{ page_title }} &mdash; Cool Magic!</title>
5 <link rel="stylesheet" href="{{ h.url_for('shared_data', file='style.css') }}" type="text/css">
6 </head>
7 <body>
8 <h1>Cool Magic</h1>
9 <h2>{{ page_title }}</h2>
10 {% block page_body %}{% endblock %}
11 </body>
12 </html>
0 {% extends "layout.html" %}
1 {% set page_title = 'About the Magic' %}
2 {% block page_body %}
3 <p>
4 Nothing to see. It's just magic.
5 </p>
6 <p>
7 <a href="{{ h.url_for('static.index') }}">back to the index</a>
8 </p>
9 {% endblock %}
0 {% extends "layout.html" %}
1 {% set page_title = 'Welcome to the Magic' %}
2 {% block page_body %}
3 <p>
4 Welcome to the magic! This is a bigger example for the
5 Werkzeug toolkit. And it contains a lot of magic.
6 </p>
7 <p>
8 <a href="{{ h.url_for('static.about') }}">about the implementation</a> or
9 click here if you want to see a <a href="{{ h.url_for('static.broken')
10 }}">broken view</a>.
11 </p>
12 {% endblock %}
0 {% extends "layout.html" %}
1 {% set page_title = 'Missing Magic' %}
2 {% block page_body %}
3 <p>
4 The requested magic really does not exist. Maybe you want
5 to look for it on the <a href="{{ h.url_for('static.index') }}">index</a>.
6 </p>
7 {% endblock %}
0 # -*- coding: utf-8 -*-
1 """
2 coolmagic.utils
3 ~~~~~~~~~~~~~~~
4
5 This module contains the subclasses of the base request and response
6 objects provided by werkzeug. The subclasses know about their charset
7 and implement some additional functionallity like the ability to link
8 to view functions.
9
10 :copyright: 2007 by Armin Ronacher.
11 :license: BSD, see LICENSE for more details.
12 """
13 from os.path import dirname, join
14 from jinja import Environment, FileSystemLoader
15 from werkzeug import BaseRequest, BaseResponse, Local, LocalManager, redirect
16
17
18 local = Local()
19 local_manager = LocalManager([local])
20 template_env = Environment(
21 loader=FileSystemLoader(join(dirname(__file__), 'templates'),
22 use_memcache=False)
23 )
24 exported_views = {}
25
26
27 def export(string, template=None, **extra):
28 """
29 Decorator for registering view functions and adding
30 templates to it.
31 """
32 def wrapped(f):
33 endpoint = (f.__module__ + '.' + f.__name__)[16:]
34 if template is not None:
35 old_f = f
36 def f(**kwargs):
37 rv = old_f(**kwargs)
38 if not isinstance(rv, Response):
39 rv = TemplateResponse(template, **(rv or {}))
40 return rv
41 f.__name__ = old_f.__name__
42 f.__doc__ = old_f.__doc__
43 exported_views[endpoint] = (f, string, extra)
44 return f
45 return wrapped
46
47
48 def url_for(endpoint, **values):
49 """
50 Build a URL
51 """
52 return local.request.url_adapter.build(endpoint, values)
53
54
55 class Request(BaseRequest):
56 """
57 The concrete request object used in the WSGI application.
58 It has some helper functions that can be used to build URLs.
59 """
60 charset = 'utf-8'
61
62 def __init__(self, environ, url_adapter):
63 BaseRequest.__init__(self, environ)
64 self.url_adapter = url_adapter
65 local.request = self
66
67
68 class ThreadedRequest(object):
69 """
70 A pseudo request object that always poins to the current
71 context active request.
72 """
73
74 def __getattr__(self, name):
75 if name == '__members__':
76 return [x for x in dir(local.request) if not
77 x.startswith('_')]
78 return getattr(local.request, name)
79
80 def __setattr__(self, name, value):
81 return setattr(local.request, name, value)
82
83
84 class Response(BaseResponse):
85 """
86 The concrete response object for the WSGI application.
87 """
88 charset = 'utf-8'
89 default_mimetype = 'text/html'
90
91
92 class TemplateResponse(Response):
93 """
94 Render a template to a response.
95 """
96
97 def __init__(self, template_name, **values):
98 from coolmagic import helpers
99 values.update(
100 request=local.request,
101 h=helpers
102 )
103 template = template_env.get_template(template_name)
104 Response.__init__(self, template.render(values))
0 # -*- coding: utf-8 -*-
1 """
2 coolmagic.views
3 ~~~~~~~~~~~~~~~
4
5 This module collects and assambles the urls.
6
7 :copyright: 2007 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
0 # -*- coding: utf-8 -*-
1 """
2 coolmagic.views.static
3 ~~~~~~~~~~~~~~~~~~~~~~
4
5 Some static views.
6
7 :copyright: 2007 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 from coolmagic.helpers import *
11
12
13 @export('/', template='static/index.html')
14 def index():
15 pass
16
17
18 @export('/about', template='static/about.html')
19 def about():
20 pass
21
22
23 @export('/broken')
24 def broken():
25 foo = request.args.get('foo', 42)
26 raise RuntimeError('that\'s really broken')
27
28
29 @export(None, template='static/not_found.html')
30 def not_found():
31 """
32 This function is always executed if an url does not
33 match or a `NotFound` exception is raised.
34 """
35 pass
0 couchy README
1
2 Requirements :
3 - python 2.4 & sup
4 - werkzeug : http://werkzeug.pocoo.org
5 - jinja : http://jinja.pocoo.org
6 - couchdb 0.72 & above : http://www.couchdb.org
7 - couchdb-python 0.3 & above : http://code.google.com/p/couchdb-python
(New empty file)
0 from couchdb.client import Server
1 from couchy.utils import STATIC_PATH, local, local_manager, \
2 url_map
3 from werkzeug import Request, SharedDataMiddleware, ClosingIterator
4 from werkzeug.exceptions import HTTPException, NotFound
5 from couchy import views
6 from couchy.models import URL
7 import couchy.models
8
9
10 class Couchy(object):
11
12 def __init__(self, db_uri):
13 local.application = self
14
15 server = Server(db_uri)
16 try:
17 db = server.create('urls')
18 except:
19 db = server['urls']
20 self.dispatch = SharedDataMiddleware(self.dispatch, {
21 '/static': STATIC_PATH
22 })
23
24 URL.db = db
25
26 def dispatch(self, environ, start_response):
27 local.application = self
28 request = Request(environ)
29 local.url_adapter = adapter = url_map.bind_to_environ(environ)
30 try:
31 endpoint, values = adapter.match()
32 handler = getattr(views, endpoint)
33 response = handler(request, **values)
34 except NotFound, e:
35 response = views.not_found(request)
36 response.status_code = 404
37 except HTTPException, e:
38 response = e
39 return ClosingIterator(response(environ, start_response),
40 [local_manager.cleanup])
41
42 def __call__(self, environ, start_response):
43 return self.dispatch(environ, start_response)
0 from datetime import datetime
1 from couchdb.schema import Document, TextField, BooleanField, DateTimeField
2 from couchy.utils import url_for, get_random_uid
3
4
5 class URL(Document):
6 target = TextField()
7 public = BooleanField()
8 added = DateTimeField(default=datetime.utcnow())
9 shorty_id = TextField(default=None)
10 db = None
11
12 @classmethod
13 def load(self, id):
14 return super(URL, self).load(URL.db, id)
15
16 @classmethod
17 def query(self, code):
18 return URL.db.query(code)
19
20 def store(self):
21 if getattr(self._data, 'id', None) is None:
22 new_id = self.shorty_id if self.shorty_id else None
23 while 1:
24 id = new_id if new_id else get_random_uid()
25 docid = None
26 try:
27 docid = URL.db.resource.put(content=self._data, path='/%s/' % str(id))['id']
28 except:
29 continue
30 if docid:
31 break
32 self._data = URL.db.get(docid)
33 else:
34 super(URL, self).store(URL.db)
35 return self
36
37 @property
38 def short_url(self):
39 return url_for('link', uid=self.id, _external=True)
40
41 def __repr__(self):
42 return '<URL %r>' % self.id
0 body {
1 background-color: #333;
2 font-family: 'Lucida Sans', 'Verdana', sans-serif;
3 font-size: 16px;
4 margin: 3em 0 3em 0;
5 padding: 0;
6 text-align: center;
7 }
8
9 a {
10 color: #0C4850;
11 }
12
13 a:hover {
14 color: #1C818F;
15 }
16
17 h1 {
18 width: 500px;
19 background-color: #24C0CE;
20 text-align: center;
21 font-size: 3em;
22 margin: 0 auto 0 auto;
23 padding: 0;
24 }
25
26 h1 a {
27 display: block;
28 padding: 0.3em;
29 color: #fff;
30 text-decoration: none;
31 }
32
33 h1 a:hover {
34 color: #ADEEF7;
35 background-color: #0E8A96;
36 }
37
38 div.footer {
39 margin: 0 auto 0 auto;
40 font-size: 13px;
41 text-align: right;
42 padding: 10px;
43 width: 480px;
44 background-color: #004C63;
45 color: white;
46 }
47
48 div.footer a {
49 color: #A0E9FF;
50 }
51
52 div.body {
53 margin: 0 auto 0 auto;
54 padding: 20px;
55 width: 460px;
56 background-color: #98CE24;
57 color: black;
58 }
59
60 div.body h2 {
61 margin: 0 0 0.5em 0;
62 text-align: center;
63 }
64
65 div.body input {
66 margin: 0.2em 0 0.2em 0;
67 font-family: 'Lucida Sans', 'Verdana', sans-serif;
68 font-size: 20px;
69 background-color: #CCEB98;
70 color: black;
71 }
72
73 div.body #url {
74 width: 400px;
75 }
76
77 div.body #alias {
78 width: 300px;
79 margin-right: 10px;
80 }
81
82 div.body #submit {
83 width: 90px;
84 }
85
86 div.body p {
87 margin: 0;
88 padding: 0.2em 0 0.2em 0;
89 }
90
91 div.body ul {
92 margin: 1em 0 1em 0;
93 padding: 0;
94 list-style: none;
95 }
96
97 div.error {
98 margin: 1em 0 1em 0;
99 border: 2px solid #AC0202;
100 background-color: #9E0303;
101 font-weight: bold;
102 color: white;
103 }
104
105 div.pagination {
106 font-size: 13px;
107 }
0 {% extends 'layout.html' %}
1 {% block body %}
2 <h2>Shortened URL</h2>
3 <p>
4 The URL {{ url.target|urlize(40, true) }}
5 was shortened to {{ url.short_url|urlize }}.
6 </p>
7 {% endblock %}
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
1 "http://www.w3.org/TR/html4/strict.dtd">
2 <html>
3 <head>
4 <title>Shorty</title>
5 <link rel="stylesheet" type="text/css" href="{{ url_for('static', file='style.css') }}">
6 </head>
7 <body>
8 <h1><a href="{{ url_for('new') }}">Shorty</a></h1>
9 <div class="body">{% block body %}{% endblock %}</div>
10 <div class="footer">
11 <a href="{{ url_for('new') }}">new</a> |
12 <a href="{{ url_for('list') }}">list</a> |
13 use shorty for good, not for evil
14 </div>
15 </body>
16 </html>
0 {% extends 'layout.html' %}
1 {% block body %}
2 <h2>List of URLs</h2>
3 <ul>
4 {%- for url in pagination.entries %}
5 <li><a href="{{ url.short_url|e }}">{{ url.id|e }}</a> &raquo;
6 <small>{{ url.target|urlize(38, true) }}</small></li>
7 {%- else %}
8 <li><em>no URLs shortened yet</em></li>
9 {%- endfor %}
10 </ul>
11 <div class="pagination">
12 {%- if pagination.has_previous %}<a href="{{ pagination.previous }}">&laquo; Previous</a>
13 {%- else %}<span class="inactive">&laquo; Previous</span>{% endif %}
14 | {{ pagination.page }} |
15 {% if pagination.has_next %}<a href="{{ pagination.next }}">Next &raquo;</a>
16 {%- else %}<span class="inactive">Next &raquo;</span>{% endif %}
17 </div>
18 {% endblock %}
0 {% extends 'layout.html' %}
1 {% block body %}
2 <h2>Create a Shorty-URL!</h2>
3 {% if error %}<div class="error">{{ error }}</div>{% endif -%}
4 <form action="" method="post">
5 <p>Enter the URL you want to shorten</p>
6 <p><input type="text" name="url" id="url" value="{{ url|e(true) }}"></p>
7 <p>Optionally you can give the URL a memorable name</p>
8 <p><input type="text" id="alias" name="alias">{#
9 #}<input type="submit" id="submit" value="Do!"></p>
10 <p><input type="checkbox" name="private" id="private">
11 <label for="private">make this URL private, so don't list it</label></p>
12 </form>
13 {% endblock %}
0 {% extends 'layout.html' %}
1 {% block body %}
2 <h2>Page Not Found</h2>
3 <p>
4 The page you have requested does not exist on this server. What about
5 <a href="{{ url_for('new') }}">adding a new URL</a>?
6 </p>
7 {% endblock %}
0 from os import path
1 from urlparse import urlparse
2 from random import sample, randrange
3 from jinja import Environment, FileSystemLoader
4 from werkzeug import Response, Local, LocalManager, cached_property
5 from werkzeug.routing import Map, Rule
6
7 TEMPLATE_PATH = path.join(path.dirname(__file__), 'templates')
8 STATIC_PATH = path.join(path.dirname(__file__), 'static')
9 ALLOWED_SCHEMES = frozenset(['http', 'https', 'ftp', 'ftps'])
10 URL_CHARS = 'abcdefghijkmpqrstuvwxyzABCDEFGHIJKLMNPQRST23456789'
11
12 local = Local()
13 local_manager = LocalManager([local])
14 application = local('application')
15
16 url_map = Map([Rule('/static/<file>', endpoint='static', build_only=True)])
17
18 jinja_env = Environment(loader=FileSystemLoader(TEMPLATE_PATH))
19
20
21 def expose(rule, **kw):
22 def decorate(f):
23 kw['endpoint'] = f.__name__
24 url_map.add(Rule(rule, **kw))
25 return f
26 return decorate
27
28 def url_for(endpoint, _external=False, **values):
29 return local.url_adapter.build(endpoint, values, force_external=_external)
30 jinja_env.globals['url_for'] = url_for
31
32 def render_template(template, **context):
33 return Response(jinja_env.get_template(template).render(**context),
34 mimetype='text/html')
35
36 def validate_url(url):
37 return urlparse(url)[0] in ALLOWED_SCHEMES
38
39 def get_random_uid():
40 return ''.join(sample(URL_CHARS, randrange(3, 9)))
41
42 class Pagination(object):
43
44 def __init__(self, results, per_page, page, endpoint):
45 self.results = results
46 self.per_page = per_page
47 self.page = page
48 self.endpoint = endpoint
49
50 @cached_property
51 def count(self):
52 return len(self.results)
53
54 @cached_property
55 def entries(self):
56 return self.results[((self.page - 1) * self.per_page):(((self.page - 1) * self.per_page)+self.per_page)]
57
58 has_previous = property(lambda x: x.page > 1)
59 has_next = property(lambda x: x.page < x.pages)
60 previous = property(lambda x: url_for(x.endpoint, page=x.page - 1))
61 next = property(lambda x: url_for(x.endpoint, page=x.page + 1))
62 pages = property(lambda x: max(0, x.count - 1) // x.per_page + 1)
0 from werkzeug import redirect
1 from werkzeug.exceptions import NotFound
2 from couchy.utils import render_template, expose, \
3 validate_url, url_for, Pagination
4 from couchy.models import URL
5
6
7 @expose('/')
8 def new(request):
9 error = url = ''
10 if request.method == 'POST':
11 url = request.form.get('url')
12 alias = request.form.get('alias')
13 if not validate_url(url):
14 error = "I'm sorry but you cannot shorten this URL."
15 elif alias:
16 if len(alias) > 140:
17 error = 'Your alias is too long'
18 elif '/' in alias:
19 error = 'Your alias might not include a slash'
20 elif URL.load(alias):
21 error = 'The alias you have requested exists already'
22 if not error:
23 url = URL(target=url, public='private' not in request.form, shorty_id=alias if alias else None)
24 url.store()
25 uid = url.id
26 return redirect(url_for('display', uid=uid))
27 return render_template('new.html', error=error, url=url)
28
29 @expose('/display/<uid>')
30 def display(request, uid):
31 url = URL.load(uid)
32 if not url:
33 raise NotFound()
34 return render_template('display.html', url=url)
35
36 @expose('/u/<uid>')
37 def link(request, uid):
38 url = URL.load(uid)
39 if not url:
40 raise NotFound()
41 return redirect(url.target, 301)
42
43 @expose('/list/', defaults={'page': 1})
44 @expose('/list/<int:page>')
45 def list(request, page):
46 def wrap(doc):
47 data = doc.value
48 data['_id'] = doc.id
49 return URL.wrap(data)
50
51 code = '''function(doc) { if (doc.public){ map([doc._id], doc); }}'''
52 docResults = URL.query(code)
53 results = [wrap(doc) for doc in docResults]
54 pagination = Pagination(results, 1, page, 'list')
55 if pagination.page > 1 and not pagination.entries:
56 raise NotFound()
57 return render_template('list.html', pagination=pagination)
58
59 def not_found(request):
60 return render_template('not_found.html')
0 # -*- coding: utf-8 -*-
1 """
2 cupoftee
3 ~~~~~~~~
4
5 Werkzeug powered Teeworlds Server Browser.
6
7 :copyright: 2008 by Armin Ronacher.
8 :license: GNU GPL.
9 """
10 from cupoftee.application import make_app
0 # -*- coding: utf-8 -*-
1 """
2 cupoftee.application
3 ~~~~~~~~~~~~~~~~~~~~
4
5 The WSGI appliction for the cup of tee browser.
6
7 :copyright: Copyright 2008 by Armin Ronacher.
8 :license: GNU GPL.
9 """
10 import time
11 from os import path
12 from threading import Thread
13 from cupoftee.db import Database
14 from cupoftee.network import ServerBrowser
15 from werkzeug import Request, Response, Template, SharedDataMiddleware
16 from werkzeug.exceptions import HTTPException, NotFound
17 from werkzeug.routing import Map, Rule
18
19
20 templates = path.join(path.dirname(__file__), 'templates')
21 pages = {}
22 url_map = Map([Rule('/shared/<file>', endpoint='shared')])
23
24
25 def make_app(database, interval=60):
26 return SharedDataMiddleware(Cup(database), {
27 '/shared': path.join(path.dirname(__file__), 'shared')
28 })
29
30
31 class PageMeta(type):
32
33 def __init__(cls, name, bases, d):
34 type.__init__(cls, name, bases, d)
35 if d.get('url_rule') is not None:
36 pages[cls.identifier] = cls
37 url_map.add(Rule(cls.url_rule, endpoint=cls.identifier,
38 **cls.url_arguments))
39
40 identifier = property(lambda x: x.__name__.lower())
41
42
43 class Page(object):
44 __metaclass__ = PageMeta
45 url_arguments = {}
46
47 def __init__(self, cup, request, url_adapter):
48 self.cup = cup
49 self.request = request
50 self.url_adapter = url_adapter
51
52 def url_for(self, endpoint, **values):
53 return self.url_adapter.build(endpoint, values)
54
55 def process(self):
56 pass
57
58 def render_template(self, template=None):
59 if template is None:
60 template = self.__class__.identifier + '.html'
61 context = dict(self.__dict__)
62 context.update(url_for=self.url_for, self=self)
63 body_tmpl = Template.from_file(path.join(templates, template))
64 layout_tmpl = Template.from_file(path.join(templates, 'layout.html'))
65 context['body'] = body_tmpl.render(context)
66 return layout_tmpl.render(context)
67
68 def get_response(self):
69 return Response(self.render_template(), mimetype='text/html')
70
71
72 class Cup(object):
73
74 def __init__(self, database, interval=120):
75 self.interval = interval
76 self.db = Database(database)
77 self.master = ServerBrowser(self)
78 self.updater = Thread(None, self.update_master)
79 self.updater.setDaemon(True)
80 self.updater.start()
81
82 def update_master(self):
83 wait = self.interval
84 while 1:
85 time.sleep(wait)
86 if self.master.sync():
87 wait = self.interval
88 else:
89 wait = self.interval // 2
90
91 def dispatch_request(self, request):
92 url_adapter = url_map.bind_to_environ(request.environ)
93 try:
94 endpoint, values = url_adapter.match()
95 page = pages[endpoint](self, request, url_adapter)
96 response = page.process(**values)
97 except NotFound, e:
98 page = MissingPage(self, request, url_adapter)
99 response = page.process()
100 except HTTPException, e:
101 return e
102 return response or page.get_response()
103
104 def __call__(self, environ, start_response):
105 request = Request(environ)
106 return self.dispatch_request(request)(environ, start_response)
107
108
109 from cupoftee.pages import MissingPage
0 # -*- coding: utf-8 -*-
1 """
2 cupoftee.db
3 ~~~~~~~~~~~
4
5 A simple object database. As long as the server is not running in
6 multiprocess mode that's good enough.
7
8 :copyright: Copyright 2008 by Armin Ronacher.
9 :license: GNU GPL.
10 """
11 from __future__ import with_statement
12 import gdbm
13 from threading import Lock
14 from pickle import dumps, loads
15
16
17 class Database(object):
18
19 def __init__(self, filename):
20 self.filename = filename
21 self._fs = gdbm.open(filename, 'cf')
22 self._local = {}
23 self._lock = Lock()
24
25 def __getitem__(self, key):
26 with self._lock:
27 return self._load_key(key)
28
29 def _load_key(self, key):
30 if key in self._local:
31 return self._local[key]
32 rv = loads(self._fs[key])
33 self._local[key] = rv
34 return rv
35
36 def __setitem__(self, key, value):
37 self._local[key] = value
38
39 def __delitem__(self, key, value):
40 with self._lock:
41 self._local.pop(key, None)
42 if self._fs.has_key(key):
43 del self._fs[key]
44
45 def __del__(self):
46 self.close()
47
48 def __contains__(self, key):
49 with self._lock:
50 try:
51 self._load_key(key)
52 except KeyError:
53 pass
54 return key in self._local
55
56 def setdefault(self, key, factory):
57 with self._lock:
58 try:
59 rv = self._load_key(key)
60 except KeyError:
61 self._local[key] = rv = factory()
62 return rv
63
64 def sync(self):
65 with self._lock:
66 for key, value in self._local.iteritems():
67 self._fs[key] = dumps(value, 2)
68 self._fs.sync()
69
70 def close(self):
71 try:
72 self.sync()
73 self._fs.close()
74 except:
75 pass
Binary diff not shown
0 # -*- coding: utf-8 -*-
1 """
2 cupyoftee.network
3 ~~~~~~~~~~~~~~~~~
4
5 Query the servers for information.
6
7 :copyright: Copyright 2008 by Armin Ronacher.
8 :license: GNU GPL.
9 """
10 import time
11 import socket
12 from math import log
13 from datetime import datetime
14 from cupoftee.utils import unicodecmp
15
16
17 GAMETYPES = dict(enumerate(('dm', 'tdm', 'ctf')))
18
19
20 class ServerError(Exception):
21 pass
22
23
24 class Syncable(object):
25 last_sync = None
26
27 def sync(self):
28 try:
29 self._sync()
30 except (socket.error, socket.timeout, IOError):
31 return False
32 self.last_sync = datetime.utcnow()
33 return True
34
35
36 class ServerBrowser(Syncable):
37
38 def __init__(self, cup):
39 self.cup = cup
40 self.servers = cup.db.setdefault('servers', dict)
41
42 def _sync(self):
43 to_delete = set(self.servers)
44 for x in xrange(1, 17):
45 addr = ('master%d.teeworlds.com' % x, 8300)
46 try:
47 self._sync_master(addr, to_delete)
48 except (socket.error, socket.timeout, IOError):
49 continue
50 for server_id in to_delete:
51 self.servers.pop(server_id, None)
52 if not self.servers:
53 raise IOError('no servers found')
54 self.cup.db.sync()
55
56 def _sync_master(self, addr, to_delete):
57 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
58 s.settimeout(5)
59 s.sendto('\x20\x00\x00\x00\x00\x48\xff\xff\xff\xffreqt', addr)
60 data = s.recvfrom(1024)[0][14:]
61 s.close()
62
63 for n in xrange(0, len(data) / 6):
64 addr = ('.'.join(map(str, map(ord, data[n * 6:n * 6 + 4]))),
65 ord(data[n * 6 + 5]) * 256 + ord(data[n * 6 + 4]))
66 server_id = '%s:%d' % addr
67 if server_id in self.servers:
68 if not self.servers[server_id].sync():
69 continue
70 else:
71 try:
72 self.servers[server_id] = Server(addr, server_id)
73 except ServerError:
74 pass
75 to_delete.discard(server_id)
76
77
78 class Server(Syncable):
79
80 def __init__(self, addr, server_id):
81 self.addr = addr
82 self.id = server_id
83 self.players = []
84 if not self.sync():
85 raise ServerError('server not responding in time')
86
87 def _sync(self):
88 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
89 s.settimeout(1)
90 s.sendto('\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffgief', self.addr)
91 bits = s.recvfrom(1024)[0][14:].split('\x00')
92 s.close()
93 self.version, server_name, map_name = bits[:3]
94 self.name = server_name.decode('latin1')
95 self.map = map_name.decode('latin1')
96 self.gametype_id, self.flags, self.progression, player_count, \
97 self.max_players = map(int, bits[3:8])
98 self.gametype = GAMETYPES.get(self.gametype_id, 'unknown')
99
100 # sync the player stats
101 players = dict((p.name, p) for p in self.players)
102 for i in xrange(player_count):
103 name = bits[8 + i * 2].decode('latin1')
104 score = int(bits[9 + i * 2])
105
106 # update existing player
107 if name in players:
108 player = players.pop(name)
109 player.score = score
110 # add new player
111 else:
112 self.players.append(Player(self, name, score))
113 # delete players that left
114 for player in players.itervalues():
115 try:
116 self.players.remove(player)
117 except:
118 pass
119
120 # sort the player list and count them
121 self.players.sort(key=lambda x: -x.score)
122 self.player_count = len(self.players)
123
124 def __cmp__(self, other):
125 return unicodecmp(self.name, other.name)
126
127
128 class Player(object):
129
130 def __init__(self, server, name, score):
131 self.server = server
132 self.name = name
133 self.score = score
134 self.size = round(100 + log(max(score, 1)) * 25, 2)
0 # -*- coding: utf-8 -*-
1 """
2 cupoftee.pages
3 ~~~~~~~~~~~~~~
4
5 The pages.
6
7 :copyright: Copyright 2008 by Armin Ronacher.
8 :license: GNU GPL.
9 """
10 import operator
11 from werkzeug import redirect
12 from werkzeug.exceptions import NotFound
13 from cupoftee.application import Page
14 from cupoftee.utils import unicodecmp
15
16
17 class ServerList(Page):
18 url_rule = '/'
19
20 def order_link(self, name, title):
21 cls = ''
22 link = '?order_by=' + name
23 desc = False
24 if name == self.order_by:
25 desc = not self.order_desc
26 cls = ' class="%s"' % (desc and 'down' or 'up')
27 if desc:
28 link += '&amp;dir=desc'
29 return '<a href="%s"%s>%s</a>' % (link, cls, title)
30
31 def process(self):
32 self.order_by = self.request.args.get('order_by') or 'name'
33 sort_func = {
34 'name': lambda x: x,
35 'map': lambda x: x.map,
36 'gametype': lambda x: x.gametype,
37 'players': lambda x: x.player_count,
38 'progression': lambda x: x.progression,
39 }.get(self.order_by)
40 if sort_func is None:
41 return redirect(self.url_for('serverlist'))
42
43 self.servers = self.cup.master.servers.values()
44 self.servers.sort(key=sort_func)
45 if self.request.args.get('dir') == 'desc':
46 self.servers.reverse()
47 self.order_desc = True
48 else:
49 self.order_desc = False
50
51 self.players = reduce(lambda a, b: a + b.players, self.servers, [])
52 self.players.sort(lambda a, b: unicodecmp(a.name, b.name))
53
54
55 class Server(Page):
56 url_rule = '/server/<id>'
57
58 def process(self, id):
59 try:
60 self.server = self.cup.master.servers[id]
61 except KeyError:
62 raise NotFound()
63
64
65 class Search(Page):
66 url_rule = '/search'
67
68 def process(self):
69 self.user = self.request.args.get('user')
70 if self.user:
71 self.results = []
72 for server in self.cup.master.servers.itervalues():
73 for player in server.players:
74 if player.name == self.user:
75 self.results.append(server)
76
77
78 class MissingPage(Page):
79
80 def get_response(self):
81 response = super(MissingPage, self).get_response()
82 response.status_code = 404
83 return response
0 body {
1 font-family: 'Verdana', sans-serif;
2 background: #2b93ad;
3 margin: 0;
4 padding: 0;
5 font-size: 15px;
6 text-align: center;
7 }
8
9 h1 {
10 font-size: 0;
11 margin: 0;
12 padding: 10px 0 0 10px;
13 height: 124px;
14 line-height: 100px;
15 background: url(header.png);
16 color: white;
17 }
18
19 h1 a {
20 display: block;
21 margin: 0 auto 0 auto;
22 height: 90px;
23 width: 395px;
24 background: url(logo.png);
25 }
26
27 div.contents {
28 background: white url(content.png) repeat-x;
29 margin: -8px auto 0 auto;
30 text-align: left;
31 padding: 15px;
32 max-width: 1000px;
33 }
34
35 div.contents a {
36 margin: 0 5px 0 5px;
37 }
38
39 div.footer {
40 max-width: 1014px;
41 margin: 0 auto 0 auto;
42 background: #1a6f96;
43 padding: 8px;
44 font-size: 10px;
45 color: white;
46 }
47
48 div.footer a {
49 color: #79b9d7;
50 }
51
52 a {
53 color: #1a6f96;
54 text-decoration: none;
55 }
56
57 a:hover {
58 color: #ffb735;
59 }
60
61 h2 {
62 margin: 0 0 0.5em 0;
63 padding: 0 0 0.1em 0;
64 color: #ffb735;
65 font-size: 2em;
66 border-bottom: 1px solid #ccc;
67 }
68
69 h3 {
70 margin: 1em 0 0.7em 0;
71 color: #ffb735;
72 font-size: 1.5em;
73 }
74
75 table {
76 width: 100%;
77 border-collapse: collapse;
78 border: 3px solid #79b9d7;
79 }
80
81 table td, table th {
82 border: 1px solid #79b9d7;
83 padding: 3px 6px 3px 6px;
84 font-weight: normal;
85 text-align: center;
86 font-size: 13px;
87 }
88
89 table th {
90 background: #f2f8fb;
91 text-align: left;
92 }
93
94 table thead th {
95 font-weight: bold;
96 background-color: #79b9d7;
97 text-align: center;
98 }
99
100 table thead th a {
101 color: white;
102 }
103
104 table thead th a.up {
105 background: url(up.png) no-repeat right;
106 padding-right: 20px;
107 }
108
109 table thead th a.down {
110 background: url(down.png) no-repeat right;
111 padding-right: 20px;
112 }
113
114 div.players {
115 font-size: 11px;
116 }
117
118 dl dt {
119 font-weight: bold;
120 padding: 5px 0 0 0;
121 }
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
1 "http://www.w3.org/TR/html4/strict.dtd">
2 <html>
3 <head>
4 <title>Teeworlds Server Browser</title>
5 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6 <link rel="stylesheet" type="text/css" href="${url_for('shared', file='style.css')}">
7 </head>
8 <body>
9 <h1><a href="http://www.teeworlds.com/">Teeworlds</a> Server Browser</h1>
10 <div class="contents">
11 ${body}
12 </div>
13 <div class="footer">
14 <a href="http://www.teeworls.com/">Teeworlds</a> Server Browser by
15 <a href="http://lucumr.pocoo.org/">Armin Ronacher</a>, powered by
16 <a href="http://werkzeug.pocoo.org/">Werkzeug</a>, licensed under
17 the <a href="http://www.gnu.org/copyleft/gpl.html">GNU GPL</a>.
18 </div>
19 </body>
20 </html>
0 <h2>Page Not Found</h2>
1 <p>
2 The requested page does not exist on this server. If you expect something
3 here (for example a server) it probably went away after the last update.
4 </p>
5 <p>
6 go back to <a href="$url_for('serverlist')">the server list</a>.
7 </p>
0 <h2>Nick Search</h2>
1 <% if not user %>
2 <form action="$url_for('search')" method="get">
3 <p>
4 You have to enter a nickname.
5 </p>
6 <p>
7 <input type="text" name="user" value="" size="30">
8 <input type="submit" value="Search">
9 </p>
10 <p>
11 Take me <a href="$url_for('serverlist')">back to the server list</a>.
12 </p>
13 </form>
14 <% else %>
15 <% if results %>
16 <p>
17 The nickname "$escape(user)" is currently playing on the
18 following ${len(results) == 1 and 'server' or 'servers'}:
19 </p>
20 <ul>
21 <% for server in results %>
22 <li><a href="$url_for('server', id=server.id)">$escape(server.name)</a></li>
23 <% endfor %>
24 </ul>
25 <% else %>
26 <p>
27 The nickname "$escape(user)" is currently not playing.
28 </p>
29 <% endif %>
30 <p>
31 You can <a href="$url_for('search', user=self.user)">bookmark this link</a>
32 to search for "$escape(user)" quickly or <a href="$url_for('serverlist')">return
33 to the server list</a>.
34 </p>
35 <% endif %>
0 <h2>$escape(server.name)</h2>
1 <p>
2 Take me back to <a href="$url_for('serverlist')">the server list</a>.
3 </p>
4 <dl class="serverinfo">
5 <dt>Map</dt>
6 <dd>$escape(server.map)</dd>
7 <dt>Gametype</dt>
8 <dd>$server.gametype</dd>
9 <dt>Number of players</dt>
10 <dd>$server.player_count</dd>
11 <dt>Server version</dt>
12 <dd>$server.version</dd>
13 <dt>Maximum number of players</dt>
14 <dd>$server.max_players</dd>
15 <% if server.progression >= 0 %>
16 <dt>Game progression</dt>
17 <dd>$server.progression%</dd>
18 <% endif %>
19 </dl>
20 <% if server.players %>
21 <h3>Players</h3>
22 <ul class="players">
23 <% for player in server.players %>
24 <li><a href="$url_for('search', user=player.name)">$escape(player.name)</a>
25 <small>($player.score)</small></li>
26 <% endfor %>
27 </ul>
28 <% endif %>
0 <h2>Server List</h2>
1 <p>
2 Currently <strong>$len(players)</strong> players are playing on
3 <strong>$len(servers)</strong> servers.
4 <% if cup.master.last_sync %>
5 This list was last synced on
6 <strong>$cup.master.last_sync.strftime('%d %B %Y at %H:%M UTC')</strong>.
7 <% else %>
8 Syncronization with master server in progress. Reload the page in a minute
9 or two, to see the server list.
10 <% endif %>
11 </p>
12 <table class="servers">
13 <thead>
14 <tr>
15 <th>$self.order_link('name', 'Name')</th>
16 <th>$self.order_link('map', 'Map')</th>
17 <th>$self.order_link('gametype', 'Gametype')</th>
18 <th>$self.order_link('players', 'Players')</th>
19 <th>$self.order_link('progression', 'Progression')</th>
20 </tr>
21 </thead>
22 <tbody>
23 <% for server in servers %>
24 <tr>
25 <th><a href="$url_for('server', id=server.id)">$escape(server.name)</a></th>
26 <td>$escape(server.map)</td>
27 <td>$server.gametype</td>
28 <td>$server.player_count / $server.max_players</td>
29 <td>${server.progression >= 0 and '%d%%' % server.progression or '?'}</td>
30 </tr>
31 <% endfor %>
32 </tbody>
33 </table>
34 <h3>Players online</h3>
35 <p>
36 The following map represents the users playing currently. The bigger their name
37 the higher their score in the current game. Clicking on the name takes you to
38 the detail page of the server for some more information.
39 </p>
40 <div class="players">
41 <% for player in players %>
42 <a href="$url_for('server', id=player.server.id)" title="score: $player.score"
43 style="font-size: $player.size%">$escape(player.name)</a>
44 <% endfor %>
45 </div>
46 <h3>Find User</h3>
47 <p>
48 Find a user by username. The result page contains a link you can bookmark to
49 find your buddy easily. Because currently there is no central user database
50 users can appear on multiple servers for too generic usernames (like the
51 default "nameless tee" user).
52 </p>
53 <form action="$url_for('search')" method="get">
54 <p>
55 <input type="text" name="user" value="" size="30">
56 <input type="submit" value="Search">
57 </p>
58 </form>
0 # -*- coding: utf-8 -*-
1 """
2 cupoftee.utils
3 ~~~~~~~~~~~~~~
4
5 Various utilities.
6
7 :copyright: Copyright 2008 by Armin Ronacher.
8 :license: GNU GPL.
9 """
10 import re
11
12
13 _sort_re = re.compile(r'\w+(?u)')
14
15
16 def unicodecmp(a, b):
17 x, y = map(_sort_re.search, [a, b])
18 return cmp((x and x.group() or a).lower(), (y and y.group() or b).lower())
0 # -*- coding: utf-8 -*-
1 """
2 HTTP Basic Auth Example
3 ~~~~~~~~~~~~~~~~~~~~~~~
4
5 Shows how you can implement HTTP basic auth support without an
6 additional component.
7
8 :copyright: Copyright 2008 by Armin Ronacher.
9 :license: BSD.
10 """
11 from werkzeug import Request, Response, run_simple
12
13
14 class Application(object):
15
16 def __init__(self, users, realm='login required'):
17 self.users = users
18 self.realm = realm
19
20 def check_auth(self, username, password):
21 return username in self.users and self.users[username] == password
22
23 def auth_required(self, request):
24 return Response('Could not verify your access level for that URL.\n'
25 'You have to login with proper credentials', 401,
26 {'WWW-Authenticate': 'Basic realm="%s"' % self.realm})
27
28 def dispatch_request(self, request):
29 return Response('Logged in as %s' % request.authorization.username)
30
31 def __call__(self, environ, start_response):
32 request = Request(environ)
33 auth = request.authorization
34 if not auth or not self.check_auth(auth.username, auth.password):
35 response = self.auth_required(request)
36 else:
37 response = self.dispatch_request(request)
38 return response(environ, start_response)
39
40
41 if __name__ == '__main__':
42 application = Application({'user1': 'password', 'user2': 'password'})
43 run_simple('localhost', 5000, application)
0 from i18nurls.application import Application as make_app
0 from os import path
1 from werkzeug import BaseRequest, BaseResponse, Template
2 from werkzeug.routing import NotFound, RequestRedirect
3 from werkzeug.exceptions import HTTPException, NotFound
4 from i18nurls.urls import map
5
6 TEMPLATES = path.join(path.dirname(__file__), 'templates')
7 views = {}
8
9
10 def expose(name):
11 """Register the function as view."""
12 def wrapped(f):
13 views[name] = f
14 return f
15 return wrapped
16
17
18 class Request(BaseRequest):
19
20 def __init__(self, environ, urls):
21 super(Request, self).__init__(environ)
22 self.urls = urls
23 self.matched_url = None
24
25 def url_for(self, endpoint, **args):
26 if not 'lang_code' in args:
27 args['lang_code'] = self.language
28 if endpoint == 'this':
29 endpoint = self.matched_url[0]
30 tmp = self.matched_url[1].copy()
31 tmp.update(args)
32 args = tmp
33 return self.urls.build(endpoint, args)
34
35
36 class Response(BaseResponse):
37 pass
38
39
40 class TemplateResponse(Response):
41
42 def __init__(self, template_name, **values):
43 self.template_name = template_name
44 self.template_values = values
45 Response.__init__(self, mimetype='text/html')
46
47 def __call__(self, environ, start_response):
48 req = environ['werkzeug.request']
49 values = self.template_values.copy()
50 values['req'] = req
51 values['body'] = self.render_template(self.template_name, values)
52 self.write(self.render_template('layout.html', values))
53 return Response.__call__(self, environ, start_response)
54
55 def render_template(self, name, values):
56 return Template.from_file(path.join(TEMPLATES, name)).render(values)
57
58
59 class Application(object):
60
61 def __init__(self):
62 from i18nurls import views
63 self.not_found = views.page_not_found
64
65 def __call__(self, environ, start_response):
66 urls = map.bind_to_environ(environ)
67 req = Request(environ, urls)
68 try:
69 endpoint, args = urls.match(req.path)
70 req.matched_url = (endpoint, args)
71 if endpoint == '#language_select':
72 lng = req.accept_languages.best
73 lng = lng and lng.split('-')[0].lower() or 'en'
74 index_url = urls.build('index', {'lang_code': lng})
75 resp = Response('Moved to %s' % index_url, status=302)
76 resp.headers['Location'] = index_url
77 else:
78 req.language = args.pop('lang_code', None)
79 resp = views[endpoint](req, **args)
80 except NotFound:
81 resp = self.not_found(req)
82 except (RequestRedirect, HTTPException), e:
83 resp = e
84 return resp(environ, start_response)
0 <p>
1 This is just another page. Maybe you want to head over to the
2 <a href="$escape(req.url_for('blog/index'))">blog</a>.
3 </p>
0 <p>Blog <% if mode == 'index' %>Index<% else %>Post $post_id<% endif %></p>
1 <p>
2 How about going to <a href="$escape(req.url_for('index'))">the index</a>.
3 </p>
0 <p>Hello in the i18n URL example application.</p>
1 <p>Because I'm too lazy to translate here is just english content.</p>
2 <ul>
3 <li><a href="$escape(req.url_for('about'))">about this page</a></li>
4 </ul>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
1 "http://www.w3.org/TR/html4/loose.dtd">
2 <html>
3 <head>
4 <title>$title | Example Application</title>
5 </head>
6 <body>
7 <h1>Example Application</h1>
8 <p>
9 Request Language: <strong>$req.language</strong>
10 </p>
11 $body
12 <blockquote>
13 <p>This page in other languages:
14 <ul>
15 <% for lng in ['en', 'de', 'fr'] %>
16 <li><a href="$escape(req.url_for('this', lang_code=lng))">$lng</a></li>
17 <% endfor %>
18 </ul>
19 </blockquote>
20 </body>
21 </html>
0 from werkzeug.routing import Map, Rule, Submount
1
2 map = Map([
3 Rule('/', endpoint='#language_select'),
4 Submount('/<string(length=2):lang_code>', [
5 Rule('/', endpoint='index'),
6 Rule('/about', endpoint='about'),
7 Rule('/blog/', endpoint='blog/index'),
8 Rule('/blog/<int:post_id>', endpoint='blog/show')
9 ])
10 ])
0 from i18nurls.application import TemplateResponse, Response, expose
1
2
3 @expose('index')
4 def index(req):
5 return TemplateResponse('index.html', title='Index')
6
7 @expose('about')
8 def about(req):
9 return TemplateResponse('about.html', title='About')
10
11 @expose('blog/index')
12 def blog_index(req):
13 return TemplateResponse('blog.html', title='Blog Index', mode='index')
14
15 @expose('blog/show')
16 def blog_show(req, post_id):
17 return TemplateResponse('blog.html', title='Blog Post #%d' % post_id,
18 post_id=post_id, mode='show')
19
20 def page_not_found(req):
21 return Response('<h1>Page Not Found</h1>', mimetype='text/html')
0 #!/usr/bin/env python
1 # -*- coding: utf-8 -*-
2 """
3 Manage Coolmagic
4 ~~~~~~~~~~~~~~~~
5
6 Manage the coolmagic example application.
7
8 :copyright: 2007 by Armin Ronacher.
9 :license: BSD, see LICENSE for more details.
10 """
11 import os
12 from coolmagic import make_app
13 from werkzeug import script
14
15 action_runserver = script.make_runserver(make_app, use_reloader=True)
16 action_shell = script.make_shell(lambda: {})
17
18 if __name__ == '__main__':
19 script.run()
0 #!/usr/bin/env python
1 from werkzeug import script
2
3 def make_app():
4 from couchy.application import Couchy
5 return Couchy('http://localhost:5984')
6
7 def make_shell():
8 from couchy import models, utils
9 application = make_app()
10 return locals()
11
12 action_runserver = script.make_runserver(make_app, use_reloader=True)
13 action_shell = script.make_shell(make_shell)
14 action_initdb = lambda: make_app().init_database()
15
16 script.run()
0 #!/usr/bin/env python
1 """
2 Manage Cup Of Tee
3 ~~~~~~~~~~~~~~~~~
4
5 Manage the cup of tee application.
6
7 :copyright: 2008 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 from werkzeug import script
11
12
13 def make_app():
14 from cupoftee import make_app
15 return make_app('/tmp/cupoftee.db')
16 action_runserver = script.make_runserver(make_app)
17
18 script.run()
0 #!/usr/bin/env python
1 # -*- coding: utf-8 -*-
2 """
3 Manage i18nurls
4 ~~~~~~~~~~~~~~~
5
6 Manage the i18n url example application.
7
8 :copyright: 2007 by Armin Ronacher.
9 :license: BSD, see LICENSE for more details.
10 """
11 import os
12 from i18nurls import make_app
13 from werkzeug import script
14
15 action_runserver = script.make_runserver(make_app)
16 action_shell = script.make_shell(lambda: {})
17
18 if __name__ == '__main__':
19 script.run()
0 #!/usr/bin/env python
1 # -*- coding: utf-8 -*-
2 """
3 Manage plnt
4 ~~~~~~~~~~~
5
6 This script manages the plnt application.
7
8 :copyright: 2007 by Armin Ronacher.
9 :license: BSD, see LICENSE for more details.
10 """
11 import os
12 from werkzeug import script
13
14
15 def make_app():
16 """Helper function that creates a plnt app."""
17 from plnt import Plnt
18 database_uri = os.environ.get('PLNT_DATABASE_URI')
19 app = Plnt(database_uri or 'sqlite:////tmp/plnt.db')
20 app.bind_to_context()
21 return app
22
23
24 action_runserver = script.make_runserver(make_app, use_reloader=True)
25 action_shell = script.make_shell(lambda: {'app': make_app()})
26
27
28 def action_initdb():
29 """Initialize the database"""
30 from plnt.database import Blog, Session
31 make_app().init_database()
32 # and now fill in some python blogs everybody should read (shamelessly
33 # added my own blog too)
34 Blog('Armin Ronacher', 'http://lucumr.pocoo.org/',
35 'http://lucumr.pocoo.org/cogitations/feed/'),
36 Blog('Georg Brandl', 'http://pyside.blogspot.com/',
37 'http://pyside.blogspot.com/feeds/posts/default')
38 Blog('Ian Bicking', 'http://blog.ianbicking.org/',
39 'http://blog.ianbicking.org/feed/')
40 Blog('Amir Salihefendic', 'http://amix.dk/',
41 'http://feeds.feedburner.com/amixdk')
42 Blog('Christopher Lenz', 'http://www.cmlenz.net/blog/',
43 'http://www.cmlenz.net/blog/atom.xml')
44 Blog('Frederick Lundh', 'http://online.effbot.org/',
45 'http://online.effbot.org/rss.xml')
46 # okay. got tired here. if someone feels that he is missing, drop me
47 # a line ;-)
48 Session().commit()
49 print 'Initialized database, now run manage-plnt.py sync to get the posts'
50
51
52 def action_sync():
53 """Sync the blogs in the planet. Call this from a cronjob."""
54 from plnt.sync import sync
55 make_app().bind_to_context()
56 sync()
57
58
59 if __name__ == '__main__':
60 script.run()
0 #!/usr/bin/env python
1 from werkzeug import script
2
3 def make_app():
4 from shorty.application import Shorty
5 return Shorty('sqlite:////tmp/shorty.db')
6
7 def make_shell():
8 from shorty import models, utils
9 application = make_app()
10 return locals()
11
12 action_runserver = script.make_runserver(make_app, use_reloader=True)
13 action_shell = script.make_shell(make_shell)
14 action_initdb = lambda: make_app().init_database()
15
16 script.run()
0 #!/usr/bin/env python
1 # -*- coding: utf-8 -*-
2 """
3 Manage SimpleWiki
4 ~~~~~~~~~~~~~~~~~
5
6 This script provides some basic commands to debug and test SimpleWiki.
7
8 :copyright: 2007 by Armin Ronacher.
9 :license: BSD, see LICENSE for more details.
10 """
11 import os
12 from werkzeug import script
13
14
15 def make_wiki():
16 """Helper function that creates a new wiki instance."""
17 from simplewiki import SimpleWiki
18 database_uri = os.environ.get('SIMPLEWIKI_DATABASE_URI')
19 return SimpleWiki(database_uri or 'sqlite:////tmp/simplewiki.db')
20
21
22 def shell_init_func():
23 """
24 Called on shell initialization. Adds useful stuff to the namespace.
25 """
26 from simplewiki import database
27 wiki = make_wiki()
28 wiki.bind_to_context()
29 return {
30 'wiki': wiki,
31 'db': database
32 }
33
34
35 action_runserver = script.make_runserver(make_wiki, use_reloader=True)
36 action_shell = script.make_shell(shell_init_func)
37
38
39 def action_initdb():
40 """Initialize the database"""
41 make_wiki().init_database()
42
43
44 if __name__ == '__main__':
45 script.run()
0 #!/usr/bin/env python
1 # -*- coding: utf-8 -*-
2 """
3 Manage web.py like application
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 A small example application that is built after the web.py tutorial. We
7 even use regular expression based dispatching. The original code can be
8 found on the `webpy.org webpage`__ in the tutorial section.
9
10 __ http://webpy.org/tutorial2.en
11
12 :copyright: 2007 by Armin Ronacher.
13 :license: BSD, see LICENSE for more details.
14 """
15 import os
16 import sys
17 sys.path.append(os.path.join(os.path.dirname(__file__), 'webpylike'))
18 from example import app
19 from werkzeug import script
20
21 action_runserver = script.make_runserver(lambda: app)
22 action_shell = script.make_shell(lambda: {})
23
24 if __name__ == '__main__':
25 script.run()
0 # -*- coding: utf-8 -*-
1 """
2 Object Based Dispatching
3 ~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Colubrid inspired object based dispatching.
6
7 :copyright: Copyright 2008 by Armin Ronacher.
8 :license: GNU GPL.
9 """
10 import inspect
11 from werkzeug import Request, Response, responder, redirect
12 from werkzeug.exceptions import HTTPException, NotFound
13
14
15 def fix_slash(request, is_container):
16 path = request.path
17 new_path = None
18 ends_with_slash = path.endswith('/')
19 if ends_with_slash and not is_container:
20 new_path = path.rstrip('/')
21 elif not ends_with_slash and is_container:
22 new_path = path + '/'
23 if new_path is not None:
24 qs = request.environ.get('QUERY_STRING')
25 if qs:
26 new_path += '?' + qs
27 return redirect(new_path)
28
29
30 class Controller(object):
31
32 def __init__(self, request):
33 self.request = request
34
35
36 class ObjectApplication(object):
37
38 root = None
39
40 def dispatch_request(self, request):
41 handler = self.root
42 container = None
43 args = []
44 for part in request.path.strip('/').split('/'):
45 if part.startswith('_'):
46 raise NotFound()
47 node = getattr(handler, part, None)
48 if node is None:
49 if part:
50 args.append(part)
51 else:
52 handler = node
53
54 if inspect.ismethod(handler):
55 if handler.__name__ == 'index':
56 container = False
57 else:
58 index = getattr(handler, 'index', None)
59 if index is not None:
60 if not hasattr(index, '_is_container'):
61 container = True
62 handler = index
63 else:
64 raise NotFound()
65
66 if container is None and hasattr(handler, '_is_container'):
67 container = handler._is_container
68
69 handler_args, varargs, _, defaults = inspect.getargspec(handler)
70 if defaults is None:
71 defaults = 0
72 else:
73 defaults = len(defaults)
74
75 max_len = len(handler_args) - 1
76 min_len = max_len - defaults
77 cur_len = len(args)
78 if varargs:
79 max_len = -1
80
81 # check if the number of arguments fits our handler
82 if max_len < 0:
83 if cur_len < min_len:
84 raise NotFound()
85 elif min_len <= cur_len <= max_len:
86 if container is None:
87 container = cur_len < max_len
88 else:
89 raise NotFound()
90
91 # redirect if necessary
92 response = fix_slash(request, container)
93 if response is not None:
94 return response
95
96 # call handler
97 return handler(handler.im_class(request), *args)
98
99 @responder
100 def __call__(self, environ, start_response):
101 request = Request(environ)
102 try:
103 rv = self.dispatch_request(request)
104 if isinstance(rv, basestring):
105 rv = Response(rv, mimetype='text/html')
106 return rv
107 except HTTPException, e:
108 return e
109
110
111 class BlogController(Controller):
112
113 def index(self):
114 return 'Hello World'
115
116
117 class AdminController(Controller):
118
119 def hello(self, name='World'):
120 times = self.request.args['times']
121 return 'Hello %s (%s times)!' % (name, times)
122
123
124 application = ObjectApplication()
125 application.root = BlogController
126 application.root.admin = AdminController
127
128
129 if __name__ == '__main__':
130 from werkzeug import run_simple
131 run_simple('localhost', 4000, application, use_reloader=True)
0 This directory contains modules that have code but that are
1 not excutable. For example routing definitions to play around
2 in the python interactive prompt.
0 from werkzeug.routing import Map, Rule, Subdomain, Submount, EndpointPrefix
1
2 m = Map([
3 # Static URLs
4 EndpointPrefix('static/', [
5 Rule('/', endpoint='index'),
6 Rule('/about', endpoint='about'),
7 Rule('/help', endpoint='help'),
8 ]),
9 # Knowledge Base
10 Subdomain('kb', [EndpointPrefix('kb/', [
11 Rule('/', endpoint='index'),
12 Submount('/browse', [
13 Rule('/', endpoint='browse'),
14 Rule('/<int:id>/', defaults={'page': 1}, endpoint='browse'),
15 Rule('/<int:id>/<int:page>', endpoint='browse')
16 ])
17 ])])
18 ])
0 # -*- coding: utf-8 -*-
1 """
2 plnt
3 ~~~~
4
5 Noun. plnt (plant) -- a planet application that sounds like a herb.
6
7 :copyright: Copyright 2007 by Armin Ronacher.
8 :license: BSD.
9 """
10 from plnt.webapp import Plnt
Binary diff not shown
0 # -*- coding: utf-8 -*-
1 """
2 plnt.database
3 ~~~~~~~~~~~~~
4
5 The database definitions for the planet.
6
7 :copyright: Copyright 2007 by Armin Ronacher.
8 :license: BSD.
9 """
10 from sqlalchemy import MetaData, Table, Column, ForeignKey, Boolean, \
11 Integer, String, DateTime
12 from sqlalchemy.orm import dynamic_loader, scoped_session, create_session
13 from plnt.utils import application, local_manager
14
15
16 def new_db_session():
17 return create_session(application.database_engine, autoflush=True,
18 transactional=True)
19
20 metadata = MetaData()
21 Session = scoped_session(new_db_session, local_manager.get_ident)
22
23
24 blog_table = Table('blogs', metadata,
25 Column('id', Integer, primary_key=True),
26 Column('name', String(120)),
27 Column('description', String),
28 Column('url', String(200)),
29 Column('feed_url', String(250))
30 )
31
32 entry_table = Table('entries', metadata,
33 Column('id', Integer, primary_key=True),
34 Column('blog_id', Integer, ForeignKey('blogs.id')),
35 Column('guid', String(200), unique=True),
36 Column('title', String(140)),
37 Column('url', String(200)),
38 Column('text', String),
39 Column('pub_date', DateTime),
40 Column('last_update', DateTime)
41 )
42
43
44 class Blog(object):
45
46 def __init__(self, name, url, feed_url, description=u''):
47 self.name = name
48 self.url = url
49 self.feed_url = feed_url
50 self.description = description
51
52 def __repr__(self):
53 return '<%s %r>' % (self.__class__.__name__, self.url)
54
55
56 class Entry(object):
57
58 def __repr__(self):
59 return '<%s %r>' % (self.__class__.__name__, self.guid)
60
61
62 Session.mapper(Entry, entry_table)
63 Session.mapper(Blog, blog_table, properties=dict(
64 entries=dynamic_loader(Entry, backref='blog')
65 ))
Binary diff not shown
0 body {
1 font-family: 'Luxi Sans', 'Lucida Sans', 'Verdana', sans-serif;
2 margin: 1em;
3 padding: 0;
4 background-color: #BDE1EC;
5 color: #0B2B35;
6 }
7
8 a {
9 color: #50ACC4;
10 }
11
12 a:hover {
13 color: #0B2B35;
14 }
15
16 div.header {
17 display: block;
18 margin: -1em -1em 0 -1em;
19 padding: 1em;
20 background-color: #0B2B35;
21 color: white;
22 }
23
24 div.header h1 {
25 font-family: 'Georgia', serif;
26 margin: 0;
27 font-size: 1.8em;
28 }
29
30 div.header blockquote {
31 margin: 0;
32 padding: 0.5em 0 0 1em;
33 font-size: 0.9em;
34 }
35
36 div.footer {
37 margin: 1em -1em -1em -1em;
38 padding: 0.5em;
39 color: #F3F7F8;
40 background-color: #1F6070;
41 }
42
43 div.footer p {
44 margin: 0;
45 padding: 0;
46 font-size: 0.8em;
47 text-align: right;
48 }
49
50 ul.navigation {
51 float: right;
52 padding: 0.7em 1em 0.7em 1em;
53 background-color: #F3F7F8;
54 border: 1px solid #85CADB;
55 border-right-color: #50ACC4;
56 border-bottom-color: #50ACC4;
57 list-style: none;
58 }
59
60 ul.navigation li {
61 padding: 0.3em 0 0.3em 0;
62 }
63
64 ul.navigation li a {
65 color: #0B2B35;
66 }
67
68 ul.navigation li a:hover {
69 color: #50ACC4;
70 }
71
72 div.pagination {
73 margin: 0.5em 0 0.5em 0;
74 padding: 0.7em;
75 text-align: center;
76 max-width: 50em;
77 background-color: white;
78 border: 1px solid #B1CDD4;
79 }
80
81 div.day, div.page {
82 max-width: 50em;
83 background-color: white;
84 border: 1px solid #50ACC4;
85 margin: 1em 0 1em 0;
86 padding: 0.7em;
87 }
88
89 div.day h2, div.page h2 {
90 margin: 0 0 0.5em 0;
91 padding: 0;
92 color: black;
93 font-size: 1.7em;
94 }
95
96 div.page p {
97 margin: 0.7em 1em 0.7em 1em;
98 line-height: 1.5em;
99 }
100
101 div.day div.entry {
102 margin: 0.5em 0.25em 0.5em 1em;
103 padding: 1em;
104 background-color: #F3F7F8;
105 border: 1px solid #85CADB;
106 border-left-color: #50ACC4;
107 border-top-color: #50ACC4;
108 }
109
110 div.day div.entry h3 {
111 margin: 0;
112 padding: 0;
113 }
114
115 div.day div.entry h3 a {
116 color: #1C6D81;
117 text-decoration: none;
118 }
119
120 div.day div.entry p.meta {
121 color: #666;
122 font-size: 0.85em;
123 margin: 0.3em 0 0.6em 0;
124 }
125
126 div.day div.entry p.meta a {
127 color: #666;
128 }
129
130 div.day div.entry div.text {
131 padding: 0 0 0 0.5em;
132 }
0 # -*- coding: utf-8 -*-
1 """
2 plnt.sync
3 ~~~~~~~~~
4
5 Does the synchronization. Called by "manage-plnt.py sync"
6
7 :copyright: 2007 by Benjamin Wiegand, Marian Sigler, Armin Ronacher.
8 :license: BSD.
9 """
10 import sys
11 import feedparser
12 from time import time
13 from datetime import datetime
14 from werkzeug import escape
15 from plnt.database import Blog, Entry, Session
16 from plnt.utils import strip_tags, nl2p
17
18
19 HTML_MIMETYPES = set(['text/html', 'application/xhtml+xml'])
20
21
22 def sync():
23 """
24 Performs a synchronization. Articles that are already syncronized aren't
25 touched anymore.
26 """
27 for blog in Blog.query.all():
28 # parse the feed. feedparser.parse will never given an exception
29 # but the bozo bit might be defined.
30 feed = feedparser.parse(blog.feed_url)
31 blog_author = feed.get('author') or blog.name
32 blog_author_detail = feed.get('author_detail')
33
34 for entry in feed.entries:
35 # get the guid. either the id if specified, otherwise the link.
36 # if none is available we skip the entry.
37 guid = entry.get('id') or entry.get('link')
38 if not guid:
39 continue
40
41 # get an old entry for the guid to check if we need to update
42 # or recreate the item
43 old_entry = Entry.query.filter_by(guid=guid).first()
44
45 # get title, url and text. skip if no title or no text is
46 # given. if the link is missing we use the blog link.
47 if 'title_detail' in entry:
48 title = entry.title_detail.get('value') or ''
49 if entry.title_detail.get('type') in HTML_MIMETYPES:
50 title = strip_tags(title)
51 else:
52 title = escape(title)
53 else:
54 title = entry.get('title')
55 url = entry.get('link') or blog.blog_url
56 text = 'content' in entry and entry.content[0] or \
57 entry.get('summary_detail')
58
59 if not title or not text:
60 continue
61
62 # if we have an html text we use that, otherwise we HTML
63 # escape the text and use that one. We also handle XHTML
64 # with our tag soup parser for the moment.
65 if text.get('type') not in HTML_MIMETYPES:
66 text = escape(nl2p(text.get('value') or ''))
67 else:
68 text = text.get('value') or ''
69
70 # no text? continue
71 if not text.strip():
72 continue
73
74 # get the pub date and updated date. This is rather complex
75 # because different feeds do different stuff
76 pub_date = entry.get('published_parsed') or \
77 entry.get('created_parsed') or \
78 entry.get('date_parsed')
79 updated = entry.get('updated_parsed') or pub_date
80 pub_date = pub_date or updated
81
82 # if we don't have a pub_date we skip.
83 if not pub_date:
84 continue
85
86 # convert the time tuples to datetime objects.
87 pub_date = datetime(*pub_date[:6])
88 updated = datetime(*updated[:6])
89 if old_entry and updated <= old_entry.last_update:
90 continue
91
92 # create a new entry object based on the data collected or
93 # update the old one.
94 entry = old_entry or Entry()
95 entry.blog = blog
96 entry.guid = guid
97 entry.title = title
98 entry.url = url
99 entry.text = text
100 entry.pub_date = pub_date
101 entry.last_update = updated
102
103 Session().commit()
0 {% extends "layout.html" %}
1 {% block body %}
2 <div class="page">
3 <h2>About Plnt</h2>
4 <p>
5 Plnt is a small example application written using the
6 <a href="http://werkzeug.pocoo.org/">Werkzeug</a> WSGI toolkit,
7 the <a href="http://jinja.pocoo.org/">Jinja</a> template language,
8 the <a href="http://sqlalchemy.org/">SQLAlchemy</a> database abstraction
9 layer and ORM and last but not least the awesome
10 <a href="http://feedparser.org/">feedparser</a> library.
11 </p>
12 <p>
13 It's one of the example applications developed to show some of the
14 features werkzeug provides and could be the base of a real planet
15 software.
16 </p>
17 </div>
18 {% endblock %}
0 {% extends "layout.html" %}
1 {% block body %}
2 {% for day in days %}
3 <div class="day">
4 <h2>{{ day.date.strftime('%d %B %Y') }}</h2>
5 {%- for entry in day.entries %}
6 <div class="entry">
7 <h3><a href="{{ entry.url|e }}">{{ entry.title }}</a></h3>
8 <p class="meta">by <a href="{{ entry.blog.url|e }}">{{ entry.blog.name|e }}</a>
9 at {{ entry.pub_date.strftime('%H:%m') }}</p>
10 <div class="text">{{ entry.text }}</div>
11 </div>
12 {%- endfor %}
13 </div>
14 {%- endfor %}
15
16 {% if pagination.pages > 1 %}
17 <div class="pagination">
18 {%- if pagination.has_previous %}<a href="{{ pagination.previous }}">&laquo; Previous</a>
19 {%- else %}<span class="inactive">&laquo; Previous</span>{% endif %}
20 | {{ pagination.page }} |
21 {% if pagination.has_next %}<a href="{{ pagination.next }}">Next &raquo;</a>
22 {%- else %}<span class="inactive">Next &raquo;</span>{% endif %}
23 </div>
24 {% endif %}
25 {% endblock %}
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
1 "http://www.w3.org/TR/html4/strict.dtd">
2 <title>Plnt Planet</title>
3 <link rel="stylesheet" type="text/css" href="{{ url_for('shared', file='style.css') }}">
4
5 <div class="header">
6 <h1>Plnt Planet</h1>
7 <blockquote>This is the Plnt Planet Werkzeug Example Application</blockquote>
8 </div>
9
10 <ul class="navigation">
11 <li><a href="{{ url_for('index') }}">Planet</a></li>
12 <li><a href="{{ url_for('about') }}">About</a></li>
13 </ul>
14
15 <div class="contents">
16 {% block body %}{% endblock %}
17 </div>
18
19 <div class="footer">
20 <p>This is a Plnt Planet, an example planet application written using
21 <a href="http://werkzeug.pocoo.org/">Werkzeug</a>.</p>
22 </div>
0 # -*- coding: utf-8 -*-
1 """
2 plnt.utils
3 ~~~~~~~~~~
4
5 The planet utilities.
6
7 :copyright: Copyright 2007 by Armin Ronacher.
8 :license: BSD.
9 """
10 import re
11 from os import path
12 from jinja import Environment, FileSystemLoader
13 from werkzeug import Response, Local, LocalManager, url_encode, \
14 url_quote, cached_property
15 from werkzeug.routing import Map, Rule
16
17
18 # context locals. these two objects are use by the application to
19 # bind objects to the current context. A context is defined as the
20 # current thread and the current greenlet if there is greenlet support.
21 # the `get_request` and `get_application` functions look up the request
22 # and application objects from this local manager.
23 local = Local()
24 local_manager = LocalManager([local])
25
26
27 # proxy objects
28 request = local('request')
29 application = local('application')
30 url_adapter = local('url_adapter')
31
32
33 # let's use jinja for templates this time
34 template_path = path.join(path.dirname(__file__), 'templates')
35 jinja_env = Environment(loader=FileSystemLoader(template_path))
36
37
38 # the collected url patterns
39 url_map = Map([Rule('/shared/<path:file>', endpoint='shared')])
40 endpoints = {}
41
42
43 _par_re = re.compile(r'\n{2,}')
44 _entity_re = re.compile(r'&([^;]+);')
45 _striptags_re = re.compile(r'(<!--.*-->|<[^>]*>)')
46
47 from htmlentitydefs import name2codepoint
48 html_entities = name2codepoint.copy()
49 html_entities['apos'] = 39
50 del name2codepoint
51
52
53 def expose(url_rule, endpoint=None, **kwargs):
54 """Expose this function to the web layer."""
55 def decorate(f):
56 e = endpoint or f.__name__
57 endpoints[e] = f
58 url_map.add(Rule(url_rule, endpoint=e, **kwargs))
59 return f
60 return decorate
61
62
63 def render_template(template_name, **context):
64 """Render a template into a response."""
65 tmpl = jinja_env.get_template(template_name)
66 context['url_for'] = url_for
67 return Response(tmpl.render(context), mimetype='text/html')
68
69
70 def nl2p(s):
71 """Add paragraphs to a text."""
72 return u'\n'.join(u'<p>%s</p>' % p for p in _par_re.split(s))
73
74
75 def url_for(endpoint, **kw):
76 """Simple function for URL generation."""
77 return url_adapter.build(endpoint, kw)
78
79
80 def strip_tags(s):
81 """Resolve HTML entities and remove tags from a string."""
82 def handle_match(m):
83 name = m.group(1)
84 if name in html_entities:
85 return unichr(html_entities[name])
86 if name[:2] in ('#x', '#X'):
87 try:
88 return unichr(int(name[2:], 16))
89 except ValueError:
90 return u''
91 elif name.startswith('#'):
92 try:
93 return unichr(int(name[1:]))
94 except ValueError:
95 return u''
96 return u''
97 return _entity_re.sub(handle_match, _striptags_re.sub('', s))
98
99
100 class Pagination(object):
101 """
102 Paginate a SQLAlchemy query object.
103 """
104
105 def __init__(self, query, per_page, page, endpoint):
106 self.query = query
107 self.per_page = per_page
108 self.page = page
109 self.endpoint = endpoint
110
111 @cached_property
112 def entries(self):
113 return self.query.offset((self.page - 1) * self.per_page) \
114 .limit(self.per_page).all()
115
116 @cached_property
117 def count(self):
118 return self.query.count()
119
120 has_previous = property(lambda x: x.page > 1)
121 has_next = property(lambda x: x.page < x.pages)
122 previous = property(lambda x: url_for(x.endpoint, page=x.page - 1))
123 next = property(lambda x: url_for(x.endpoint, page=x.page + 1))
124 pages = property(lambda x: max(0, x.count - 1) // x.per_page + 1)
Binary diff not shown
0 # -*- coding: utf-8 -*-
1 """
2 plnt.views
3 ~~~~~~~~~~
4
5 Display the aggregated feeds.
6
7 :copyright: Copyright 2007 by Armin Ronacher.
8 :license: BSD.
9 """
10 from datetime import datetime, date
11 from plnt.database import Blog, Entry
12 from plnt.utils import Pagination, expose, render_template
13
14
15 #: number of items per page
16 PER_PAGE = 30
17
18
19 @expose('/', defaults={'page': 1})
20 @expose('/page/<int:page>')
21 def index(request, page):
22 """Show the index page or any an offset of it."""
23 days = []
24 days_found = set()
25 query = Entry.query.order_by(Entry.pub_date.desc())
26 pagination = Pagination(query, PER_PAGE, page, 'index')
27 for entry in pagination.entries:
28 day = date(*entry.pub_date.timetuple()[:3])
29 if day not in days_found:
30 days_found.add(day)
31 days.append({'date': day, 'entries': []})
32 days[-1]['entries'].append(entry)
33 return render_template('index.html', days=days, pagination=pagination)
34
35
36 @expose('/about')
37 def about(request):
38 """Show the about page, so that we have another view func ;-)"""
39 return render_template('about.html')
Binary diff not shown
0 # -*- coding: utf-8 -*-
1 """
2 plnt.webapp
3 ~~~~~~~~~~~
4
5 The web part of the planet.
6
7 :copyright: Copyright 2007 by Armin Ronacher.
8 :license: BSD.
9 """
10 from os import path
11 from sqlalchemy import create_engine
12 from werkzeug import SharedDataMiddleware, ClosingIterator, Request
13 from werkzeug.exceptions import HTTPException, NotFound
14 from plnt.utils import local, local_manager, url_map, endpoints
15 from plnt.database import Session, metadata
16
17 # import the views module because it contains setup code
18 import plnt.views
19
20 #: path to shared data
21 SHARED_DATA = path.join(path.dirname(__file__), 'shared')
22
23
24 class Plnt(object):
25
26 def __init__(self, database_uri):
27 self.database_engine = create_engine(database_uri)
28
29 self._dispatch = local_manager.middleware(self.dispatch_request)
30 self._dispatch = SharedDataMiddleware(self._dispatch, {
31 '/shared': SHARED_DATA
32 })
33
34 def init_database(self):
35 metadata.create_all(self.database_engine)
36
37 def bind_to_context(self):
38 local.application = self
39
40 def dispatch_request(self, environ, start_response):
41 self.bind_to_context()
42 local.request = request = Request(environ, start_response)
43 local.url_adapter = adapter = url_map.bind_to_environ(environ)
44 try:
45 endpoint, values = adapter.match(request.path)
46 response = endpoints[endpoint](request, **values)
47 except HTTPException, e:
48 response = e
49 return ClosingIterator(response(environ, start_response),
50 Session.remove)
51
52 def __call__(self, environ, start_response):
53 return self._dispatch(environ, start_response)
Binary diff not shown
(New empty file)
0 from sqlalchemy import create_engine
1 from werkzeug import Request, SharedDataMiddleware, ClosingIterator
2 from werkzeug.exceptions import HTTPException, NotFound
3 from shorty.utils import STATIC_PATH, Session, local, local_manager, \
4 metadata, url_map
5
6 import shorty.models
7 from shorty import views
8
9
10 class Shorty(object):
11
12 def __init__(self, db_uri):
13 local.application = self
14 self.database_engine = create_engine(db_uri, convert_unicode=True)
15
16 self.dispatch = SharedDataMiddleware(self.dispatch, {
17 '/static': STATIC_PATH
18 })
19
20 def init_database(self):
21 metadata.create_all(self.database_engine)
22
23 def dispatch(self, environ, start_response):
24 local.application = self
25 request = Request(environ)
26 local.url_adapter = adapter = url_map.bind_to_environ(environ)
27 try:
28 endpoint, values = adapter.match()
29 handler = getattr(views, endpoint)
30 response = handler(request, **values)
31 except NotFound, e:
32 response = views.not_found(request)
33 response.status_code = 404
34 except HTTPException, e:
35 response = e
36 return ClosingIterator(response(environ, start_response),
37 [Session.remove, local_manager.cleanup])
38
39 def __call__(self, environ, start_response):
40 return self.dispatch(environ, start_response)
0 from datetime import datetime
1 from sqlalchemy import Table, Column, String, Boolean, DateTime
2 from shorty.utils import Session, metadata, url_for, get_random_uid
3
4 url_table = Table('urls', metadata,
5 Column('uid', String(140), primary_key=True),
6 Column('target', String(500)),
7 Column('added', DateTime),
8 Column('public', Boolean)
9 )
10
11 class URL(object):
12
13 def __init__(self, target, public=True, uid=None, added=None):
14 self.target = target
15 self.public = public
16 self.added = added or datetime.utcnow()
17 if not uid:
18 while 1:
19 uid = get_random_uid()
20 if not URL.query.get(uid):
21 break
22 self.uid = uid
23
24 @property
25 def short_url(self):
26 return url_for('link', uid=self.uid, _external=True)
27
28 def __repr__(self):
29 return '<URL %r>' % self.uid
30
31 Session.mapper(URL, url_table)
Binary diff not shown
0 body {
1 background-color: #333;
2 font-family: 'Lucida Sans', 'Verdana', sans-serif;
3 font-size: 16px;
4 margin: 3em 0 3em 0;
5 padding: 0;
6 text-align: center;
7 }
8
9 a {
10 color: #0C4850;
11 }
12
13 a:hover {
14 color: #1C818F;
15 }
16
17 h1 {
18 width: 500px;
19 background-color: #24C0CE;
20 text-align: center;
21 font-size: 3em;
22 margin: 0 auto 0 auto;
23 padding: 0;
24 }
25
26 h1 a {
27 display: block;
28 padding: 0.3em;
29 color: #fff;
30 text-decoration: none;
31 }
32
33 h1 a:hover {
34 color: #ADEEF7;
35 background-color: #0E8A96;
36 }
37
38 div.footer {
39 margin: 0 auto 0 auto;
40 font-size: 13px;
41 text-align: right;
42 padding: 10px;
43 width: 480px;
44 background-color: #004C63;
45 color: white;
46 }
47
48 div.footer a {
49 color: #A0E9FF;
50 }
51
52 div.body {
53 margin: 0 auto 0 auto;
54 padding: 20px;
55 width: 460px;
56 background-color: #98CE24;
57 color: black;
58 }
59
60 div.body h2 {
61 margin: 0 0 0.5em 0;
62 text-align: center;
63 }
64
65 div.body input {
66 margin: 0.2em 0 0.2em 0;
67 font-family: 'Lucida Sans', 'Verdana', sans-serif;
68 font-size: 20px;
69 background-color: #CCEB98;
70 color: black;
71 }
72
73 div.body #url {
74 width: 400px;
75 }
76
77 div.body #alias {
78 width: 300px;
79 margin-right: 10px;
80 }
81
82 div.body #submit {
83 width: 90px;
84 }
85
86 div.body p {
87 margin: 0;
88 padding: 0.2em 0 0.2em 0;
89 }
90
91 div.body ul {
92 margin: 1em 0 1em 0;
93 padding: 0;
94 list-style: none;
95 }
96
97 div.error {
98 margin: 1em 0 1em 0;
99 border: 2px solid #AC0202;
100 background-color: #9E0303;
101 font-weight: bold;
102 color: white;
103 }
104
105 div.pagination {
106 font-size: 13px;
107 }
0 {% extends 'layout.html' %}
1 {% block body %}
2 <h2>Shortened URL</h2>
3 <p>
4 The URL {{ url.target|urlize(40, true) }}
5 was shortened to {{ url.short_url|urlize }}.
6 </p>
7 {% endblock %}
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
1 "http://www.w3.org/TR/html4/strict.dtd">
2 <html>
3 <head>
4 <title>Shorty</title>
5 <link rel="stylesheet" type="text/css" href="{{ url_for('static', file='style.css') }}">
6 </head>
7 <body>
8 <h1><a href="{{ url_for('new') }}">Shorty</a></h1>
9 <div class="body">{% block body %}{% endblock %}</div>
10 <div class="footer">
11 <a href="{{ url_for('new') }}">new</a> |
12 <a href="{{ url_for('list') }}">list</a> |
13 use shorty for good, not for evil
14 </div>
15 </body>
16 </html>
0 {% extends 'layout.html' %}
1 {% block body %}
2 <h2>List of URLs</h2>
3 <ul>
4 {%- for url in pagination.entries %}
5 <li><a href="{{ url.short_url|e }}">{{ url.uid|e }}</a> &raquo;
6 <small>{{ url.target|urlize(38, true) }}</small></li>
7 {%- else %}
8 <li><em>no URLs shortened yet</em></li>
9 {%- endfor %}
10 </ul>
11 <div class="pagination">
12 {%- if pagination.has_previous %}<a href="{{ pagination.previous }}">&laquo; Previous</a>
13 {%- else %}<span class="inactive">&laquo; Previous</span>{% endif %}
14 | {{ pagination.page }} |
15 {% if pagination.has_next %}<a href="{{ pagination.next }}">Next &raquo;</a>
16 {%- else %}<span class="inactive">Next &raquo;</span>{% endif %}
17 </div>
18 {% endblock %}
0 {% extends 'layout.html' %}
1 {% block body %}
2 <h2>Create a Shorty-URL!</h2>
3 {% if error %}<div class="error">{{ error }}</div>{% endif -%}
4 <form action="" method="post">
5 <p>Enter the URL you want to shorten</p>
6 <p><input type="text" name="url" id="url" value="{{ url|e(true) }}"></p>
7 <p>Optionally you can give the URL a memorable name</p>
8 <p><input type="text" id="alias" name="alias">{#
9 #}<input type="submit" id="submit" value="Do!"></p>
10 <p><input type="checkbox" name="private" id="private">
11 <label for="private">make this URL private, so don't list it</label></p>
12 </form>
13 {% endblock %}
0 {% extends 'layout.html' %}
1 {% block body %}
2 <h2>Page Not Found</h2>
3 <p>
4 The page you have requested does not exist on this server. What about
5 <a href="{{ url_for('new') }}">adding a new URL</a>?
6 </p>
7 {% endblock %}
0 from os import path
1 from urlparse import urlparse
2 from random import sample, randrange
3 from jinja import Environment, FileSystemLoader
4 from werkzeug import Response, Local, LocalManager, cached_property
5 from werkzeug.routing import Map, Rule
6 from sqlalchemy import MetaData
7 from sqlalchemy.orm import create_session, scoped_session
8
9
10 TEMPLATE_PATH = path.join(path.dirname(__file__), 'templates')
11 STATIC_PATH = path.join(path.dirname(__file__), 'static')
12 ALLOWED_SCHEMES = frozenset(['http', 'https', 'ftp', 'ftps'])
13 URL_CHARS = 'abcdefghijkmpqrstuvwxyzABCDEFGHIJKLMNPQRST23456789'
14
15 local = Local()
16 local_manager = LocalManager([local])
17 application = local('application')
18
19 metadata = MetaData()
20 url_map = Map([Rule('/static/<file>', endpoint='static', build_only=True)])
21
22 Session = scoped_session(lambda: create_session(application.database_engine,
23 transactional=True), local_manager.get_ident)
24 jinja_env = Environment(loader=FileSystemLoader(TEMPLATE_PATH))
25
26
27 def expose(rule, **kw):
28 def decorate(f):
29 kw['endpoint'] = f.__name__
30 url_map.add(Rule(rule, **kw))
31 return f
32 return decorate
33
34 def url_for(endpoint, _external=False, **values):
35 return local.url_adapter.build(endpoint, values, force_external=_external)
36 jinja_env.globals['url_for'] = url_for
37
38 def render_template(template, **context):
39 return Response(jinja_env.get_template(template).render(**context),
40 mimetype='text/html')
41
42 def validate_url(url):
43 return urlparse(url)[0] in ALLOWED_SCHEMES
44
45 def get_random_uid():
46 return ''.join(sample(URL_CHARS, randrange(3, 9)))
47
48
49 class Pagination(object):
50
51 def __init__(self, query, per_page, page, endpoint):
52 self.query = query
53 self.per_page = per_page
54 self.page = page
55 self.endpoint = endpoint
56
57 @cached_property
58 def count(self):
59 return self.query.count()
60
61 @cached_property
62 def entries(self):
63 return self.query.offset((self.page - 1) * self.per_page) \
64 .limit(self.per_page).all()
65
66 has_previous = property(lambda x: x.page > 1)
67 has_next = property(lambda x: x.page < x.pages)
68 previous = property(lambda x: url_for(x.endpoint, page=x.page - 1))
69 next = property(lambda x: url_for(x.endpoint, page=x.page + 1))
70 pages = property(lambda x: max(0, x.count - 1) // x.per_page + 1)
Binary diff not shown
0 from werkzeug import redirect
1 from werkzeug.exceptions import NotFound
2 from shorty.utils import Session, Pagination, render_template, expose, \
3 validate_url, url_for
4 from shorty.models import URL
5
6 @expose('/')
7 def new(request):
8 error = url = ''
9 if request.method == 'POST':
10 url = request.form.get('url')
11 alias = request.form.get('alias')
12 if not validate_url(url):
13 error = "I'm sorry but you cannot shorten this URL."
14 elif alias:
15 if len(alias) > 140:
16 error = 'Your alias is too long'
17 elif '/' in alias:
18 error = 'Your alias might not include a slash'
19 elif URL.query.get(alias):
20 error = 'The alias you have requested exists already'
21 if not error:
22 uid = URL(url, 'private' not in request.form, alias).uid
23 Session.commit()
24 return redirect(url_for('display', uid=uid))
25 return render_template('new.html', error=error, url=url)
26
27 @expose('/display/<uid>')
28 def display(request, uid):
29 url = URL.query.get(uid)
30 if not url:
31 raise NotFound()
32 return render_template('display.html', url=url)
33
34 @expose('/u/<uid>')
35 def link(request, uid):
36 url = URL.query.get(uid)
37 if not url:
38 raise NotFound()
39 return redirect(url.target, 301)
40
41 @expose('/list/', defaults={'page': 1})
42 @expose('/list/<int:page>')
43 def list(request, page):
44 query = URL.query.filter_by(public=True)
45 pagination = Pagination(query, 30, page, 'list')
46 if pagination.page > 1 and not pagination.entries:
47 raise NotFound()
48 return render_template('list.html', pagination=pagination)
49
50 def not_found(request):
51 return render_template('not_found.html')
Binary diff not shown
0 # -*- coding: utf-8 -*-
1 """
2 simplewiki
3 ~~~~~~~~~~
4
5 Very simple wiki application based on Genshi, Werkzeug and SQLAlchemy.
6 Additionally the creoleparser is used for the wiki markup.
7
8 This example application requires Python 2.4 or higher, primarly beacause
9 the creoleparser requires Python 2.4 . Additionally the code uses some
10 decorators or generator expressions.
11
12
13 :copyright: Copyright 2007 by Armin Ronacher.
14 :license: BSD.
15 """
16 from simplewiki.application import SimpleWiki
0 # -*- coding: utf-8 -*-
1 """
2 simplewiki.actions
3 ~~~~~~~~~~~~~~~~~~
4
5 The per page actions. The actions are defined in the URL with the
6 `action` parameter and directly dispatched to the functions in this
7 module. In the module the actions are prefixed with 'on_', so be
8 careful not to name any other objects in the module with the same
9 prefix unless you want to act them as actions.
10
11 :copyright: Copyright 2007 by Armin Ronacher.
12 :license: BSD.
13 """
14 from difflib import unified_diff
15 from simplewiki.utils import Response, generate_template, parse_creole, \
16 href, redirect, format_datetime
17 from simplewiki.database import RevisionedPage, Page, Revision, session
18
19
20 def on_show(request, page_name):
21 """Displays the page the user requests."""
22 revision_id = request.args.get('rev', type=int)
23 query = RevisionedPage.query.filter_by(name=page_name)
24 if revision_id:
25 query = query.filter_by(revision_id=revision_id)
26 revision_requested = True
27 else:
28 query = query.order_by(RevisionedPage.revision_id.desc())
29 revision_requested = False
30 page = query.first()
31 if page is None:
32 return page_missing(request, page_name, revision_requested)
33 return Response(generate_template('action_show.html',
34 page=page
35 ))
36
37
38 def on_edit(request, page_name):
39 """Edit the current revision of a page."""
40 change_note = error = ''
41 revision = Revision.query.filter(
42 (Page.name == page_name) &
43 (Page.page_id == Revision.page_id)
44 ).order_by(Revision.revision_id.desc()).first()
45 if revision is None:
46 page = None
47 else:
48 page = revision.page
49
50 if request.method == 'POST':
51 text = request.form.get('text')
52 if request.form.get('cancel') or \
53 revision and revision.text == text:
54 return redirect(href(page.name))
55 elif not text:
56 error = 'You cannot save empty revisions.'
57 else:
58 change_note = request.form.get('change_note', '')
59 if page is None:
60 page = Page(page_name)
61 revision = Revision(page, text, change_note)
62 session.commit()
63 return redirect(href(page.name))
64
65 return Response(generate_template('action_edit.html',
66 revision=revision,
67 page=page,
68 new=page is None,
69 page_name=page_name,
70 change_note=change_note,
71 error=error
72 ))
73
74
75 def on_log(request, page_name):
76 """Show the list of recent changes."""
77 page = Page.query.filter_by(name=page_name).first()
78 if page is None:
79 return page_missing(request, page_name, False)
80 return Response(generate_template('action_log.html',
81 page=page
82 ))
83
84
85 def on_diff(request, page_name):
86 """Show the diff between two revisions."""
87 old = request.args.get('old', type=int)
88 new = request.args.get('new', type=int)
89 error = ''
90 diff = page = old_rev = new_rev = None
91
92 if not (old and new):
93 error = 'No revisions specified.'
94 else:
95 revisions = dict((x.revision_id, x) for x in Revision.query.filter(
96 (Revision.revision_id.in_((old, new))) &
97 (Revision.page_id == Page.page_id) &
98 (Page.name == page_name)
99 ))
100 if len(revisions) != 2:
101 error = 'At least one of the revisions requested ' \
102 'does not exist.'
103 else:
104 new_rev = revisions[new]
105 old_rev = revisions[old]
106 page = old_rev.page
107 diff = unified_diff(
108 (old_rev.text + '\n').splitlines(True),
109 (new_rev.text + '\n').splitlines(True),
110 page.name, page.name,
111 format_datetime(old_rev.timestamp),
112 format_datetime(new_rev.timestamp),
113 3
114 )
115
116 return Response(generate_template('action_diff.html',
117 error=error,
118 old_revision=old_rev,
119 new_revision=new_rev,
120 page=page,
121 diff=diff
122 ))
123
124
125 def on_revert(request, page_name):
126 """Revert an old revision."""
127 rev_id = request.args.get('rev', type=int)
128
129 old_revision = page = None
130 error = 'No such revision'
131
132 if request.method == 'POST' and request.form.get('cancel'):
133 return redirect(href(page_name))
134
135 if rev_id:
136 old_revision = Revision.query.filter(
137 (Revision.revision_id == rev_id) &
138 (Revision.page_id == Page.page_id) &
139 (Page.name == page_name)
140 ).first()
141 if old_revision:
142 new_revision = Revision.query.filter(
143 (Revision.page_id == Page.page_id) &
144 (Page.name == page_name)
145 ).order_by(Revision.revision_id.desc()).first()
146 if old_revision == new_revision:
147 error = 'You tried to revert the current active ' \
148 'revision.'
149 elif old_revision.text == new_revision.text:
150 error = 'There are no changes between the current ' \
151 'revision and the revision you want to ' \
152 'restore.'
153 else:
154 error = ''
155 page = old_revision.page
156 if request.method == 'POST':
157 change_note = request.form.get('change_note', '')
158 change_note = 'revert' + (change_note and ': ' +
159 change_note or '')
160 revision = Revision(page, old_revision.text,
161 change_note)
162 session.commit()
163 return redirect(href(page_name))
164
165 return Response(generate_template('action_revert.html',
166 error=error,
167 old_revision=old_revision,
168 page=page
169 ))
170
171
172 def page_missing(request, page_name, revision_requested, protected=False):
173 """Displayed if page or revision does not exist."""
174 return Response(generate_template('page_missing.html',
175 page_name=page_name,
176 revision_requested=revision_requested,
177 protected=protected
178 ), status=404)
179
180
181 def missing_action(request, action):
182 """Displayed if a user tried to access a action that does not exist."""
183 return Response(generate_template('missing_action.html',
184 action=action
185 ), status=404)
0 # -*- coding: utf-8 -*-
1 """
2 simplewiki.application
3 ~~~~~~~~~~~~~~~~~~~~~~
4
5 This module implements the wiki WSGI application which dispatches
6 requests to specific wiki pages and actions.
7
8
9 :copyright: Copyright 2007 by Armin Ronacher.
10 :license: BSD.
11 """
12 from os import path
13 from sqlalchemy import create_engine
14 from werkzeug import ClosingIterator, SharedDataMiddleware, redirect
15 from simplewiki.utils import Request, Response, local, local_manager, href
16 from simplewiki.database import session, metadata
17 from simplewiki import actions
18 from simplewiki.specialpages import pages, page_not_found
19
20
21 #: path to shared data
22 SHARED_DATA = path.join(path.dirname(__file__), 'shared')
23
24
25 class SimpleWiki(object):
26 """
27 Our central WSGI application.
28 """
29
30 def __init__(self, database_uri):
31 self.database_engine = create_engine(database_uri)
32
33 # apply our middlewares. we apply the middlewars *inside* the
34 # application and not outside of it so that we never lose the
35 # reference to the `SimpleWiki` object.
36 self._dispatch = SharedDataMiddleware(self.dispatch_request, {
37 '/_shared': SHARED_DATA
38 })
39
40 # free the context locals at the end of the request
41 self._dispatch = local_manager.make_middleware(self._dispatch)
42
43 def init_database(self):
44 """Called from the management script to generate the db."""
45 metadata.create_all(bind=self.database_engine)
46
47 def bind_to_context(self):
48 """
49 Useful for the shell. Binds the application to the current active
50 context. It's automatically called by the shell command.
51 """
52 local.application = self
53
54 def dispatch_request(self, environ, start_response):
55 """Dispatch an incoming request."""
56 # set up all the stuff we want to have for this request. That is
57 # creating a request object, propagating the application to the
58 # current context and instanciating the database session.
59 self.bind_to_context()
60 request = Request(environ)
61 request.bind_to_context()
62
63 # get the current action from the url and normalize the page name
64 # which is just the request path
65 action_name = request.args.get('action') or 'show'
66 page_name = u'_'.join([x for x in request.path.strip('/')
67 .split() if x])
68
69 # redirect to the Main_Page if the user requested the index
70 if not page_name:
71 response = redirect(href('Main_Page'))
72
73 # check special pages
74 elif page_name.startswith('Special:'):
75 if page_name[8:] not in pages:
76 response = page_not_found(request, page_name)
77 else:
78 response = pages[page_name[8:]](request)
79
80 # get the callback function for the requested action from the
81 # action module. It's "on_" + the action name. If it doesn't
82 # exists call the missing_action method from the same module.
83 else:
84 action = getattr(actions, 'on_' + action_name, None)
85 if action is None:
86 response = actions.missing_action(request, action_name)
87 else:
88 response = action(request, page_name)
89
90 # make sure the session is removed properly
91 return ClosingIterator(response(environ, start_response),
92 session.remove)
93
94 def __call__(self, environ, start_response):
95 """Just forward a WSGI call to the first internal middleware."""
96 return self._dispatch(environ, start_response)
0 # -*- coding: utf-8 -*-
1 """
2 simplewiki.database
3 ~~~~~~~~~~~~~~~~~~~
4
5 The database.
6
7 :copyright: Copyright 2007 by Armin Ronacher.
8 :license: BSD.
9 """
10 from datetime import datetime
11 from sqlalchemy import Table, Column, Integer, String, DateTime, \
12 ForeignKey, MetaData, join
13 from sqlalchemy.orm import relation, create_session, scoped_session
14 from simplewiki.utils import application, local_manager, parse_creole
15
16
17 # create a global metadata
18 metadata = MetaData()
19
20
21 def new_db_session():
22 """
23 This function creates a new session if there is no session yet for
24 the current context. It looks up the application and if it finds
25 one it creates a session bound to the active database engine in that
26 application. If there is no application bound to the context it
27 raises an exception.
28 """
29 return create_session(application.database_engine, autoflush=True,
30 transactional=True)
31
32
33 # and create a new global session factory. Calling this object gives
34 # you the current active session
35 session = scoped_session(new_db_session, local_manager.get_ident)
36
37
38 # our database tables.
39 page_table = Table('pages', metadata,
40 Column('page_id', Integer, primary_key=True),
41 Column('name', String(60), unique=True)
42 )
43
44 revision_table = Table('revisions', metadata,
45 Column('revision_id', Integer, primary_key=True),
46 Column('page_id', Integer, ForeignKey('pages.page_id')),
47 Column('timestamp', DateTime),
48 Column('text', String),
49 Column('change_note', String(200))
50 )
51
52
53 class Revision(object):
54 """
55 Represents one revision of a page.
56 This is useful for editing particular revision of pages or creating
57 new revisions. It's also used for the diff system and the revision
58 log.
59 """
60
61 def __init__(self, page, text, change_note='', timestamp=None):
62 if isinstance(page, (int, long)):
63 self.page_id = page
64 else:
65 self.page = page
66 self.text = text
67 self.change_note = change_note
68 self.timestamp = timestamp or datetime.utcnow()
69
70 def render(self):
71 """Render the page text into a genshi stream."""
72 return parse_creole(self.text)
73
74 def __repr__(self):
75 return '<%s %r:%r>' % (
76 self.__class__.__name__,
77 self.page_id,
78 self.revision_id
79 )
80
81
82 class Page(object):
83 """
84 Represents a simple page without any revisions. This is for example
85 used in the page index where the page contents are not relevant.
86 """
87
88 def __init__(self, name):
89 self.name = name
90
91 @property
92 def title(self):
93 return self.name.replace('_', ' ')
94
95 def __repr__(self):
96 return '<%s %r>' % (self.__class__.__name__, self.name)
97
98
99 class RevisionedPage(Page, Revision):
100 """
101 Represents a wiki page with a revision. Thanks to multiple inhertiance
102 and the ability of SQLAlchemy to map to joins we can combine `Page` and
103 `Revision` into one class here.
104 """
105
106 def __init__(self):
107 raise TypeError('cannot create WikiPage instances, use the Page and '
108 'Revision classes for data manipulation.')
109
110 def __repr__(self):
111 return '<%s %r:%r>' % (
112 self.__class__.__name__,
113 self.name,
114 self.revision_id
115 )
116
117
118 # setup mappers
119 session.mapper(Revision, revision_table)
120 session.mapper(Page, page_table, properties=dict(
121 revisions=relation(Revision, backref='page',
122 order_by=Revision.revision_id.desc())
123 ))
124 session.mapper(RevisionedPage, join(page_table, revision_table), properties=dict(
125 page_id=[page_table.c.page_id, revision_table.c.page_id],
126 ))
0 body {
1 font-family: 'Luxi Sans', 'Lucida Sans', 'Trebuchet MS', sans-serif;
2 margin: 2em 1em 2em 1em;
3 padding: 0;
4 background: #1C0424;
5 }
6
7 a {
8 color: #6A2F7E;
9 }
10
11 a:hover {
12 color: #3D0F4D;
13 }
14
15 pre {
16 border: 1px solid #ccc;
17 background-color: white;
18 font-family: 'Consolas', 'Monaco', 'Bitstream Vera Sans', monospace;
19 font-size: 0.9em;
20 padding: 0.3em;
21 }
22
23 table {
24 border: 2px solid #ccc;
25 border-collapse: collapse;
26 }
27
28 table td, table th {
29 border: 1px solid #ccc;
30 padding: 0.4em;
31 }
32
33 div.bodywrapper {
34 margin: 0 auto 0 auto;
35 max-width: 50em;
36 background: #F1EBF3;
37 border: 1px solid #4C1068;
38 padding: 0;
39 color: #111;
40 }
41
42 div.header {
43 background-color: #320846;
44 color: white;
45 }
46
47 div.header h1 {
48 margin: 0;
49 padding: 0.4em;
50 font-size: 1.7em;
51 }
52
53 div.header h1 a {
54 text-decoration: none;
55 color: white;
56 }
57
58 div.header h1 a:hover {
59 color: #6A2F7E;
60 }
61
62 div.contents {
63 padding: 1em;
64 margin: 0;
65 border: 1px solid #3D0F4D;
66 }
67
68 div.footer {
69 padding: 0.5em;
70 background: #15031B;
71 color: white;
72 font-size: 0.8em;
73 text-align: right;
74 color: white;
75 }
76
77 div.contents h1, div.contents h2, div.contents h3, div.contents h4,
78 div.contents h5 {
79 margin: 0;
80 padding: 0.3em 0 0.2em 0;
81 color: #3D0F4D;
82 }
83
84 div.contents h1 { font-size: 1.7em; }
85 div.contents h2 { font-size: 1.6em; }
86 div.contents h3 { font-size: 1.4em; }
87 div.contents h4 { font-size: 1.2em; }
88 div.contents h5 { font-size: 1em; }
89
90 div.contents p {
91 margin: 0;
92 padding: 0.3em 0 0.3em 0;
93 line-height: 1.5em;
94 }
95
96 div.contents div.navigation {
97 padding: 0 0 0.3em 0;
98 margin: 0 0 0.3em 0;
99 border-bottom: 1px solid #6A2F7E;
100 font-size: 0.85em;
101 color: #555;
102 }
103
104 div.contents div.navigation a {
105 padding: 0 0.2em 0 0.2em;
106 font-weight: bold;
107 color: #555;
108 }
109
110 div.contents div.navigation a:hover {
111 color: #6A2F7E;
112 }
113
114 div.contents div.navigation a.active {
115 background-color: #ccc;
116 text-decoration: none;
117 }
118
119 div.contents div.page_meta {
120 font-size: 0.7em;
121 color: #555;
122 float: right;
123 }
124
125 textarea {
126 width: 99%;
127 font-family: 'Consolas', 'Monaco', 'Bitstream Vera Sans', monospace;
128 font-size: 0.9em;
129 padding: 0.3em;
130 margin: 0.5em 0 0.5em 0;
131 }
132
133 input {
134 font-family: 'Luxi Sans', 'Lucida Sans', 'Trebuchet MS', sans-serif;
135 }
136
137 table.revisions, table.changes {
138 border-collapse: collapse;
139 border: 1px solid #6A2F7E;
140 background: #fdfdfd;
141 width: 100%;
142 margin: 1em 0 0.5em 0;
143 }
144
145 table.revisions th, table.changes th {
146 background-color: #6A2F7E;
147 color: white;
148 padding: 0.1em 0.6em 0.1em 0.6em;
149 font-size: 0.8em;
150 border: none;
151 }
152
153 table.revisions td, table.changes td {
154 padding: 0.2em 0.5em 0.2em 0.5em;
155 font-size: 0.9em;
156 border: none;
157 }
158
159 table.revisions .timestamp, table.changes .timestamp {
160 text-align: left;
161 width: 10em;
162 }
163
164 table.revisions td.timestamp, table.changes td.timestamp {
165 color: #444;
166 }
167
168 table.revisions .change_note, table.changes .change_note {
169 text-align: left;
170 }
171
172 table.revisions td.change_note, table.changes td.change_note {
173 font-style: italic;
174 }
175
176 table.revisions th.diff input {
177 background-color: #3D0F4D;
178 color: white;
179 border: 1px solid #1C0424;
180 }
181
182 table.revisions .diff {
183 width: 5em;
184 text-align: right;
185 }
186
187 table.revisions .actions {
188 width: 8em;
189 text-align: left;
190 }
191
192 table.revisions td.actions {
193 font-size: 0.75em;
194 }
195
196 table.revisions tr.odd, table.changes tr.odd {
197 background-color: #f7f7f7;
198 }
199
200 pre.udiff {
201 overflow: auto;
202 font-size: 0.75em;
203 }
204
205 div.pagination {
206 font-size: 0.9em;
207 padding: 0.5em 0 0.5em 0;
208 text-align: center;
209 }
0 # -*- coding: utf-8 -*-
1 """
2 simplewiki.specialpages
3 ~~~~~~~~~~~~~~~~~~~~~~~
4
5 This module contains special pages such as the recent changes page.
6
7
8 :copyright: Copyright 2007 by Armin Ronacher.
9 :license: BSD.
10 """
11 from simplewiki.utils import Response, Pagination, generate_template, href
12 from simplewiki.database import RevisionedPage, Page
13 from simplewiki.actions import page_missing
14
15
16
17 def page_index(request):
18 """Index of all pages."""
19 letters = {}
20 for page in Page.query.order_by(Page.name):
21 letters.setdefault(page.name.capitalize()[0], []).append(page)
22 return Response(generate_template('page_index.html',
23 letters=sorted(letters.items())
24 ))
25
26
27 def recent_changes(request):
28 """Display the recent changes."""
29 page = max(1, request.args.get('page', type=int))
30 query = RevisionedPage.query \
31 .order_by(RevisionedPage.revision_id.desc())
32 return Response(generate_template('recent_changes.html',
33 pagination=Pagination(query, 20, page, 'Special:Recent_Changes')
34 ))
35
36
37 def page_not_found(request, page_name):
38 """
39 Displays an error message if a user tried to access
40 a not existing special page.
41 """
42 return page_missing(request, page_name, True)
43
44
45 pages = {
46 'Index': page_index,
47 'Recent_Changes': recent_changes
48 }
0 <?python
1 page_action = 'log'
2 ?>
3 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude"
4 xmlns:py="http://genshi.edgewall.org/"><xi:include href="layout.html" />
5 <head>
6 <title>View Diff</title>
7 </head>
8 <body>
9 <py:if test="not error">
10 <h1>Diff for “<a href="${href(page.name)}">${page.title}</a>”</h1>
11 <p>
12 Below you can see the differences between the revision from
13 <a href="${href(page.name, rev=old_revision.revision_id)}"
14 >${format_datetime(old_revision.timestamp)}</a> and the
15 revision from <a href="${href(page.name, rev=new_revision.revision_id)}"
16 >${format_datetime(new_revision.timestamp)}</a> in unified
17 diff format.
18 </p>
19 <pre class="udiff">${diff}</pre>
20 </py:if>
21 <py:if test="error">
22 <h1>Cannot Display Diff</h1>
23 <p class="error" py:if="error">${error}</p>
24 </py:if>
25 </body>
26 </html>
0 <?python
1 hide_navigation = new
2 page_action = 'edit'
3 ?>
4 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude"
5 xmlns:py="http://genshi.edgewall.org/"><xi:include href="layout.html" />
6 <head>
7 <title>${new and 'Create' or 'Edit'} Page</title>
8 </head>
9 <body>
10 <h1>${new and 'Create' or 'Edit'} “${page.title or page_name}”</h1>
11 <p>
12 You can now ${new and 'create' or 'modify'} the page contents. To
13 format your text you can use <a href="http://www.wikicreole.org/">creole markup</a>.
14 </p>
15 <p class="error" py:if="error">${error}</p>
16 <form action="" method="post">
17 <textarea name="text" rows="15" cols="50">${revision.text}</textarea>
18 <div class="actions">
19 <input type="text" name="change_note" value="${change_note}" size="50" />
20 <input type="submit" value="Save" />
21 <input type="submit" name="cancel" value="Cancel" />
22 </div>
23 </form>
24 </body>
25 </html>
0 <?python
1 page_action = 'log'
2 ?>
3 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude"
4 xmlns:py="http://genshi.edgewall.org/"><xi:include href="layout.html" />
5 <head>
6 <title>Revisions for “${page.title}”</title>
7 </head>
8 <body>
9 <h1>Revisions for “<a href="${href(page.name)}">${page.title}</a>”</h1>
10 <p>
11 In this list you can see all the revisions of the requested page.
12 </p>
13 <form action="${href(page.name)}" method="get">
14 <input type="hidden" name="action" value="diff" />
15 <table class="revisions">
16 <tr>
17 <th class="timestamp">Date</th>
18 <th class="change_note">Change Note</th>
19 <th class="diff"><input type="submit" value="Diff" /></th>
20 <th class="actions">Actions</th>
21 </tr>
22 <tr py:for="idx, revision in enumerate(page.revisions)"
23 class="${idx % 2 == 1 and 'even' or 'odd'}">
24 <td class="timestamp">${format_datetime(revision.timestamp)}</td>
25 <td class="change_note">${revision.change_note}</td>
26 <td class="diff">
27 <input type="radio" name="old" value="${revision.revision_id}"
28 checked="${idx == 1 and 'checked' or None}" />
29 <input type="radio" name="new" value="${revision.revision_id}"
30 checked="${idx == 0 and 'checked' or None}" />
31 </td>
32 <td class="actions">
33 <a href="${href(page.name, rev=revision.revision_id)}">show</a>
34 <py:if test="idx">|
35 <a href="${href(page.name, rev=revision.revision_id,
36 action='revert')}">revert</a>
37 </py:if>
38 </td>
39 </tr>
40 </table>
41 </form>
42 </body>
43 </html>
0 <?python
1 page_action = 'log'
2 ?>
3 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude"
4 xmlns:py="http://genshi.edgewall.org/"><xi:include href="layout.html" />
5 <head>
6 <title>Revert Old Revision</title>
7 </head>
8 <body>
9 <py:if test="not error">
10 <h1>Revert Old Revision of “<a href="${href(page.name)}">${page.title}</a>”</h1>
11 <p>
12 If you want to restore the old revision from
13 <a href="${href(page.name, rev=old_revision.revision_id)}"
14 >${format_datetime(old_revision.timestamp)}</a> enter your change
15 note and click “Revert”.
16 </p>
17 <form action="" method="post">
18 <div class="actions">
19 <input type="text" name="change_note" value="${change_note}" size="50" />
20 <input type="submit" value="Revert" />
21 <input type="submit" name="cancel" value="Cancel" />
22 </div>
23 </form>
24 </py:if>
25 <py:if test="error">
26 <h2>Cannot Revert</h2>
27 <p class="error" py:if="error">${error}</p>
28 </py:if>
29 </body>
30 </html>
0 <?python
1 page_action = 'show'
2 ?>
3 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude"
4 xmlns:py="http://genshi.edgewall.org/"><xi:include href="layout.html" />
5 <head>
6 <title>${page.title}</title>
7 </head>
8 <body>
9 ${page.render()}
10 </body>
11 </html>
0 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/"
1 xmlns:xi="http://www.w3.org/2001/XInclude" py:strip="">
2 <xi:include href="macros.xml" />
3 <py:match path="head" once="true">
4 <head py:attrs="select('@*')">
5 <title py:with="title = list(select('title/text()'))"><py:if
6 test="title">${title} — </py:if>SimpleWiki</title>
7 <link rel="stylesheet" type="text/css" href="${href('_shared', 'style.css')}" />
8 ${select('*[local-name()!="title"]')}
9 </head>
10 </py:match>
11 <py:match path="body" once="true">
12 <body py:attrs="select('@*')">
13 <div class="bodywrapper">
14 <div class="header">
15 <h1><a href="${href()}">Simple Wiki</a></h1>
16 </div>
17 <div class="contents">
18 <div class="page_meta" py:if="not hide_navigation and page.timestamp">
19 <a href="${href(page.name, rev=page.revision_id)}">This revision</a>
20 was created on ${format_datetime(page.timestamp)}.
21 </div>
22 <div class="navigation">
23 <py:if test="not hide_navigation">
24 <py:for each="id, href, title in (
25 ('show', href(page.name), 'show'),
26 ('edit', href(page.name, action='edit'), 'edit'),
27 ('log', href(page.name, action='log'), 'log')
28 )">
29 <a href="${href}" class="${id == page_action and 'active' or
30 None}">${title}</a> |
31 </py:for>
32 </py:if>
33 <a href="${href()}">main</a> |
34 <a href="${href('Special:Index')}">index</a> |
35 <a href="${href('Special:Recent_Changes')}">changes</a>
36 </div>
37 ${select('*|text()')}
38 </div>
39 <div class="footer">
40 Werkzeug Example Wiki Application
41 </div>
42 </div>
43 </body>
44 </py:match>
45 </html>
0 <div xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/"
1 py:strip="">
2
3 <py:def function="render_pagination(pagination)">
4 <div class="pagination" py:if="pagination.pages > 1">
5 <py:choose test="pagination.has_previous">
6 <a href="${pagination.previous}" py:when="True">&laquo; Previous</a>
7 <span class="inactive" py:when="False">&laquo; Previous</span>
8 </py:choose>
9 | <span class="active">${pagination.page}</span> |
10 <py:choose test="pagination.has_next">
11 <a href="${pagination.next}" py:when="True">Next &raquo;</a>
12 <span class="inactive" py:when="False">Next &raquo;</span>
13 </py:choose>
14 </div>
15 </py:def>
16
17 </div>
0 <?python hide_navigation = True ?>
1 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude"
2 xmlns:py="http://genshi.edgewall.org/"><xi:include href="layout.html" />
3 <head>
4 <title>Action Not Found</title>
5 </head>
6 <body>
7 <h1>Action “${action}” Not Found</h1>
8 <p>The requested action does not exist.</p>
9 <p>Try to <a href="?">access the same URL</a> without parameters.</p>
10 </body>
11 </html>
0 <?python
1 hide_navigation = True
2 ?>
3 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude"
4 xmlns:py="http://genshi.edgewall.org/"><xi:include href="layout.html" />
5 <head>
6 <title>Index</title>
7 </head>
8 <body>
9 <h1>Index</h1>
10 <py:for each="letter, pages in letters">
11 <h2 id="${letter}"><a href="#${letter}">${letter}</a></h2>
12 <ul>
13 <li py:for="page in pages"><a href="${href(page.name)}">${page.title}</a></li>
14 </ul>
15 </py:for>
16 </body>
17 </html>
0 <?python
1 hide_navigation = True
2 ?>
3 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude"
4 xmlns:py="http://genshi.edgewall.org/"><xi:include href="layout.html" />
5 <head>
6 <title>Page Not Found</title>
7 </head>
8 <body>
9 <h1>Page Not Found</h1>
10 <p>The page you requested does not exist.</p>
11 <p py:if="revision_requested">
12 It also could be that there is no such revision of that page.
13 </p>
14 <p py:if="not protected">
15 Feel free to <a href="${href(page_name, action='edit'
16 )}">create such a page</a>.
17 </p>
18 <p py:if="protected">
19 Although this page does not exist by now you cannot create it because
20 the system protected the page name for future use.
21 </p>
22 </body>
23 </html>
0 <?python
1 hide_navigation = True
2 ?>
3 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude"
4 xmlns:py="http://genshi.edgewall.org/"><xi:include href="layout.html" />
5 <head>
6 <title>Recent Changes</title>
7 </head>
8 <body>
9 <h1>Recent Changes</h1>
10 <table class="changes">
11 <tr>
12 <th class="timestamp">Date</th>
13 <th class="page">Page</th>
14 <th class="change_note">Change Note</th>
15 </tr>
16 <tr py:for="idx, entry in enumerate(pagination.entries)"
17 class="${idx % 2 == 1 and 'even' or 'odd'}">
18 <td class="timestamp">${format_datetime(entry.timestamp)}</td>
19 <td class="page"><a href="${href(entry.name)}">${entry.title}</a></td>
20 <td class="change_note">${entry.change_note}</td>
21 </tr>
22 </table>
23 ${render_pagination(pagination)}
24 </body>
25 </html>
0 # -*- coding: utf-8 -*-
1 """
2 simplewiki.utils
3 ~~~~~~~~~~~~~~~~
4
5 This module implements various utility functions and classes used all
6 over the application.
7
8 :copyright: Copyright 2007 by Armin Ronacher.
9 :license: BSD.
10 """
11 import difflib
12 from os import path
13 from genshi import Stream
14 from genshi.template import TemplateLoader
15 from creoleparser import Parser, Creole10
16 from werkzeug import BaseRequest, BaseResponse, Local, LocalManager, \
17 url_encode, url_quote, redirect, cached_property
18
19
20 # calculate the path to the templates an create the template loader
21 TEMPLATE_PATH = path.join(path.dirname(__file__), 'templates')
22 template_loader = TemplateLoader(TEMPLATE_PATH, auto_reload=True,
23 variable_lookup='lenient')
24
25
26 # context locals. these two objects are use by the application to
27 # bind objects to the current context. A context is defined as the
28 # current thread and the current greenlet if there is greenlet support.
29 local = Local()
30 local_manager = LocalManager([local])
31 request = local('request')
32 application = local('application')
33
34 # create a new creole parser
35 creole_parser = Parser(dialect=Creole10(
36 wiki_links_base_url='',
37 wiki_links_path_func=lambda page_name: href(page_name),
38 wiki_links_space_char='_',
39 no_wiki_monospace=True,
40 use_additions=True
41 ))
42
43
44 def generate_template(template_name, **context):
45 """Load and generate a template."""
46 context.update(
47 href=href,
48 format_datetime=format_datetime
49 )
50 return template_loader.load(template_name).generate(**context)
51
52
53 def parse_creole(markup):
54 """Parse some creole markup and create a genshi stream."""
55 return creole_parser.generate(markup)
56
57
58 def href(*args, **kw):
59 """
60 Simple function for URL generation. Position arguments are used for the
61 URL path and keyword arguments are used for the url parameters.
62 """
63 result = [(request and request.script_root or '') + '/']
64 for idx, arg in enumerate(args):
65 result.append((idx and '/' or '') + url_quote(arg))
66 if kw:
67 result.append('?' + url_encode(kw))
68 return ''.join(result)
69
70
71 def format_datetime(obj):
72 """Format a datetime object."""
73 return obj.strftime('%Y-%m-%d %H:%M')
74
75
76 class Request(BaseRequest):
77 """
78 Simple request subclass that allows to bind the object to the
79 current context.
80 """
81
82 def bind_to_context(self):
83 local.request = self
84
85
86 class Response(BaseResponse):
87 """
88 Encapsulates a WSGI response. Unlike the default response object werkzeug
89 provides, this accepts a genshi stream and will automatically render it
90 to html. This makes it possible to switch to xhtml or html5 easily.
91 """
92
93 default_mimetype = 'text/html'
94
95 def __init__(self, response=None, status=200, headers=None, mimetype=None,
96 content_type=None):
97 if isinstance(response, Stream):
98 response = response.render('html', encoding=None, doctype='html')
99 BaseResponse.__init__(self, response, status, headers, mimetype,
100 content_type)
101
102
103 class Pagination(object):
104 """
105 Paginate a SQLAlchemy query object.
106 """
107
108 def __init__(self, query, per_page, page, link):
109 self.query = query
110 self.per_page = per_page
111 self.page = page
112 self.link = link
113 self._count = None
114
115 @cached_property
116 def entries(self):
117 return self.query.offset((self.page - 1) * self.per_page) \
118 .limit(self.per_page).all()
119
120 @property
121 def has_previous(self):
122 return self.page > 1
123
124 @property
125 def has_next(self):
126 return self.page < self.pages
127
128 @property
129 def previous(self):
130 return href(self.link, page=self.page - 1)
131
132 @property
133 def next(self):
134 return href(self.link, page=self.page + 1)
135
136 @cached_property
137 def count(self):
138 return self.query.count()
139
140 @property
141 def pages(self):
142 return max(0, self.count - 1) // self.per_page + 1
0 #!/usr/bin/env python
1 """
2 Simple Upload Application
3 ~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 All uploaded files are directly send back to the client.
6
7 :copyright: 2007 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 from werkzeug import BaseRequest, BaseResponse, run_simple
11
12
13 def view_file(req):
14 if not 'uploaded_file' in req.files:
15 return BaseResponse('no file uploaded')
16 f = req.files['uploaded_file']
17 def stream():
18 while 1:
19 data = f.read(65536)
20 if not data:
21 break
22 yield data
23 return BaseResponse(stream(), mimetype=f.content_type)
24
25
26 def upload_file(req):
27 return BaseResponse('''
28 <h1>Upload File</h1>
29 <form action="" method="post" enctype="multipart/form-data">
30 <input type="file" name="uploaded_file">
31 <input type="submit" value="Upload">
32 </form>
33 ''', mimetype='text/html')
34
35
36 def application(environ, start_response):
37 req = BaseRequest(environ)
38 if req.method == 'POST':
39 resp = view_file(req)
40 else:
41 resp = upload_file(req)
42 return resp(environ, start_response)
43
44
45 if __name__ == '__main__':
46 run_simple('localhost', 5000, application)
0 # -*- coding: utf-8 -*-
1 """
2 Example application based on weblikepy
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 The application from th web.py tutorial.
6
7 :copyright: Copyright 2007 by Armin Ronacher.
8 :license: BSD.
9 """
10 from webpylike import WebPyApp, View, Response
11
12
13 urls = (
14 '/', 'index',
15 '/about', 'about'
16 )
17
18
19 class index(View):
20 def GET(self):
21 return Response('Hello World')
22
23
24 class about(View):
25 def GET(self):
26 return Response('This is the about page')
27
28
29 app = WebPyApp(urls, globals())
0 # -*- coding: utf-8 -*-
1 """
2 webpylike
3 ~~~~~~~~~
4
5 This module implements web.py like dispatching. What this module does
6 not implement is a stream system that hooks into sys.stdout like web.py
7 provides. I consider this bad design.
8
9 :copyright: Copyright 2007 by Armin Ronacher.
10 :license: BSD.
11 """
12 import re
13 from werkzeug import BaseRequest, BaseResponse
14 from werkzeug.exceptions import HTTPException, MethodNotAllowed, \
15 NotImplemented, NotFound
16
17
18 class Request(BaseRequest):
19 """Encapsulates a request."""
20
21
22 class Response(BaseResponse):
23 """Encapsulates a response."""
24
25
26 class View(object):
27 """Baseclass for our views."""
28
29 def __init__(self, app, req):
30 self.app = app
31 self.req = req
32
33 def GET(self):
34 raise MethodNotAllowed()
35 POST = DELETE = PUT = GET
36
37 def HEAD(self):
38 return self.GET()
39
40
41 class WebPyApp(object):
42 """
43 An interface to a web.py like application. It works like the web.run
44 function in web.py
45 """
46
47 def __init__(self, urls, views):
48 self.urls = [(re.compile('^%s$' % urls[i]), urls[i + 1])
49 for i in xrange(0, len(urls), 2)]
50 self.views = views
51
52 def __call__(self, environ, start_response):
53 try:
54 req = Request(environ)
55 for regex, view in self.urls:
56 match = regex.match(req.path)
57 if match is not None:
58 view = self.views[view](self, req)
59 if req.method not in ('GET', 'HEAD', 'POST',
60 'DELETE', 'PUT'):
61 raise NotImplemented()
62 resp = getattr(view, req.method)(*match.groups())
63 break
64 else:
65 raise NotFound()
66 except HTTPException, e:
67 resp = e
68 return resp(environ, start_response)
4646 Development Version
4747 -------------------
4848
49 The `Werkzeug tip <http://dev.pocoo.org/hg/werkzeug-main/archive/tip.zip#egg=Werkzeug-dev`
49 The `Werkzeug tip <http://dev.pocoo.org/hg/werkzeug-main/archive/tip.zip#egg=Werkzeug-dev>`_
5050 is installable via `easy_install` with ``easy_install Werkzeug==dev``.
5151 """
5252 import os
5555
5656 from setuptools import setup, Feature
5757
58
59 data_files = []
60 documentation_path = 'docs/build'
61 if os.path.exists(documentation_path):
62 documentation_files = []
63 for fn in os.listdir(documentation_path):
64 if not fn.startswith('.'):
65 fn = os.path.join(documentation_path, fn)
66 if os.path.isfile(fn):
67 documentation_files.append(fn)
68 data_files.append(('docs', documentation_files))
69
70
5871 setup(
5972 name='Werkzeug',
60 version='0.2',
73 version='0.3',
6174 url='http://werkzeug.pocoo.org/',
62 download_url='http://werkzeug.pocoo.org/download',
6375 license='BSD',
6476 author='Armin Ronacher',
6577 author_email='armin.ronacher@active-4.com',
6779 long_description=__doc__,
6880 zip_safe=False,
6981 classifiers=[
70 'Development Status :: 3 - Alpha',
82 'Development Status :: 4 - Beta',
7183 'Environment :: Web Environment',
7284 'Intended Audience :: Developers',
7385 'License :: OSI Approved :: BSD License',
7789 'Topic :: Software Development :: Libraries :: Python Modules'
7890 ],
7991 packages=['werkzeug', 'werkzeug.debug'],
92 data_files=data_files,
8093 package_data={
81 'werkzeug.debug': ['shared/*']
94 'werkzeug.debug': ['shared/*', 'templates/*']
8295 },
8396 features={
8497 'contrib': Feature('optional contribute addon modules',
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.http test
3 ~~~~~~~~~~~~~~~~~~~
4
5 :license: BSD license.
6 """
7 from datetime import datetime
8 from py.test import raises
9 from werkzeug.http import *
10
11
12 def test_accept():
13 a = parse_accept_header('en-us,ru;q=0.5')
14 assert a.values() == ['en-us', 'ru']
15 assert a.best == 'en-us'
16 assert a.find('ru') == 1
17 raises(IndexError, lambda: a.index('de'))
18
19 a = parse_accept_header('text/xml,application/xml,application/xhtml+xml,'
20 'text/html;q=0.9,text/plain;q=0.8,'
21 'image/png,*/*;q=0.5')
22 assert a['missing'] == 0
23 assert a['image/png'] == 1
24 assert a['text/plain'] == 0.8
25
26
27 def test_set_header():
28 hs = parse_set_header('foo, Bar, "Blah baz", Hehe')
29 assert 'blah baz' in hs
30 assert 'foobar' not in hs
31 assert 'foo' in hs
32 assert list(hs) == ['foo', 'Bar', 'Blah baz', 'Hehe']
33 hs.add('Foo')
34 assert hs.to_header() == 'foo, Bar, "Blah baz", Hehe'
35
36
37 def test_list_header():
38 hl = parse_list_header('foo baz, blah')
39 assert hl == ['foo baz', 'blah']
40
41
42 def test_dict_header():
43 d = parse_dict_header('foo="bar baz", blah=42')
44 assert d == {'foo': 'bar baz', 'blah': '42'}
45
46
47 def test_cache_control_header():
48 cc = parse_cache_control_header('max-age=0, no-cache')
49 assert cc.max_age == 0
50 assert cc.no_cache
51 cc = parse_cache_control_header('private, community="UCI"')
52 assert cc.private
53 assert cc['community'] == 'UCI'
54
55
56 def test_authorization_header():
57 a = parse_authorization_header('Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==')
58 assert a.type == 'basic'
59 assert a.username == 'Aladdin'
60 assert a.password == 'open sesame'
61
62 a = parse_authorization_header('''Digest username="Mufasa",
63 realm="testrealm@host.invalid",
64 nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
65 uri="/dir/index.html",
66 qop=auth,
67 nc=00000001,
68 cnonce="0a4f113b",
69 response="6629fae49393a05397450978507c4ef1",
70 opaque="5ccc069c403ebaf9f0171e9517f40e41"''')
71 assert a.type == 'digest'
72 assert a.realm == 'testrealm@host.invalid'
73 assert a.nonce == 'dcd98b7102dd2f0e8b11d0f600bfb0c093'
74 assert a.uri == '/dir/index.html'
75 assert 'auth' in a.qop
76 assert a.nc == '00000001'
77 assert a.cnonce == '0a4f113b'
78 assert a.response == '6629fae49393a05397450978507c4ef1'
79 assert a.opaque == '5ccc069c403ebaf9f0171e9517f40e41'
80
81
82 def test_www_authenticate_header():
83 wa = parse_www_authenticate_header('Basic realm="WallyWorld"')
84 assert wa.type == 'basic'
85 assert wa.realm == 'WallyWorld'
86 wa.realm = 'Foo Bar'
87 assert wa.to_header() == 'Basic realm="Foo Bar"'
88
89 wa = parse_www_authenticate_header('''Digest
90 realm="testrealm@host.com",
91 qop="auth,auth-int",
92 nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
93 opaque="5ccc069c403ebaf9f0171e9517f40e41"''')
94 assert wa.type == 'digest'
95 assert wa.realm == 'testrealm@host.com'
96 assert 'auth' in wa.qop
97 assert 'auth-int' in wa.qop
98 assert wa.nonce == 'dcd98b7102dd2f0e8b11d0f600bfb0c093'
99 assert wa.opaque == '5ccc069c403ebaf9f0171e9517f40e41'
100
101
102 def test_etags():
103 assert quote_etag('foo') == '"foo"'
104 assert quote_etag('foo', True) == 'w/"foo"'
105 assert unquote_etag('"foo"') == ('foo', False)
106 assert unquote_etag('w/"foo"') == ('foo', True)
107 es = parse_etags('"foo", "bar", w/"baz", blar')
108 assert 'foo' in es
109 assert 'baz' not in es
110 assert es.contains_weak('baz')
111 assert 'blar' in es
112 assert es.contains_raw('w/"baz"')
113 assert es.contains_raw('"foo"')
114 assert sorted(es.to_header().split(', ')) == ['"bar"', '"blar"', '"foo"', 'w/"baz"']
115
116
117 def test_parse_date():
118 assert parse_date('Sun, 06 Nov 1994 08:49:37 GMT ') == datetime(1994, 11, 6, 8, 49, 37)
119 assert parse_date('Sunday, 06-Nov-94 08:49:37 GMT') == datetime(1994, 11, 6, 8, 49, 37)
120 assert parse_date(' Sun Nov 6 08:49:37 1994') == datetime(1994, 11, 6, 8, 49, 37)
121 assert parse_date('foo') is None
Binary diff not shown
77 :license: BSD license.
88 """
99 from py.test import raises
10 from werkzeug.wrappers import Response
1011 from werkzeug.routing import Map, Rule, NotFound, BuildError, RequestRedirect
1112 from werkzeug.utils import create_environ
1213
128129 assert adapter.match('/User:thomas') == ('user', {'username':'thomas'})
129130 assert adapter.match('/User:thomas/projects/werkzeug') == ('userpage', {'username':'thomas', 'name':'projects/werkzeug'})
130131 assert adapter.match('/Files/downloads/werkzeug/0.2.zip') == ('files', {'file':'downloads/werkzeug/0.2.zip'})
132
133
134 def test_dispatch():
135 env = create_environ('/')
136 map = Map([
137 Rule('/', endpoint='root'),
138 Rule('/foo/', endpoint='foo')
139 ])
140 adapter = map.bind_to_environ(env)
141
142 raise_this = None
143 def view_func(endpoint, values):
144 if raise_this is not None:
145 raise raise_this
146 return Response(repr((endpoint, values)))
147 dispatch = lambda p, q=False: Response.force_type(adapter.dispatch(view_func, p,
148 catch_http_exceptions=q), env)
149
150 assert dispatch('/').data == "('root', {})"
151 assert dispatch('/foo').status_code == 301
152 raise_this = NotFound()
153 raises(NotFound, lambda: dispatch('/bar'))
154 assert dispatch('/bar', True).status_code == 404
Binary diff not shown
156156
157157 # list conversion
158158 assert headers.to_list() == [
159 ('X-Foo', 'bar'),
160 ('Content-Type', 'foo/bar')
159 ('Content-Type', 'foo/bar'),
160 ('X-Foo', 'bar')
161161 ]
162162
163163 # defaults
217217
218218 string = environ_property('string')
219219 missing = environ_property('missing', 'spam')
220 read_only = environ_property('number', read_only=True)
220 read_only = environ_property('number')
221221 number = environ_property('number', load_func=int)
222222 broken_number = environ_property('broken_number', load_func=int)
223 date = environ_property('date', None, parse_date, http_date)
223 date = environ_property('date', None, parse_date, http_date,
224 read_only=False)
225 foo = environ_property('foo')
224226
225227 a = A()
226228 assert a.string == 'abc'
415417
416418
417419 def test_find_modules():
418 assert list(find_modules('werkzeug.debug')) == ['werkzeug.debug.render',
419 'werkzeug.debug.util']
420 assert list(find_modules('werkzeug.debug')) == \
421 ['werkzeug.debug.console', 'werkzeug.debug.render',
422 'werkzeug.debug.repr', 'werkzeug.debug.tbtools',
423 'werkzeug.debug.utils']
420424
421425
422426 def test_html_builder():
433437 )
434438 ) == '<html><head><title>foo</title><script type="text/javascript">' \
435439 '</script></head></html>'
440
441
442 def test_shareddatamiddleware_get_file_loader():
443 app = SharedDataMiddleware(None, {})
444 assert callable(app.get_file_loader('foo'))
445
446
447 def test_validate_arguments():
448 take_none = lambda: None
449 take_two = lambda a, b: None
450 take_two_one_default = lambda a, b=0: None
451
452 assert validate_arguments(take_two, (1, 2,), {}) == ((1, 2), {})
453 assert validate_arguments(take_two, (1,), {'b': 2}) == ((1, 2), {})
454 assert validate_arguments(take_two_one_default, (1,), {}) == ((1, 0), {})
455 assert validate_arguments(take_two_one_default, (1, 2), {}) == ((1, 2), {})
456
457 raises(ArgumentValidationError, validate_arguments, take_two, (), {})
458
459 assert validate_arguments(take_none, (1, 2,), {'c': 3}) == ((), {})
460 raises(ArgumentValidationError,
461 validate_arguments, take_none, (1,), {}, drop_extra=False)
462 raises(ArgumentValidationError,
463 validate_arguments, take_none, (), {'a': 1}, drop_extra=False)
Binary diff not shown
102102 assert response.data == 'öäü'
103103
104104 # writing
105 response = BaseResponse('foo')
106 response.write('bar')
105 response = Response('foo')
106 response.stream.write('bar')
107107 assert response.data == 'foobar'
108108
109109 # set cookie
263263 response.content_language.add('en-US')
264264 response.content_language.add('fr')
265265 assert response.headers['Content-Language'] == 'en-US, fr'
266
267
268 def test_shallow_mode():
269 request = Request({'QUERY_STRING': 'foo=bar'}, shallow=True)
270 assert request.args['foo'] == 'bar'
271 raises(RuntimeError, lambda: request.form['foo'])
Binary diff not shown
3333 'SharedDataMiddleware', 'ClosingIterator',
3434 'FileStorage', 'url_unquote_plus', 'url_decode',
3535 'url_unquote', 'get_current_url', 'redirect',
36 'append_slash_redirect', 'lazy_property',
36 'append_slash_redirect',
3737 'cached_property', 'MultiDict', 'import_string',
3838 'dump_cookie', 'parse_cookie', 'unescape',
3939 'format_string', 'Href', 'DispatcherMiddleware',
4040 'find_modules', 'header_property', 'html',
41 'xhtml'],
41 'xhtml', 'HTMLBuilder', 'parse_form_data',
42 'validate_arguments', 'ArgumentValidationError',
43 'bind_arguments'],
4244 'werkzeug.useragents': ['UserAgent'],
4345 'werkzeug.http': ['Accept', 'CacheControl', 'ETags', 'parse_etags',
4446 'parse_date', 'parse_cache_control_header',
4547 'is_resource_modified', 'parse_accept_header',
4648 'parse_set_header', 'quote_etag', 'unquote_etag',
47 'HeaderSet', 'HTTP_STATUS_CODES'],
49 'generate_etag', 'dump_header',
50 'parse_list_header', 'parse_dict_header',
51 'HeaderSet', 'parse_authorization_header',
52 'parse_www_authenticate_header',
53 'WWWAuthenticate', 'Authorization',
54 'HTTP_STATUS_CODES'],
4855 'werkzeug.wrappers': ['BaseResponse', 'BaseRequest', 'Request',
4956 'Response', 'AcceptMixin', 'ETagRequestMixin',
5057 'ETagResponseMixin', 'ResponseStreamMixin',
5158 'CommonResponseDescriptorsMixin',
52 'UserAgentMixin', 'BaseReporterStream']
59 'UserAgentMixin', 'AuthorizationMixin',
60 'WWWAuthenticateMixin'],
61 # the undocumented easteregg ;-)
62 'werkzeug._internal': ['_easteregg']
5363 }
5464
55 attribute_modules = ['exceptions', 'routing', 'script']
65 attribute_modules = dict.fromkeys(['exceptions', 'routing', 'script'])
5666
5767
5868 object_origins = {}
8494 '__file__': __file__,
8595 '__path__': __path__,
8696 '__doc__': __doc__,
87 '__all__': tuple(object_origins)
97 '__all__': tuple(object_origins) + tuple(attribute_modules)
8898 })
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug._internal
3 ~~~~~~~~~~~~~~~~~~
4
5 This module provides internally used helpers and constants.
6
7 :copyright: Copyright 2008 by Armin Ronacher.
8 :license: GNU GPL.
9 """
10 import cgi
11 import inspect
12 from weakref import WeakKeyDictionary
13 from cStringIO import StringIO
14 from Cookie import BaseCookie, Morsel, CookieError
15 from time import asctime, gmtime, time
16 from datetime import datetime
17
18
19 _logger = None
20 _empty_stream = StringIO('')
21 _signature_cache = WeakKeyDictionary()
22
23
24 HTTP_STATUS_CODES = {
25 100: 'Continue',
26 101: 'Switching Protocols',
27 102: 'Processing',
28 200: 'OK',
29 201: 'Created',
30 202: 'Accepted',
31 203: 'Non Authoritative Information',
32 204: 'No Content',
33 205: 'Reset Content',
34 206: 'Partial Content',
35 207: 'Multi Status',
36 226: 'IM Used', # see RFC 3229
37 300: 'Multiple Choices',
38 301: 'Moved Permanently',
39 302: 'Found',
40 303: 'See Other',
41 304: 'Not Modified',
42 305: 'Use Proxy',
43 307: 'Temporary Redirect',
44 400: 'Bad Request',
45 401: 'Unauthorized',
46 402: 'Payment Required', # unused
47 403: 'Forbidden',
48 404: 'Not Found',
49 405: 'Method Not Allowed',
50 406: 'Not Acceptable',
51 407: 'Proxy Authentication Required',
52 408: 'Request Timeout',
53 409: 'Conflict',
54 410: 'Gone',
55 411: 'Length Required',
56 412: 'Precondition Failed',
57 413: 'Request Entity Too Large',
58 414: 'Request URI Too Long',
59 415: 'Unsupported Media Type',
60 416: 'Requested Range Not Satisfiable',
61 417: 'Expectation Failed',
62 422: 'Unprocessable Entity',
63 423: 'Locked',
64 424: 'Failed Dependency',
65 426: 'Upgrade Required',
66 449: 'Retry With', # propritary MS extension
67 500: 'Internal Server Error',
68 501: 'Not Implemented',
69 502: 'Bad Gateway',
70 503: 'Service Unavailable',
71 504: 'Gateway Timeout',
72 505: 'HTTP Version Not Supported',
73 507: 'Insufficient Storage',
74 510: 'Not Extended'
75 }
76
77
78 def _log(type, message, *args, **kwargs):
79 """Log into the internal werkzeug logger."""
80 global _logger
81 if _logger is None:
82 import logging
83 handler = logging.StreamHandler()
84 _logger = logging.getLogger('werkzeug')
85 _logger.addHandler(handler)
86 _logger.setLevel(logging.INFO)
87 getattr(_logger, type)(message.rstrip(), *args, **kwargs)
88
89
90 def _parse_signature(func):
91 """Return a signature object for the function."""
92 if hasattr(func, 'im_func'):
93 func = func.im_func
94
95 # if we have a cached validator for this function, return it
96 parse = _signature_cache.get(func)
97 if parse is not None:
98 return parse
99
100 # inspect the function signature and collect all the information
101 positional, vararg_var, kwarg_var, defaults = inspect.getargspec(func)
102 defaults = defaults or ()
103 arg_count = len(positional)
104 arguments = []
105 for idx, name in enumerate(positional):
106 if isinstance(name, list):
107 raise TypeError('cannot parse functions that unpack tuples '
108 'in the function signature')
109 try:
110 default = defaults[idx - arg_count]
111 except IndexError:
112 param = (name, False, None)
113 else:
114 param = (name, True, default)
115 arguments.append(param)
116 arguments = tuple(arguments)
117
118 def parse(args, kwargs):
119 new_args = []
120 missing = []
121 extra = {}
122
123 # consume as many arguments as positional as possible
124 for idx, (name, has_default, default) in enumerate(arguments):
125 try:
126 new_args.append(args[idx])
127 except IndexError:
128 try:
129 new_args.append(kwargs.pop(name))
130 except KeyError:
131 if has_default:
132 new_args.append(default)
133 else:
134 missing.append(name)
135 else:
136 if name in kwargs:
137 extra[name] = kwargs.pop(name)
138
139 # handle extra arguments
140 extra_positional = args[arg_count:]
141 if vararg_var is not None:
142 new_args.extend(extra_positional)
143 extra_positional = ()
144 if kwargs and not kwarg_var is not None:
145 extra.update(kwargs)
146 kwargs = {}
147
148 return new_args, kwargs, missing, extra, extra_positional, \
149 arguments, vararg_var, kwarg_var
150 _signature_cache[func] = parse
151 return parse
152
153
154 def _patch_wrapper(old, new):
155 """Helper function that forwards all the function details to the
156 decorated function."""
157 try:
158 new.__name__ = old.__name__
159 new.__module__ = old.__module__
160 new.__doc__ = old.__doc__
161 new.__dict__ = old.__dict__
162 except:
163 pass
164 return new
165
166
167 def _decode_unicode(value, charset, errors):
168 """Like the regular decode function but this one raises an
169 `HTTPUnicodeError` if errors is `strict`."""
170 fallback = None
171 if errors.startswith('fallback:'):
172 fallback = errors[9:]
173 errors = 'strict'
174 try:
175 return value.decode(charset, errors)
176 except UnicodeError, e:
177 if fallback is not None:
178 return value.decode(fallback, 'ignore')
179 from werkzeug.exceptions import HTTPUnicodeError
180 raise HTTPUnicodeError(str(e))
181
182
183 def _iter_modules(path):
184 """Iterate over all modules in a package."""
185 import pkgutil
186 if hasattr(pkgutil, 'iter_modules'):
187 for importer, modname, ispkg in pkgutil.iter_modules(path):
188 yield modname, ispkg
189 return
190 from inspect import getmodulename
191 from pydoc import ispackage
192 found = set()
193 for path in path:
194 for filename in os.listdir(path):
195 p = os.path.join(path, filename)
196 modname = getmodulename(filename)
197 if modname and modname != '__init__':
198 if modname not in found:
199 found.add(modname)
200 yield modname, ispackage(modname)
201
202
203 def _dump_date(d, delim):
204 """Used for `http_date` and `cookie_date`."""
205 if d is None:
206 d = gmtime()
207 elif isinstance(d, datetime):
208 d = d.utctimetuple()
209 elif isinstance(d, (int, long, float)):
210 d = gmtime(d)
211 return '%s, %02d%s%s%s%s %02d:%02d:%02d GMT' % (
212 ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')[d.tm_wday],
213 d.tm_mday, delim,
214 ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
215 'Oct', 'Nov', 'Dec')[d.tm_mon - 1],
216 delim, str(d.tm_year), d.tm_hour, d.tm_min, d.tm_sec
217 )
218
219
220 class _ExtendedMorsel(Morsel):
221 _reserved = {'httponly': 'HttpOnly'}
222 _reserved.update(Morsel._reserved)
223
224 def __init__(self, name=None, value=None):
225 Morsel.__init__(self)
226 if name is not None:
227 self.set(name, value, value)
228
229 def OutputString(self, attrs=None):
230 httponly = self.pop('httponly', False)
231 result = Morsel.OutputString(self, attrs).rstrip('\t ;')
232 if httponly:
233 result += '; HttpOnly'
234 return result
235
236
237 class _StorageHelper(cgi.FieldStorage):
238 """Helper class used by `parse_form_data` to parse submitted file and
239 form data. Don't use this class directly. This also defines a simple
240 repr that prints just the filename as the default repr reads the
241 complete data of the stream.
242 """
243
244 FieldStorageClass = cgi.FieldStorage
245
246 def __init__(self, environ, stream_factory):
247 if stream_factory is not None:
248 self.make_file = lambda binary=None: stream_factory()
249 cgi.FieldStorage.__init__(self,
250 fp=environ['wsgi.input'],
251 environ={
252 'REQUEST_METHOD': environ['REQUEST_METHOD'],
253 'CONTENT_TYPE': environ['CONTENT_TYPE'],
254 'CONTENT_LENGTH': environ['CONTENT_LENGTH']
255 },
256 keep_blank_values=True
257 )
258
259 def __repr__(self):
260 return '<%s %r>' % (
261 self.__class__.__name__,
262 self.name
263 )
264
265
266 class _ExtendedCookie(BaseCookie):
267 """Form of the base cookie that doesn't raise a `CookieError` for
268 malformed keys. This has the advantage that broken cookies submitted
269 by nonstandard browsers don't cause the cookie to be empty.
270 """
271
272 def _BaseCookie__set(self, key, real_value, coded_value):
273 morsel = self.get(key, _ExtendedMorsel())
274 try:
275 morsel.set(key, real_value, coded_value)
276 except CookieError:
277 pass
278 dict.__setitem__(self, key, morsel)
279
280
281 class _DictAccessorProperty(object):
282 """Baseclass for `environ_property` and `header_property`."""
283 read_only = False
284
285 def __init__(self, name, default=None, load_func=None, dump_func=None,
286 read_only=None, doc=None):
287 self.name = name
288 self.default = default
289 self.load_func = load_func
290 self.dump_func = dump_func
291 if read_only is not None:
292 self.read_only = read_only
293 self.__doc__ = doc
294
295 def __get__(self, obj, type=None):
296 if obj is None:
297 return self
298 storage = self.lookup(obj)
299 if self.name not in storage:
300 return self.default
301 rv = storage[self.name]
302 if self.load_func is not None:
303 try:
304 rv = self.load_func(rv)
305 except (ValueError, TypeError):
306 rv = self.default
307 return rv
308
309 def __set__(self, obj, value):
310 if self.read_only:
311 raise AttributeError('read only property')
312 if self.dump_func is not None:
313 value = self.dump_func(value)
314 self.lookup(obj)[self.name] = value
315
316 def __delete__(self, obj):
317 if self.read_only:
318 raise AttributeError('read only property')
319 self.lookup(obj).pop(self.name, None)
320
321 def __repr__(self):
322 return '<%s %s>' % (
323 self.__class__.__name__,
324 self.name
325 )
326
327
328 class _UpdateDict(dict):
329 """A dict that calls `on_update` on modifications."""
330
331 def __init__(self, data, on_update):
332 dict.__init__(self, data)
333 self.on_update = on_update
334
335 def calls_update(f):
336 def oncall(self, *args, **kw):
337 rv = f(self, *args, **kw)
338 if self.on_update is not None:
339 self.on_update(self)
340 return rv
341 return _patch_wrapper(f, oncall)
342
343 __setitem__ = calls_update(dict.__setitem__)
344 __delitem__ = calls_update(dict.__delitem__)
345 clear = calls_update(dict.clear)
346 pop = calls_update(dict.pop)
347 popitem = calls_update(dict.popitem)
348 setdefault = calls_update(dict.setdefault)
349 update = calls_update(dict.update)
350
351
352
353 def _easteregg(app):
354 """Like the name says."""
355 gyver = '\n'.join([x + (77 - len(x)) * ' ' for x in '''
356 eJyFlzuOJDkMRP06xRjymKgDJCDQStBYT8BCgK4gTwfQ2fcFs2a2FzvZk+hvlcRvRJD148efHt9m
357 9Xz94dRY5hGt1nrYcXx7us9qlcP9HHNh28rz8dZj+q4rynVFFPdlY4zH873NKCexrDM6zxxRymzz
358 4QIxzK4bth1PV7+uHn6WXZ5C4ka/+prFzx3zWLMHAVZb8RRUxtFXI5DTQ2n3Hi2sNI+HK43AOWSY
359 jmEzE4naFp58PdzhPMdslLVWHTGUVpSxImw+pS/D+JhzLfdS1j7PzUMxij+mc2U0I9zcbZ/HcZxc
360 q1QjvvcThMYFnp93agEx392ZdLJWXbi/Ca4Oivl4h/Y1ErEqP+lrg7Xa4qnUKu5UE9UUA4xeqLJ5
361 jWlPKJvR2yhRI7xFPdzPuc6adXu6ovwXwRPXXnZHxlPtkSkqWHilsOrGrvcVWXgGP3daXomCj317
362 8P2UOw/NnA0OOikZyFf3zZ76eN9QXNwYdD8f8/LdBRFg0BO3bB+Pe/+G8er8tDJv83XTkj7WeMBJ
363 v/rnAfdO51d6sFglfi8U7zbnr0u9tyJHhFZNXYfH8Iafv2Oa+DT6l8u9UYlajV/hcEgk1x8E8L/r
364 XJXl2SK+GJCxtnyhVKv6GFCEB1OO3f9YWAIEbwcRWv/6RPpsEzOkXURMN37J0PoCSYeBnJQd9Giu
365 LxYQJNlYPSo/iTQwgaihbART7Fcyem2tTSCcwNCs85MOOpJtXhXDe0E7zgZJkcxWTar/zEjdIVCk
366 iXy87FW6j5aGZhttDBoAZ3vnmlkx4q4mMmCdLtnHkBXFMCReqthSGkQ+MDXLLCpXwBs0t+sIhsDI
367 tjBB8MwqYQpLygZ56rRHHpw+OAVyGgaGRHWy2QfXez+ZQQTTBkmRXdV/A9LwH6XGZpEAZU8rs4pE
368 1R4FQ3Uwt8RKEtRc0/CrANUoes3EzM6WYcFyskGZ6UTHJWenBDS7h163Eo2bpzqxNE9aVgEM2CqI
369 GAJe9Yra4P5qKmta27VjzYdR04Vc7KHeY4vs61C0nbywFmcSXYjzBHdiEjraS7PGG2jHHTpJUMxN
370 Jlxr3pUuFvlBWLJGE3GcA1/1xxLcHmlO+LAXbhrXah1tD6Ze+uqFGdZa5FM+3eHcKNaEarutAQ0A
371 QMAZHV+ve6LxAwWnXbbSXEG2DmCX5ijeLCKj5lhVFBrMm+ryOttCAeFpUdZyQLAQkA06RLs56rzG
372 8MID55vqr/g64Qr/wqwlE0TVxgoiZhHrbY2h1iuuyUVg1nlkpDrQ7Vm1xIkI5XRKLedN9EjzVchu
373 jQhXcVkjVdgP2O99QShpdvXWoSwkp5uMwyjt3jiWCqWGSiaaPAzohjPanXVLbM3x0dNskJsaCEyz
374 DTKIs+7WKJD4ZcJGfMhLFBf6hlbnNkLEePF8Cx2o2kwmYF4+MzAxa6i+6xIQkswOqGO+3x9NaZX8
375 MrZRaFZpLeVTYI9F/djY6DDVVs340nZGmwrDqTCiiqD5luj3OzwpmQCiQhdRYowUYEA3i1WWGwL4
376 GCtSoO4XbIPFeKGU13XPkDf5IdimLpAvi2kVDVQbzOOa4KAXMFlpi/hV8F6IDe0Y2reg3PuNKT3i
377 RYhZqtkQZqSB2Qm0SGtjAw7RDwaM1roESC8HWiPxkoOy0lLTRFG39kvbLZbU9gFKFRvixDZBJmpi
378 Xyq3RE5lW00EJjaqwp/v3EByMSpVZYsEIJ4APaHmVtpGSieV5CALOtNUAzTBiw81GLgC0quyzf6c
379 NlWknzJeCsJ5fup2R4d8CYGN77mu5vnO1UqbfElZ9E6cR6zbHjgsr9ly18fXjZoPeDjPuzlWbFwS
380 pdvPkhntFvkc13qb9094LL5NrA3NIq3r9eNnop9DizWOqCEbyRBFJTHn6Tt3CG1o8a4HevYh0XiJ
381 sR0AVVHuGuMOIfbuQ/OKBkGRC6NJ4u7sbPX8bG/n5sNIOQ6/Y/BX3IwRlTSabtZpYLB85lYtkkgm
382 p1qXK3Du2mnr5INXmT/78KI12n11EFBkJHHp0wJyLe9MvPNUGYsf+170maayRoy2lURGHAIapSpQ
383 krEDuNoJCHNlZYhKpvw4mspVWxqo415n8cD62N9+EfHrAvqQnINStetek7RY2Urv8nxsnGaZfRr/
384 nhXbJ6m/yl1LzYqscDZA9QHLNbdaSTTr+kFg3bC0iYbX/eQy0Bv3h4B50/SGYzKAXkCeOLI3bcAt
385 mj2Z/FM1vQWgDynsRwNvrWnJHlespkrp8+vO1jNaibm+PhqXPPv30YwDZ6jApe3wUjFQobghvW9p
386 7f2zLkGNv8b191cD/3vs9Q833z8t'''.decode('base64').decode('zlib').splitlines()])
387 def easteregged(environ, start_response):
388 def injecting_start_response(status, headers, exc_info=None):
389 headers.append(('X-Powered-By', 'Werkzeug'))
390 return start_response(status, headers, exc_info)
391 if environ.get('QUERY_STRING') != 'macgybarchakku':
392 return app(environ, injecting_start_response)
393 injecting_start_response('200 OK', [('Content-Type', 'text/html')])
394 return ['''<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
395 <title>About Werkzeug</>
396 <style type="text/css">
397 body { font: 15px Georgia, serif; text-align: center; }
398 a { color: #333; text-decoration: none; }
399 h1 { font-size: 30px; margin: 20px 0 10px 0; }
400 p { margin: 0 0 30px 0; }
401 pre { font: 11px 'Consolas', 'Monaco', monospace; line-height: 0.95; }
402 </style>
403 <h1><a href="http://werkzeug.pocoo.org/">Werkzeug</a></h1>
404 <p>the Swiss Army knife of Python web development.
405 <pre>%s\n\n\n</>''' % gyver]
406 return easteregged
2929
3030 def format_iso8601(obj):
3131 """Format a datetime object for iso8601"""
32 return obj.strftime('%Y-%d-%mT%H:%M:%SZ')
32 return obj.strftime('%Y-%m-%dT%H:%M:%SZ')
3333
3434
3535 class AtomFeed(object):
36 """
37 A helper class that creates ATOM feeds.
38 """
36 """A helper class that creates Atom feeds."""
3937 default_generator = ('Werkzeug', None, None)
4038
4139 def __init__(self, title=None, entries=None, **kwargs):
42 """
43 Create an atom feed.
40 """Create an Atom feed.
4441
4542 :Parameters:
4643 title
9592 Everywhere where a list is demanded, any iterable can be used.
9693 """
9794 self.title = title
98 self.title_type = kwargs.get('title_type')
95 self.title_type = kwargs.get('title_type', 'text')
9996 self.url = kwargs.get('url')
10097 self.feed_url = kwargs.get('feed_url', self.url)
10198 self.id = kwargs.get('id', self.feed_url)
106103 self.rights = kwargs.get('rights')
107104 self.rights_type = kwargs.get('rights_type')
108105 self.subtitle = kwargs.get('subtitle')
109 self.subtitle_type = kwargs.get('subtitle_type')
106 self.subtitle_type = kwargs.get('subtitle_type', 'text')
110107 self.generator = kwargs.get('generator')
111108 if self.generator is None:
112109 self.generator = self.default_generator
133130 if len(args) == 1 and not kwargs and isinstance(args[0], FeedEntry):
134131 self.entries.append(args[0])
135132 else:
133 kwargs['feed_url'] = self.feed_url
136134 self.entries.append(FeedEntry(*args, **kwargs))
137135
138136 def __repr__(self):
150148 self.author = ({'name': u'unbekannter Autor'},)
151149
152150 if not self.updated:
153 dates = sorted(entry.updated for entry in self.entries)
151 dates = sorted([entry.updated for entry in self.entries])
154152 self.updated = dates and dates[-1] or datetime.utcnow()
155153
156154 yield u'<?xml version="1.0" encoding="utf-8"?>\n'
165163 escape(self.feed_url, True)
166164 for link in self.links:
167165 yield u' <link %s/>\n' % ''.join('%s="%s" ' % \
168 (k, escape(link[k], True)) for k in link)
166 [(k, escape(link[k], True)) for k in link])
169167 for author in self.author:
170168 yield u' <author>\n'
171169 yield u' <name>%s</name>\n' % escape(author['name'])
218216
219217
220218 class FeedEntry(object):
221 """
222 Represents a single entry in a feed.
223 """
224
225 def __init__(self, title=None, content=None, **kwargs):
226 """
227 Holds an atom feed entry.
219 """Represents a single entry in a feed."""
220
221 def __init__(self, title=None, content=None, feed_url=None, **kwargs):
222 """Holds an Atom feed entry.
228223
229224 :Parameters:
230225 title
277272 Everywhere where a list is demanded, any iterable can be used.
278273 """
279274 self.title = title
280 self.title_type = kwargs.get('title_type', 'html')
275 self.title_type = kwargs.get('title_type', 'text')
281276 self.content = content
282277 self.content_type = kwargs.get('content_type', 'html')
283278 self.url = kwargs.get('url')
289284 self.published = kwargs.get('published')
290285 self.rights = kwargs.get('rights')
291286 self.links = kwargs.get('links', [])
292 self.xml_base = kwargs.get('xml_base', self.url)
287 self.xml_base = kwargs.get('xml_base', feed_url)
293288
294289 if not hasattr(self.author, '__iter__') \
295290 or isinstance(self.author, (basestring, dict)):
335330 yield u' </author>\n'
336331 for link in self.links:
337332 yield u' <link %s/>\n' % ''.join('%s="%s" ' % \
338 (k, escape(link[k], True)) for k in link)
333 [(k, escape(link[k], True)) for k in link])
339334 if self.summary:
340335 yield u' ' + _make_text_block('summary', self.summary,
341336 self.summary_type)
1212 :copyright: 2007-2008 by Armin Ronacher.
1313 :license: BSD, see LICENSE for more details.
1414 """
15 import os
16 import re
1517 try:
1618 from hashlib import md5
1719 except ImportError:
2224
2325 have_memcache = True
2426 try:
25 from cmemcache import memcache
27 import cmemcache as memcache
28 is_cmemcache = True
2629 except ImportError:
2730 try:
2831 import memcache
32 is_cmemcache = False
2933 except ImportError:
3034 have_memcache = False
3135
4145 delete = get
4246
4347 def get_many(self, *keys):
44 return [self.get(key) for key in keys]
48 return map(self.get, keys)
4549
4650 def get_dict(self, *keys):
4751 return dict(izip(keys, self.get_many(keys)))
6165 def clear(self):
6266 pass
6367
68 def inc(self, key, delta=1):
69 self.set(key, (self.get(key) or 0) + delta)
70
71 def dec(self, key, delta=1):
72 self.set(key, (self.get(key) or 0) - delta)
73
6474
6575 class NullCache(BaseCache):
6676 """A cache that doesn't cache."""
6777
6878
6979 class SimpleCache(BaseCache):
70 """
71 Simple memory cache for single process environments. This class exists
80 """Simple memory cache for single process environments. This class exists
7281 mainly for the development server and is not 100% thread safe. It tries
7382 to use as many atomic operations as possible and no locks for simplicity
7483 but it could happen under heavy load that keys are added multiple times.
111120 self._cache.pop(key, None)
112121
113122
123 _test_memcached_key = re.compile(r'[^\x00-\x21\xff]{1,250}$').match
124
114125 class MemcachedCache(BaseCache):
115 """A cache that uses memcached as backend."""
126 """A cache that uses memcached as backend.
127
128 Implementation notes: This cache backend works around some limitations in
129 memcached to simplify the interface. For example unicode keys are encoded
130 to utf-8 on the fly. Methods such as `get_dict` return the keys in the
131 same format as passed. Furthermore all get methods silently ignore key
132 errors to not cause problems when untrusted user data is passed to the get
133 methods which is often the case in web applications.
134 """
116135
117136 def __init__(self, servers, default_timeout=300):
118 BaseClient.__init__(self, default_timeout)
119 self._client = memcache.Client(servers)
137 BaseCache.__init__(self, default_timeout)
138 if not have_memcache:
139 raise RuntimeError('no memcache module found')
140
141 # cmemcache has a bug that debuglog is not defined for the
142 # client. Whenever pickle fails you get a weird AttributError.
143 if is_cmemcache:
144 self._client = memcache.Client(map(str, servers))
145 try:
146 self._client.debuglog = lambda *a: None
147 except:
148 pass
149 else:
150 self._client = memcache.Client(servers, False, HIGHEST_PROTOCOL)
120151
121152 def get(self, key):
122 return self._client.get(key)
153 if isinstance(key, unicode):
154 key = key.encode('utf-8')
155 # memcached doesn't support keys longer than that. Because often
156 # checks for so long keys can occour because it's tested from user
157 # submitted data etc we fail silently for getting.
158 if _test_memcached_key(key):
159 return self._client.get(key)
160
161 def get_dict(self, *keys):
162 key_mapping = {}
163 have_encoded_keys = False
164 for idx, key in enumerate(keys):
165 if isinstance(key, unicode):
166 encoded_key = key.encode('utf-8')
167 have_encoded_keys = True
168 else:
169 encoded_key = key
170 if _test_memcached_key(key):
171 key_mapping[encoded_key] = key
172 # the keys call here is important because otherwise cmemcache
173 # does ugly things. What exaclty I don't know, i think it does
174 # Py_DECREF but quite frankly i don't care.
175 d = rv = self._client.get_multi(key_mapping.keys())
176 if have_encoded_keys:
177 rv = {}
178 for key, value in d.iteritems():
179 rv[key_mapping[key]] = value
180 if len(rv) < len(keys):
181 for key in keys:
182 if key not in rv:
183 rv[key] = None
184 return rv
185
186 def add(self, key, value, timeout=None):
187 if timeout is None:
188 timeout = self.default_timeout
189 if isinstance(key, unicode):
190 key = key.encode('utf-8')
191 self._client.add(key, value, timeout)
192
193 def set(self, key, value, timeout=None):
194 if timeout is None:
195 timeout = self.default_timeout
196 if isinstance(key, unicode):
197 key = key.encode('utf-8')
198 self._client.set(key, value, timeout)
123199
124200 def get_many(self, *keys):
125 return self._client.get_multi(*keys)
126
127 def add(self, key, value, timeout=None):
128 if timeout is None:
129 timeout = self.default_timeout
130 self._client.add(key, value, timeout)
131
132 def set(self, key, value, timeout=None):
133 if timeout is None:
134 timeout = self.default_timeout
135 self._client.set(key, value, timeout)
201 d = self.get_dict(*keys)
202 return [d[key] for key in keys]
136203
137204 def set_many(self, mapping, timeout=None):
138205 if timeout is None:
139206 timeout = self.default_timeout
140 self._client.set_multi(mapping, timeout)
207 new_mapping = {}
208 for key, value in mapping.iteritems():
209 if isinstance(key, unicode):
210 key = key.encode('utf-8')
211 new_mapping[key] = value
212 self._client.set_multi(new_mapping, timeout)
141213
142214 def delete(self, key):
215 if isinstance(key, unicode):
216 key = key.encode('utf-8')
143217 self._client.delete(key)
144218
145219 def delete_many(self, *keys):
220 keys = list(keys)
221 for idx, key in enumerate(keys):
222 if isinstance(key, unicode):
223 keys[idx] = key.encode('utf-8')
146224 self._client.delete_multi(keys)
147225
148226 def clear(self):
149227 self._client.flush_all()
228
229 def inc(self, key, delta=1):
230 if isinstance(key, unicode):
231 key = key.encode('utf-8')
232 self._client.incr(key, key, delta)
233
234 def dec(self, key, delta=1):
235 if isinstance(key, unicode):
236 key = key.encode('utf-8')
237 self._client.decr(key, key, delta)
150238
151239
152240 class FileSystemCache(BaseCache):
1818 added in a trivial way. These loaders provide a template interface similar
1919 to the one that Django uses.
2020
21 :copyright: 2007 by Marek Kubica, Armin Ronacher.
21 :copyright: 2007-2008 by Marek Kubica, Armin Ronacher.
2222 :license: BSD, see LICENSE for more details.
2323 """
2424 from os import path
25 from werkzeug.wrappers import BaseRequest, BaseResponse
25 from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
2626 from werkzeug.templates import Template
2727 from werkzeug.exceptions import HTTPException
2828 from werkzeug.routing import RequestRedirect
2929 from werkzeug.contrib.sessions import FilesystemSessionStore
3030
3131 __all__ = ['Request', 'Response', 'TemplateNotFound', 'TemplateLoader',
32 'GenshiTemplateLoader', 'Application']
33
34
35 class Request(BaseRequest):
36 """
37 A handy subclass of the base request that adds a URL builder.
32 'GenshiTemplateLoader', 'Application']
33
34
35 class Request(RequestBase):
36 """A handy subclass of the base request that adds a URL builder.
3837 It when supplied a session store, it is also able to handle sessions.
3938 """
4039
4140 def __init__(self, environ, url_map,
4241 session_store=None, cookie_name=None):
4342 # call the parent for initialization
44 BaseRequest.__init__(self, environ)
43 RequestBase.__init__(self, environ)
4544 # create an adapter
4645 self.url_adapter = url_map.bind_to_environ(environ)
4746 # create all stuff for sessions
6059 return self.url_adapter.build(callback, values)
6160
6261
63 class Response(BaseResponse):
62 class Response(ResponseBase):
6463 """
6564 A subclass of base response which sets the default mimetype to text/html.
6665 It the `Request` that came in is using Werkzeug sessions, this class
8180 self.set_cookie(request.cookie_name, request.session.sid)
8281
8382 # go on with normal response business
84 return BaseResponse.__call__(self, environ, start_response)
83 return ResponseBase.__call__(self, environ, start_response)
8584
8685
8786 class Processor(object):
88 """
89 A request and response processor - it is what Django calls a middleware,
90 but Werkzeug also includes straight-foward support for real WSGI
91 middlewares, so another name was chosen.
87 """A request and response processor - it is what Django calls a
88 middleware, but Werkzeug also includes straight-foward support for real
89 WSGI middlewares, so another name was chosen.
9290
9391 The code of this processor is derived from the example in the Werkzeug
9492 trac, called `Request and Response Processor
102100 return response
103101
104102 def process_view(self, request, view_func, view_args, view_kwargs):
105 """
106 process_view() is called just before the Application calls the
103 """process_view() is called just before the Application calls the
107104 function specified by view_func.
108105
109106 If this returns None, the Application processes the next Processor,
117114
118115
119116 class Application(object):
120 """
121 A generic WSGI application which can be used to start with Werkzeug in an
122 easy, straightforward way.
117 """A generic WSGI application which can be used to start with Werkzeug in
118 an easy, straightforward way.
123119 """
124120
125121 def __init__(self, name, url_map, session=False, processors=None):
234230
235231
236232 class GenshiTemplateLoader(TemplateLoader):
237 """
238 A unified interface for loading Genshi templates. Actually a quite thin
233 """A unified interface for loading Genshi templates. Actually a quite thin
239234 wrapper for Genshi's TemplateLoader.
240235
241236 It sets some defaults that differ from the Genshi loader, most notably
281276 context = context or {}
282277 tmpl = self.get_template(template_name)
283278 # render the template into a unicode string (None means unicode)
284 return tmpl.\
285 generate(**context).\
279 return tmpl. \
280 generate(**context). \
286281 render(self.output_type, encoding=None)
2020
2121
2222 class MergeStream(object):
23 """
24 A object that redirects `write` calls to multiple streams.
23 """An object that redirects `write` calls to multiple streams.
2524 Use this to log to both `sys.stdout` and a file::
2625
2726 f = file('profiler.log')
4039
4140
4241 class ProfilerMiddleware(object):
43 """
44 Simple profiler middleware
45 """
42 """Simple profiler middleware."""
4643
4744 def __init__(self, app, stream=None,
4845 sort_by=('time', 'calls'), restrictions=()):
8481 def make_action(app_factory, hostname='localhost', port=5000,
8582 threaded=False, processes=1, stream=None,
8683 sort_by=('time', 'calls'), restrictions=()):
87 """
88 Return a new callback for werkzeug scripts that starts a local server
89 for profiling.
84 """Return a new callback for werkzeug scripts that starts a local
85 server for profiling.
9086 """
9187 def action(hostname=('h', hostname), port=('p', port),
9288 threaded=threaded, processes=processes):
108108
109109
110110 class SecureCookie(ModificationTrackingDict):
111 """
112 Represents a secure cookie. You can subclass this class and provide
111 """Represents a secure cookie. You can subclass this class and provide
113112 an alternative hash method. The import thing is that the hash method
114113 is a function with a similar interface to the hashlib. Required
115114 methods are update() and digest().
140139 return generate_key()[:4]
141140
142141 def serialize(self, expires=None):
143 """
144 Serialize the secure cookie into a string.
142 """Serialize the secure cookie into a string.
145143
146144 If expires is provided, the session will be automatically invalidated
147145 after expiration when you unseralize it. This provides better
214212 unserialize = classmethod(unserialize)
215213
216214 def load_cookie(cls, request, key='session', secret_key=None):
217 """
218 Loads a SecureCookie from a cookie in request. If the cookie is not
215 """Loads a SecureCookie from a cookie in request. If the cookie is not
219216 set, a new SecureCookie instanced is returned.
220217 """
221218 data = request.cookies.get(key)
148148
149149
150150 class SessionStore(object):
151 """
152 Baseclass for all session stores. The Werkzeug contrib module does not
151 """Baseclass for all session stores. The Werkzeug contrib module does not
153152 implement any useful stores beside the filesystem store, application
154153 developers are encouraged to create their own stores.
155154 """
183182 """Delete a session."""
184183
185184 def get(self, sid):
186 """
187 Get a session for this sid or a new session object. This method has
188 to check if the session key is valid and create a new session if it
185 """Get a session for this sid or a new session object. This method
186 has to check if the session key is valid and create a new session if
189187 that wasn't the case.
190188 """
191189 return self.session_class({}, sid, True)
192190
193191
194192 class FilesystemSessionStore(SessionStore):
195 """
196 Simple example session store that saves session on the filesystem like
193 """Simple example session store that saves session on the filesystem like
197194 PHP does.
198195 """
199196
237234
238235
239236 class SessionMiddleware(object):
240 """
241 A simple middleware that puts the session object of a store provided into
242 the WSGI environ. It automatically sets cookies and restores sessions.
237 """A simple middleware that puts the session object of a store provided
238 into the WSGI environ. It automatically sets cookies and restores
239 sessions.
243240
244241 However a middleware is not the preferred solution because it won't be as
245242 fast as sessions managed by the application itself and will put a key into
251248 cookie_age=None, cookie_expires=None, cookie_path='/',
252249 cookie_domain=None, cookie_secure=None,
253250 cookie_httponly=False, environ_key='werkzeug.session'):
254 """
255 The cookie parameters are the same as for the `dump_cookie` function
256 just prefixed with "cookie_". Additionally "max_age" is "cookie_age"
257 for backwards compatibility.
251 """The cookie parameters are the same as for the `dump_cookie`
252 function just prefixed with "cookie_". Additionally "max_age" is
253 called "cookie_age" and not "cookie_max_age" because of backwards
254 compatibility.
258255 """
259256 self.app = app
260257 self.store = store
77 a response wrapper wich adds various cached attributes for
88 simplified assertions on various contenttypes
99
10
1011 :copyright: 2007 by Ronny Pfannschmidt.
1112 :license: BSD, see LICENSE for more details.
1213 """
1415
1516
1617 class ContentAccessors(object):
18 """
19 A mixin class for response objects that provides a couple of useful
20 accessors for unittesting.
21 """
1722
1823 def xml(self):
1924 """Get an etree if possible."""
5459
5560
5661 class TestResponse(Response, ContentAccessors):
57 """
58 Pass this to `werkzeug.test.Client` for easier unittesting.
59 """
62 """Pass this to `werkzeug.test.Client` for easier unittesting."""
44
55 WSGI application traceback debugger.
66
7 :copyright: 2007 by Georg Brandl, Armin Ronacher.
7 :copyright: 2008 by Georg Brandl, Armin Ronacher.
88 :license: BSD, see LICENSE for more details.
99 """
10 import sys
11 import inspect
12 import traceback
13 import code
14
15 from werkzeug.debug.render import debug_page, load_resource
16 from werkzeug.debug.util import ThreadedStream, Namespace, get_uid, \
17 get_frame_info, ExceptionRepr
18 from werkzeug.utils import url_decode
10 from os.path import join, dirname, basename, isfile
11 from mimetypes import guess_type
12 from werkzeug.wrappers import BaseRequest as Request, BaseResponse as Response
13 from werkzeug.debug.repr import debug_repr
14 from werkzeug.debug.tbtools import get_current_traceback
15 from werkzeug.debug.console import Console
16 from werkzeug.debug.utils import render_template
1917
2018
21 try:
22 system_exceptions = (GeneratorExit,)
23 except NameError:
24 system_exceptions = ()
25 system_exceptions += (SystemExit, KeyboardInterrupt)
19 class _ConsoleFrame(object):
20 """Helper class so that we can reuse the frame console code for the
21 standalone console.
22 """
23
24 def __init__(self, namespace):
25 self.console = Console(namespace)
26 self.id = 0
2627
2728
2829 class DebuggedApplication(object):
29 """
30 Enables debugging support for a given application::
30 """Enables debugging support for a given application::
3131
3232 from werkzeug.debug import DebuggedApplication
3333 from myapp import app
3939 THIS IS A GAPING SECURITY HOLE IF PUBLICLY ACCESSIBLE!
4040 """
4141
42 def __init__(self, application, evalex=False,
43 request_key='werkzeug.request'):
44 self.evalex = bool(evalex)
45 self.application = application
42 def __init__(self, app, evalex=False, request_key='werkzeug.request',
43 console_path='/console', console_init_func=dict,
44 show_hidden_frames=False):
45 self.app = app
46 self.evalex = evalex
47 self.frames = {}
48 self.tracebacks = {}
4649 self.request_key = request_key
47 self.tracebacks = {}
50 self.console_path = console_path
51 self.console_init_func = console_init_func
52 self.show_hidden_frames = show_hidden_frames
53
54 def debug_application(self, environ, start_response):
55 """Run the application and conserve the traceback frames."""
56 app_iter = None
57 try:
58 app_iter = self.app(environ, start_response)
59 for item in app_iter:
60 yield item
61 if hasattr(app_iter, 'close'):
62 app_iter.close()
63 except:
64 if hasattr(app_iter, 'close'):
65 app_iter.close()
66 traceback = get_current_traceback(skip=1, show_hidden_frames=
67 self.show_hidden_frames)
68 for frame in traceback.frames:
69 self.frames[frame.id] = frame
70 self.tracebacks[traceback.id] = traceback
71
72 try:
73 start_response('500 INTERNAL SERVER ERROR', [
74 ('Content-Type', 'text/html; charset=utf-8')
75 ])
76 except:
77 # if we end up here there has been output but an error
78 # occurred. in that situation we can do nothing fancy any
79 # more, better log something into the error log and fall
80 # back gracefully.
81 environ['wsgi.errors'].write(
82 '\nDebugging middlware catched exception in streamed '
83 'reponse a point where response headers were already '
84 'sent.\n')
85 traceback.log(environ['wsgi.errors'])
86 return
87
88 yield traceback.render_full(evalex=self.evalex) \
89 .encode('utf-8', 'replace')
90 traceback.log(environ['wsgi.errors'])
91
92 def execute_command(self, request, command, frame):
93 """Execute a command in a console."""
94 return Response(frame.console.eval(command), mimetype='text/html')
95
96 def display_console(self, request):
97 """Display a standalone shell."""
98 if 0 not in self.frames:
99 self.frames[0] = _ConsoleFrame(self.console_init_func())
100 return Response(render_template('console.html'), mimetype='text/html')
101
102 def paste_traceback(self, request, traceback):
103 """Paste the traceback and return a JSON response."""
104 paste_id = traceback.paste()
105 return Response('{"url": "http://paste.pocoo.org/show/%d/", "id": %d}'
106 % (paste_id, paste_id), mimetype='application/json')
107
108 def get_source(self, request, frame):
109 """Render the source viewer."""
110 return Response(frame.render_source(), mimetype='text/html')
111
112 def get_resource(self, request, filename):
113 """Return a static resource from the shared folder."""
114 filename = join(dirname(__file__), 'shared', basename(filename))
115 if isfile(filename):
116 mimetype = guess_type(filename)[0] or 'application/octet-stream'
117 f = file(filename, 'rb')
118 try:
119 return Response(f.read(), mimetype=mimetype)
120 finally:
121 f.close()
122 return Response('Not Found', status=404)
48123
49124 def __call__(self, environ, start_response):
50 # exec code in open tracebacks or provide shared data
51 if environ.get('PATH_INFO', '').strip('/').endswith('__traceback__'):
52 parameters = url_decode(environ.get('QUERY_STRING', ''))
53 # shared data
54 if 'resource' in parameters and 'mimetype' in parameters:
55 data = load_resource(parameters['resource'])
56 start_response('200 OK', [
57 ('Content-Type', str(parameters['mimetype'])),
58 ('Content-Length', str(len(data)))
59 ])
60 yield data
61 return
62 # pastebin
63 elif parameters.get('pastetb'):
64 from xmlrpclib import ServerProxy
65 try:
66 length = int(environ['CONTENT_LENGTH'])
67 except (KeyError, ValueError):
68 length = 0
69 data = environ['wsgi.input'].read(length)
70 s = ServerProxy('http://paste.pocoo.org/xmlrpc/')
71 paste_id = s.pastes.newPaste('pytb', data)
72 start_response('200 OK', [('Content-Type', 'text/plain')])
73 yield '{"paste_id": %d, "url": "%s"}' % (
74 paste_id,
75 'http://paste.pocoo.org/show/%d' % paste_id
76 )
77 return
78 # execute commands in an existing debug context
79 elif self.evalex:
80 try:
81 tb = self.tracebacks[parameters['tb']]
82 frame = parameters['frame']
83 context = tb[frame]
84 code = parameters['code']
85 except (IndexError, KeyError):
86 pass
87 else:
88 result = context.exec_expr(code)
89 start_response('200 OK', [('Content-Type', 'text/plain')])
90 yield result
91 return
92
93 # wrap the application and catch errors.
94 appiter = None
95 try:
96 appiter = self.application(environ, start_response)
97 for line in appiter:
98 yield line
99 except system_exceptions, e:
100 raise e
101 except:
102 ThreadedStream.install()
103 exc_info = sys.exc_info()
104 try:
105 headers = [('Content-Type', 'text/html; charset=utf-8')]
106 start_response('500 INTERNAL SERVER ERROR', headers)
107 except:
108 pass
109 debug_context = self.create_debug_context(environ, exc_info)
110 yield debug_page(debug_context)
111
112 if hasattr(appiter, 'close'):
113 appiter.close()
114
115 def format_exception(self, exc_info):
116 """Format a text/plain traceback."""
117 return self.create_debug_context({
118 'wsgi.run_once': True
119 }, exc_info, True).plaintb + '\n'
120
121 def create_debug_context(self, environ, exc_info, simple=False):
122 exception_type, exception_value, tb = exc_info
123 # skip first internal frame
124 if not tb.tb_next is None:
125 tb = tb.tb_next
126
127 # load frames
128 frames = []
129 frame_map = {}
130 tb_uid = None
131 if not environ['wsgi.run_once'] and not environ['wsgi.multiprocess']:
132 tb_uid = get_uid()
133 frame_map = self.tracebacks[tb_uid] = {}
134
135 plaintb_buffer = ['Traceback (most recent call last):']
136 write = plaintb_buffer.append
137
138 # walk through frames and collect information
139 while tb is not None:
140 if not tb.tb_frame.f_locals.get('__traceback_hide__', False):
141 if tb_uid and not simple:
142 frame_uid = get_uid()
143 frame_map[frame_uid] = InteractiveDebugger(self,
144 tb.tb_frame)
145 else:
146 frame_uid = None
147 frame = get_frame_info(tb, simple=simple)
148 frame['frame_uid'] = frame_uid
149 frames.append(frame)
150 write(' File "%s", line %s, in %s' % (
151 frame['filename'],
152 frame['lineno'],
153 frame['function']
154 ))
155 if frame['raw_context_line'] is None:
156 write(' <no sourcecode available>')
157 else:
158 write(' ' + frame['raw_context_line'])
159 tb = tb.tb_next
160
161 # guard for string exceptions
162 if isinstance(exception_type, str):
163 extypestr = 'string exception'
164 exception_value = exception_type
165 elif exception_type.__module__ == 'exceptions':
166 extypestr = exception_type.__name__
167 else:
168 extypestr = '%s.%s' % (
169 exception_type.__module__,
170 exception_type.__name__
171 )
172
173 # finialize plain traceback and write it to stderr
174 try:
175 if isinstance(exception_value, unicode):
176 exception_value = exception_value.encode('utf-8')
177 else:
178 exception_value = str(exception_value)
179 exvalstr = ': ' + exception_value
180 except:
181 exvalstr = ''
182 write(extypestr + exvalstr)
183 plaintb = '\n'.join(plaintb_buffer)
184
185 if not simple:
186 environ['wsgi.errors'].write(plaintb)
187
188 # support for the werkzeug request object or fall back to
189 # WSGI environment
190 req_vars = []
191 if not simple:
192 request = environ.get(self.request_key)
193 if request is not None:
194 for varname in dir(request):
195 if varname.startswith('_'):
196 continue
197 try:
198 value = getattr(request, varname)
199 except Exception, err:
200 value = ExceptionRepr(err)
201 if not hasattr(value, 'im_func'):
202 req_vars.append((varname, value))
203 else:
204 req_vars.append(('WSGI Environ', environ))
205
206 return Namespace(
207 evalex = self.evalex,
208 exception_type = extypestr,
209 exception_value = exception_value,
210 frames = frames,
211 last_frame = frames[-1],
212 plaintb = plaintb,
213 tb_uid = tb_uid,
214 frame_map = frame_map,
215 req_vars = req_vars,
216 )
217
218
219 class InteractiveDebugger(code.InteractiveInterpreter):
220 """
221 Subclass of the python interactive interpreter that
222 automatically captures stdout and buffers older input.
223 """
224
225 def __init__(self, middleware, frame):
226 self.middleware = middleware
227 self.globals = frame.f_globals
228 code.InteractiveInterpreter.__init__(self, frame.f_locals)
229 self.more = False
230 self.buffer = []
231
232 def runsource(self, source):
233 if isinstance(source, unicode):
234 source = source.encode('utf-8')
235 source = source.rstrip() + '\n'
236 ThreadedStream.push()
237 prompt = self.more and '... ' or '>>> '
238 try:
239 source_to_eval = ''.join(self.buffer + [source])
240 if code.InteractiveInterpreter.runsource(self,
241 source_to_eval, '<debugger>', 'single'):
242 self.more = True
243 self.buffer.append(source)
244 else:
245 self.more = False
246 del self.buffer[:]
247 finally:
248 return prompt + source + ThreadedStream.fetch()
249
250 def runcode(self, code):
251 try:
252 exec code in self.globals, self.locals
253 except:
254 self.write(self.middleware.format_exception(sys.exc_info()))
255
256 def write(self, data):
257 sys.stdout.write(data)
258
259 def exec_expr(self, code):
260 rv = self.runsource(code)
261 if isinstance(rv, unicode):
262 return rv.encode('utf-8')
263 return rv
125 """Dispatch the requests."""
126 # important: don't ever access a function here that reads the incoming
127 # form data! Otherwise the application won't have access to that data
128 # any more!
129 request = Request(environ)
130 response = self.debug_application
131 if self.evalex and self.console_path is not None and \
132 request.path == self.console_path:
133 response = self.display_console(request)
134 elif request.path.rstrip('/').endswith('/__debugger__'):
135 cmd = request.args.get('cmd')
136 arg = request.args.get('f')
137 traceback = self.tracebacks.get(request.args.get('tb', type=int))
138 frame = self.frames.get(request.args.get('frm', type=int))
139 if cmd == 'resource' and arg:
140 response = self.get_resource(request, arg)
141 elif cmd == 'paste' and traceback is not None:
142 response = self.paste_traceback(request, traceback)
143 elif cmd == 'source' and frame:
144 response = self.get_source(request, frame)
145 elif self.evalex and cmd is not None and frame is not None:
146 response = self.execute_command(request, cmd, frame)
147 return response(environ, start_response)
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.debug.console
3 ~~~~~~~~~~~~~~~~~~~~~~
4
5 Interactive console support.
6
7 :copyright: Copyright 2008 by Armin Ronacher.
8 :license: BSD.
9 """
10 import sys
11 import code
12 from types import CodeType
13 from cgi import escape
14 from werkzeug.local import Local
15 from werkzeug.debug.repr import debug_repr, dump, helper
16 from werkzeug.debug.utils import render_template
17
18
19 _local = Local()
20
21
22 class HTMLStringO(object):
23 """A StringO version that HTML escapes on write."""
24
25 def __init__(self):
26 self._buffer = []
27
28 def isatty(self):
29 return False
30
31 def close(self):
32 pass
33
34 def reset(self):
35 val = ''.join(self._buffer)
36 del self._buffer[:]
37 return val
38
39 def _write(self, x):
40 if isinstance(x, str):
41 x = x.decode('utf-8', 'replace')
42 self._buffer.append(x)
43
44 def write(self, x):
45 self._write(escape(x))
46
47 def writelines(self, x):
48 self._write(escape(''.join(x)))
49
50
51 class ThreadedStream(object):
52 """Thread-local wrapper for sys.stdout for the interactive console."""
53
54 def push():
55 if sys.stdout is sys.__stdout__:
56 sys.stdout = ThreadedStream()
57 _local.stream = HTMLStringO()
58 push = staticmethod(push)
59
60 def fetch():
61 try:
62 stream = _local.stream
63 except AttributeError:
64 return ''
65 return stream.reset()
66 fetch = staticmethod(fetch)
67
68 def displayhook(obj):
69 try:
70 stream = _local.stream
71 except AttributeError:
72 return _displayhook(obj)
73 # stream._write bypasses escaping as debug_repr is
74 # already generating HTML for us.
75 if obj is not None:
76 stream._write(debug_repr(obj))
77 displayhook = staticmethod(displayhook)
78
79 def __setattr__(self, name, value):
80 raise AttributeError('read only attribute %s' % name)
81
82 def __dir__(self):
83 return dir(sys.__stdout__)
84
85 def __getattribute__(self, name):
86 if name == '__members__':
87 return dir(sys.__stdout__)
88 try:
89 stream = _local.stream
90 except AttributeError:
91 stream = sys.__stdout__
92 return getattr(stream, name)
93
94 def __repr__(self):
95 return repr(sys.__stdout__)
96
97
98 # add the threaded stream as display hook
99 _displayhook = sys.displayhook
100 sys.displayhook = ThreadedStream.displayhook
101
102
103 class _ConsoleLoader(object):
104
105 def __init__(self):
106 self._storage = {}
107
108 def register(self, code, source):
109 self._storage[id(code)] = source
110 # register code objects of wrapped functions too.
111 for var in code.co_consts:
112 if isinstance(var, CodeType):
113 self._storage[id(var)] = source
114
115 def get_source_by_code(self, code):
116 try:
117 return self._storage[id(code)]
118 except KeyError:
119 pass
120
121
122 def _wrap_compiler(console):
123 compile = console.compile
124 def func(source, filename, symbol):
125 code = compile(source, filename, symbol)
126 console.loader.register(code, source)
127 return code
128 console.compile = func
129
130
131 class _InteractiveConsole(code.InteractiveInterpreter):
132
133 def __init__(self, globals, locals):
134 code.InteractiveInterpreter.__init__(self, locals)
135 self.globals = dict(globals)
136 self.globals['dump'] = dump
137 self.globals['help'] = helper
138 self.globals['__loader__'] = self.loader = _ConsoleLoader()
139 self.more = False
140 self.buffer = []
141 _wrap_compiler(self)
142
143 def runsource(self, source):
144 source = source.rstrip() + '\n'
145 ThreadedStream.push()
146 prompt = self.more and '... ' or '>>> '
147 try:
148 source_to_eval = ''.join(self.buffer + [source])
149 if code.InteractiveInterpreter.runsource(self,
150 source_to_eval, '<debugger>', 'single'):
151 self.more = True
152 self.buffer.append(source)
153 else:
154 self.more = False
155 del self.buffer[:]
156 finally:
157 output = ThreadedStream.fetch()
158 return prompt + source + output
159
160 def runcode(self, code):
161 try:
162 exec code in self.globals, self.locals
163 except:
164 self.showtraceback()
165
166 def showtraceback(self):
167 from werkzeug.debug.tbtools import get_current_traceback
168 tb = get_current_traceback(skip=1)
169 sys.stdout._write(tb.render_summary())
170
171 def showsyntaxerror(self, filename=None):
172 from werkzeug.debug.tbtools import get_current_traceback
173 tb = get_current_traceback(skip=4)
174 sys.stdout._write(tb.render_summary())
175
176 def write(self, data):
177 sys.stdout.write(data)
178
179
180 class Console(object):
181 """An interactive console."""
182
183 def __init__(self, globals=None, locals=None):
184 if locals is None:
185 locals = {}
186 if globals is None:
187 globals = {}
188 self._ipy = _InteractiveConsole(globals, locals)
189
190 def eval(self, code):
191 return self._ipy.runsource(code)
1616
1717 def get_template(name):
1818 return Template.from_file(join(dirname(__file__), 'shared', name),
19 unicode_mode=False)
19 unicode_mode=False, errors='ignore')
2020
2121
2222 def load_resource(res):
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.debug.repr
3 ~~~~~~~~~~~~~~~~~~~
4
5 This module implements object representations for debugging purposes.
6 Unlike the default repr these reprs expose a lot more information and
7 produce HTML instead of ASCII.
8
9 Together with the CSS and JavaScript files of the debugger this gives
10 a colorful and more compact output.
11
12 :copyright: Copyright 2008 by Armin Ronacher.
13 :license: BSD.
14 """
15 import sys
16 import re
17 from types import InstanceType
18 from traceback import format_exception_only
19 try:
20 from collections import deque
21 except ImportError:
22 deque = None
23 from cgi import escape
24 try:
25 set
26 except NameError:
27 from sets import Set as set, ImmutableSet as frozenset
28 from werkzeug.debug.utils import render_template
29
30
31 missing = object()
32 _paragraph_re = re.compile(r'(?:\r\n|\r|\n){2,}')
33 RegexType = type(_paragraph_re)
34
35
36 def debug_repr(obj):
37 """Creates a debug repr of an object as HTML unicode string."""
38 return DebugReprGenerator().repr(obj)
39
40
41 def dump(obj=missing):
42 """Print the object details to stdout._write (for the interactive
43 console of the web debugger.
44 """
45 gen = DebugReprGenerator()
46 if obj is missing:
47 rv = gen.dump_locals(sys._getframe(1).f_locals)
48 else:
49 rv = gen.dump_object(obj)
50 sys.stdout._write(rv)
51
52
53 class _Helper(object):
54 """Displays an HTML version of the normal help, for the interactive
55 debugger only because it requirse a patched sys.stdout.
56 """
57
58 def __call__(self, topic=None):
59 sys.stdout._write(self.get_help(topic))
60
61 def get_help(self, topic):
62 title = text = None
63 if topic is not None:
64 import pydoc
65 pydoc.help(topic)
66 rv = sys.stdout.reset().decode('utf-8', 'ignore')
67 paragraphs = _paragraph_re.split(rv)
68 if len(paragraphs) > 1:
69 title = paragraphs[0]
70 text = '\n\n'.join(paragraphs[1:])
71 else:
72 title = 'Help'
73 text = paragraphs[0]
74 return render_template('help_command.html', title=title, text=text)
75
76 helper = _Helper()
77
78
79 def _add_subclass_info(inner, obj, base):
80 if isinstance(base, tuple):
81 for base in base:
82 if type(obj) is base:
83 return inner
84 elif type(obj) is base:
85 return inner
86 module = ''
87 if obj.__class__.__module__ not in ('__builtin__', 'exceptions'):
88 module = '<span class="module">%s.</span>' % obj.__class__.__module__
89 return '%s%s(%s)' % (module, obj.__class__.__name__, inner)
90
91
92 class DebugReprGenerator(object):
93
94 def __init__(self):
95 self._stack = []
96
97 def _sequence_repr_maker(left, right, base=object(), limit=8):
98 def proxy(self, obj, recursive):
99 if recursive:
100 return _add_subclass_info(left + '...' + right, obj, base)
101 buf = [left]
102 have_extended_section = False
103 for idx, item in enumerate(obj):
104 if idx:
105 buf.append(', ')
106 if idx == limit:
107 buf.append('<span class="extended">')
108 have_extended_section = True
109 buf.append(self.repr(item))
110 if have_extended_section:
111 buf.append('</span>')
112 buf.append(right)
113 return _add_subclass_info(u''.join(buf), obj, base)
114 return proxy
115
116 list_repr = _sequence_repr_maker('[', ']', list)
117 tuple_repr = _sequence_repr_maker('(', ')', tuple)
118 set_repr = _sequence_repr_maker('set([', '])', set)
119 frozenset_repr = _sequence_repr_maker('frozenset([', '])', frozenset)
120 if deque is not None:
121 deque_repr = _sequence_repr_maker('<span class="module">collections.'
122 '</span>deque([', '])', deque)
123 del _sequence_repr_maker
124
125 def regex_repr(self, obj):
126 pattern = repr(obj.pattern).decode('string-escape', 'ignore')
127 if pattern[:1] == 'u':
128 pattern = 'ur' + pattern[1:]
129 else:
130 pattern = 'r' + pattern
131 return u're.compile(<span class="string regex">%s</span>)' % pattern
132
133 def string_repr(self, obj, limit=70):
134 buf = ['<span class="string">']
135 escaped = escape(obj)
136 a = repr(escaped[:limit])
137 b = repr(escaped[limit:])
138 if isinstance(obj, unicode):
139 buf.append('u')
140 a = a[1:]
141 b = b[1:]
142 if b != "''":
143 buf.extend((a[:-1], '<span class="extended">', b[1:], '</span>'))
144 else:
145 buf.append(a)
146 buf.append('</span>')
147 return _add_subclass_info(u''.join(buf), obj, (str, unicode))
148
149 def dict_repr(self, d, recursive, limit=5):
150 if recursive:
151 return _add_subclass_info(u'{...}', d, dict)
152 buf = ['{']
153 have_extended_section = False
154 for idx, (key, value) in enumerate(d.iteritems()):
155 if idx:
156 buf.append(', ')
157 if idx == limit - 1:
158 buf.append('<span class="extended">')
159 have_extended_section = True
160 buf.append('<span class="pair"><span class="key">%s</span>: '
161 '<span class="value">%s</span></span>' %
162 (self.repr(key), self.repr(value)))
163 if have_extended_section:
164 buf.append('</span>')
165 buf.append('}')
166 return _add_subclass_info(u''.join(buf), d, dict)
167
168 def object_repr(self, obj):
169 return u'<span class="object">%s</span>' % \
170 escape(repr(obj).decode('utf-8', 'replace'))
171
172 def dispatch_repr(self, obj, recursive):
173 if obj is helper:
174 return helper.get_help(None)
175 if isinstance(obj, (int, long, float, complex)):
176 return u'<span class="number">%r</span>' % obj
177 if isinstance(obj, basestring):
178 return self.string_repr(obj)
179 if isinstance(obj, RegexType):
180 return self.regex_repr(obj)
181 if isinstance(obj, list):
182 return self.list_repr(obj, recursive)
183 if isinstance(obj, tuple):
184 return self.tuple_repr(obj, recursive)
185 if isinstance(obj, set):
186 return self.set_repr(obj, recursive)
187 if isinstance(obj, frozenset):
188 return self.frozenset_repr(obj, recursive)
189 if isinstance(obj, dict):
190 return self.dict_repr(obj, recursive)
191 if deque is not None and isinstance(obj, deque):
192 return self.deque_repr(obj, recursive)
193 return self.object_repr(obj)
194
195 def fallback_repr(self):
196 try:
197 info = ''.join(format_exception_only(*sys.exc_info()[:2]))
198 except:
199 info = '?'
200 return u'<span class="brokenrepr">&lt;broken repr (%s)&gt;' \
201 u'</span>' % escape(info.decode('utf-8', 'ignore').strip())
202
203 def repr(self, obj):
204 recursive = False
205 for item in self._stack:
206 if item is obj:
207 recursive = True
208 break
209 self._stack.append(obj)
210 try:
211 try:
212 return self.dispatch_repr(obj, recursive)
213 except:
214 return self.fallback_repr()
215 finally:
216 self._stack.pop()
217
218 def dump_object(self, obj):
219 repr = items = None
220 if isinstance(obj, dict):
221 title = 'Contents of'
222 items = []
223 for key, value in obj.iteritems():
224 if not isinstance(key, basestring):
225 items = None
226 break
227 items.append((key, self.repr(value)))
228 if items is None:
229 items = []
230 repr = self.repr(obj)
231 for key in dir(obj):
232 try:
233 items.append((key, self.repr(getattr(obj, key))))
234 except:
235 pass
236 title = 'Details for'
237 title += ' ' + object.__repr__(obj)[1:-1]
238 return render_template('dump_object.html', items=items,
239 title=title, repr=repr)
240
241 def dump_locals(self, d):
242 items = [(key, self.repr(value)) for key, value in d.items()]
243 return render_template('dump_object.html', items=items,
244 title='Local variables in frame', repr=None)
0 HISTORY = {};
1 HISTORY_POSITIONS = {};
0 $(function() {
1 var sourceView = null;
22
3 function changeTB() {
4 $('#interactive').slideToggle('fast');
5 $('#plain').slideToggle('fast');
6 }
3 /**
4 * if we are in console mode, show the console.
5 */
6 if (CONSOLE_MODE && EVALEX) {
7 openShell(null, $('div.console div.inner').empty(), 0);
8 }
79
8 function toggleFrameVars(num) {
9 $('#frame-' + num + ' .vars').slideToggle('fast');
10 }
10 $('div.traceback div.frame').each(function() {
11 var
12 target = $('pre', this)
13 .click(function() {
14 sourceButton.click();
15 }),
16 consoleNode = null, source = null,
17 frameID = this.id.substring(6);
1118
12 function toggleInterpreter(num) {
13 $('#frame-' + num + ' .exec_code').slideToggle('fast', function() {
14 if ($(this).css('display') == 'block')
15 $('input.input', this).focus();
19 /**
20 * Add an interactive console to the frames
21 */
22 if (EVALEX)
23 $('<img src="./__debugger__?cmd=resource&f=console.png">')
24 .attr('title', 'Open an interactive python shell in this frame')
25 .click(function() {
26 consoleNode = openShell(consoleNode, target, frameID);
27 return false;
28 })
29 .prependTo(target);
30
31 /**
32 * Show sourcecode
33 */
34 var sourceButton = $('<img src="./__debugger__?cmd=resource&f=source.png">')
35 .attr('title', 'Display the sourcecode for this frame')
36 .click(function() {
37 if (!sourceView)
38 $('h2', sourceView =
39 $('<div class="box"><h2>View Source</h2><div class="sourceview">' +
40 '<table></table></div>')
41 .insertBefore('div.explanation'))
42 .css('cursor', 'pointer')
43 .click(function() {
44 sourceView.slideUp('fast');
45 });
46 $.get('./__debugger__', {cmd: 'source', frm: frameID}, function(data) {
47 $('table', sourceView)
48 .replaceWith(data);
49 if (!sourceView.is(':visible'))
50 sourceView.slideDown('fast', function() {
51 focusSourceBlock();
52 });
53 else
54 focusSourceBlock();
55 });
56 return false;
57 })
58 .prependTo(target);
59 });
60
61 /**
62 * toggle traceback types on click.
63 */
64 $('h2.traceback').click(function() {
65 $(this).next().slideToggle('fast');
66 $('div.plain').slideToggle('fast');
67 }).css('cursor', 'pointer');
68 $('div.plain').hide();
69
70 /**
71 * Add extra info (this is here so that only users with JavaScript
72 * enabled see it.)
73 */
74 $('span.nojavascript')
75 .removeClass('nojavascript')
76 .text('To switch between the interactive traceback and the plaintext ' +
77 'one, you can click on the "Traceback" headline. From the text ' +
78 'traceback you can also create a paste of it. For code execution ' +
79 'mouse-over the frame you want to debug and click on the console ' +
80 'icon on the right side.');
81
82 /**
83 * Add the pastebin feature
84 */
85 $('div.plain form')
86 .submit(function() {
87 $('input[@type="submit"]', this).val('submitting...');
88 $.ajax({
89 dataType: 'json',
90 url: './__debugger__',
91 data: {tb: TRACEBACK, cmd: 'paste'},
92 success: function(data) {
93 console.log(data);
94 $('div.plain span.pastemessage')
95 .removeClass('pastemessage')
96 .text('Paste created: ')
97 .append($('<a>#' + data.id + '</a>').attr('href', data.url));
98 }});
99 return false;
100 });
101
102 // if we have javascript we submit by ajax anyways, so no need for the
103 // not scaling textarea.
104 var plainTraceback = $('div.plain textarea');
105 plainTraceback.replaceWith($('<pre>').text(plainTraceback.text()));
106 });
107
108
109 /**
110 * Helper function for shell initialization
111 */
112 function openShell(consoleNode, target, frameID) {
113 if (consoleNode)
114 return consoleNode.slideToggle('fast');
115 consoleNode = $('<pre class="console">')
116 .appendTo(target.parent())
117 .hide()
118 var historyPos = 0, history = [''];
119 var output = $('<div class="output">[console ready]</div>')
120 .appendTo(consoleNode);
121 var form = $('<form>&gt;&gt;&gt; </form>')
122 .submit(function() {
123 var cmd = command.val();
124 $.get('./__debugger__', {cmd: cmd, frm: frameID}, function(data) {
125 var tmp = $('<div>').html(data);
126 $('span.extended', tmp).each(function() {
127 var hidden = $(this).wrap('<span>').hide();
128 hidden
129 .parent()
130 .append($('<a href="#" class="toggle">&nbsp;&nbsp;</a>')
131 .click(function() {
132 hidden.toggle();
133 $(this).toggleClass('open')
134 return false;
135 }));
136 });
137 output.append(tmp);
138 command.focus();
139 var old = history.pop();
140 history.push(cmd);
141 if (typeof old != 'undefined')
142 history.push(old);
143 historyPos = history.length - 1;
144 });
145 command.val('');
146 return false;
147 }).
148 appendTo(consoleNode);
149
150 var command = $('<input type="text">')
151 .appendTo(form)
152 .keypress(function(e) {
153 if (e.charCode == 100 && e.ctrlKey) {
154 output.text('--- screen cleared ---');
155 return false;
156 }
157 else if (e.charCode == 0 && (e.keyCode == 38 || e.keyCode == 40)) {
158 if (e.keyCode == 38 && historyPos > 0)
159 historyPos--;
160 else if (e.keyCode == 40 && historyPos < history.length)
161 historyPos++;
162 command.val(history[historyPos]);
163 return false;
164 }
165 });
166
167 return consoleNode.slideDown('fast', function() {
168 command.focus();
16169 });
17170 }
18171
19 function toggleTableVars(num) {
20 $('#tvar-' + num + ' .vars').slideToggle('fast');
172 /**
173 * Focus the current block in the source view.
174 */
175 function focusSourceBlock() {
176 var tmp, line = $('table.source tr.current');
177 for (var i = 0; i < 7; i++) {
178 tmp = line.prev();
179 if (!(tmp && tmp.is('.in-frame')))
180 break
181 line = tmp;
182 }
183 var container = $('div.sourceview')[0];
184 container.scrollTop = line.offset().top - container.offsetTop;
21185 }
22
23 function getHistory(tb, frame) {
24 var key = tb + '||' + frame;
25 if (key in HISTORY)
26 var h = HISTORY[key];
27 else {
28 var h = HISTORY[key] = [''];
29 HISTORY_POSITIONS[key] = 0;
30 }
31 return {
32 history: h,
33 setPos: function(val) {
34 HISTORY_POSITIONS[key] = val;
35 },
36 getPos: function() {
37 return HISTORY_POSITIONS[key];
38 },
39 getCurrent: function() {
40 return h[HISTORY_POSITIONS[key]];
41 }
42 };
43 }
44
45 function addToHistory(tb, frame, value) {
46 var h = getHistory(tb, frame);
47 var tmp = h.history.pop();
48 h.history.push(value);
49 if (tmp != undefined)
50 h.history.push(tmp);
51 h.setPos(h.history.length - 1);
52 }
53
54 function backInHistory(tb, frame, input) {
55 var pos, h = getHistory(tb, frame);
56 if ((pos = h.getPos()) > 0)
57 h.setPos(pos - 1);
58 input.value = h.getCurrent();
59 }
60
61 function forwardInHistory(tb, frame, input) {
62 var pos, h = getHistory(tb, frame);
63 if ((pos = h.getPos()) < h.history.length - 1)
64 h.setPos(pos + 1);
65 input.value = h.getCurrent();
66 }
67
68 function sendCommand(tb, frame, cmd, output) {
69 addToHistory(tb, frame, cmd);
70 $.get('__traceback__', {
71 tb: tb,
72 frame: frame,
73 code: cmd + '\n'
74 }, function(data) {
75 var x = output.append($('<div>').text(data))[0];
76 x.scrollTop = x.scrollHeight;
77 });
78 }
79
80 function pasteIt() {
81 var info = $('#plain p.pastebininfo');
82 var orig = info.html();
83 info.html('<em>submitting traceback...</em>');
84
85 $.ajax({
86 type: 'POST',
87 url: '__traceback__?pastetb=yes',
88 data: $('#plain pre.plain').text(),
89 dataType: 'json',
90 error: function() {
91 alert('Submitting paste failed. Make sure you have a\n' +
92 'working internet connection.');
93 info.html(orig);
94 },
95 success: function(result) {
96 info.text('Submitted paste: ').append(
97 $('<a>').attr('href', result.url).text('#' + result.paste_id)
98 );
99 }
100 });
101 }
102
103 $(document).ready(function() {
104 $('.exec_code').hide();
105 $('.vars').hide();
106 $('.code .pre').hide();
107 $('.code .post').hide();
108
109 $('.exec_code').submit(function() {
110 sendCommand(this.tb.value, this.frame.value, this.cmd.value,
111 $('.output', this));
112 this.cmd.value = '';
113 return false;
114 });
115
116 $('.code').click(function() {
117 $('.pre', $(this)).toggle();
118 $('.post', $(this)).toggle();
119 });
120
121 $('.exec_code input.input').keypress(function(e) {
122 if (e.charCode == 100 && e.ctrlKey) {
123 $('.output', $(this).parent()).text('--- screen cleared ---');
124 return false;
125 }
126 else if (e.charCode == 0 && (e.keyCode == 38 || e.keyCode == 40)) {
127 var parent = $(this).parent();
128 var tb = $('input[@name="tb"]', parent).attr('value');
129 var frame = $('input[@name="frame"]', parent).attr('value');
130 if (e.keyCode == 38)
131 backInHistory(tb, frame, this);
132 else
133 forwardInHistory(tb, frame, this);
134 return false;
135 }
136 return true;
137 });
138 });
0 eval(function(p,a,c,k,e,d){e=function(c){return(c<a?"":e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('7(1C 1w.6=="T"){1w.T=1w.T;B 6=u(a,c){7(1w==q)v 1p 6(a,c);a=a||17;7(6.1t(a))v 1p 6(17)[6.E.27?"27":"2O"](a);7(1C a=="23"){B m=/^[^<]*(<(.|\\s)+>)[^>]*$/.2Q(a);7(m)a=6.3k([m[1]]);J v 1p 6(c).2o(a)}v q.6r(a.1l==2y&&a||(a.3Y||a.I&&a!=1w&&!a.24&&a[0]!=T&&a[0].24)&&6.3M(a)||[a])};7(1C $!="T")6.2S$=$;B $=6;6.E=6.8p={3Y:"1.1.2",8q:u(){v q.I},I:0,2b:u(1T){v 1T==T?6.3M(q):q[1T]},2r:u(a){B L=6(a);L.6p=q;v L},6r:u(a){q.I=0;[].1g.14(q,a);v q},K:u(E,1E){v 6.K(q,E,1E)},2h:u(1c){B 4c=-1;q.K(u(i){7(q==1c)4c=i});v 4c},1I:u(1Y,O,C){B 1c=1Y;7(1Y.1l==3t)7(O==T)v q.I&&6[C||"1I"](q[0],1Y)||T;J{1c={};1c[1Y]=O}v q.K(u(2h){P(B H 1x 1c)6.1I(C?q.1q:q,H,6.H(q,1c[H],C,2h,H))})},1m:u(1Y,O){v q.1I(1Y,O,"30")},2L:u(e){7(1C e=="23")v q.3u().3r(17.8t(e));B t="";6.K(e||q,u(){6.K(q.2I,u(){7(q.24!=8)t+=q.24!=1?q.60:6.E.2L([q])})});v t},2K:u(){B a=6.3k(1A);v q.K(u(){B b=a[0].3l(U);q.11.2X(b,q);22(b.1b)b=b.1b;b.4C(q)})},3r:u(){v q.3j(1A,U,1,u(a){q.4C(a)})},5i:u(){v q.3j(1A,U,-1,u(a){q.2X(a,q.1b)})},5j:u(){v q.3j(1A,12,1,u(a){q.11.2X(a,q)})},5t:u(){v q.3j(1A,12,-1,u(a){q.11.2X(a,q.2e)})},4g:u(){v q.6p||6([])},2o:u(t){v q.2r(6.31(q,u(a){v 6.2o(t,a)}),t)},4Y:u(4N){v q.2r(6.31(q,u(a){B a=a.3l(4N!=T?4N:U);a.$1H=16;v a}))},1D:u(t){v q.2r(6.1t(t)&&6.2q(q,u(2z,2h){v t.14(2z,[2h])})||6.3z(t,q))},2g:u(t){v q.2r(t.1l==3t&&6.3z(t,q,U)||6.2q(q,u(a){v(t.1l==2y||t.3Y)?6.3y(a,t)<0:a!=t}))},1M:u(t){v q.2r(6.2k(q.2b(),t.1l==3t?6(t).2b():t.I!=T&&(!t.1f||t.1f=="8v")?t:[t]))},4l:u(1s){v 1s?6.1D(1s,q).r.I>0:12},1a:u(1a){v 1a==T?(q.I?q[0].O:16):q.1I("O",1a)},4U:u(1a){v 1a==T?(q.I?q[0].2t:16):q.3u().3r(1a)},3j:u(1E,1P,3Z,E){B 4Y=q.I>1;B a=6.3k(1E);7(3Z<0)a.8w();v q.K(u(){B 1c=q;7(1P&&6.1f(q,"1P")&&6.1f(a[0],"3m"))1c=q.5J("20")[0]||q.4C(17.6n("20"));6.K(a,u(){E.14(1c,[4Y?q.3l(U):q])})})}};6.1z=6.E.1z=u(){B 1O=1A[0],a=1;7(1A.I==1){1O=q;a=0}B H;22(H=1A[a++])P(B i 1x H)1O[i]=H[i];v 1O};6.1z({8x:u(){7(6.2S$)$=6.2S$;v 6},1t:u(E){v!!E&&1C E!="23"&&!E.1f&&1C E[0]=="T"&&/u/i.1n(E+"")},4B:u(D){v D.66&&D.5I&&!D.5I.64},1f:u(D,Y){v D.1f&&D.1f.3K()==Y.3K()},K:u(1c,E,1E){7(1c.I==T)P(B i 1x 1c)E.14(1c[i],1E||[i,1c[i]]);J P(B i=0,6q=1c.I;i<6q;i++)7(E.14(1c[i],1E||[i,1c[i]])===12)3O;v 1c},H:u(D,O,C,2h,H){7(6.1t(O))O=O.3n(D,[2h]);B 6s=/z-?2h|7P-?8A|1d|58|8B-?28/i;v O&&O.1l==3Q&&C=="30"&&!6s.1n(H)?O+"4S":O},19:{1M:u(D,c){6.K(c.3o(/\\s+/),u(i,Q){7(!6.19.2V(D.19,Q))D.19+=(D.19?" ":"")+Q})},2f:u(D,c){D.19=c?6.2q(D.19.3o(/\\s+/),u(Q){v!6.19.2V(c,Q)}).6t(" "):""},2V:u(t,c){t=t.19||t;c=c.1R(/([\\.\\\\\\+\\*\\?\\[\\^\\]\\$\\(\\)\\{\\}\\=\\!\\<\\>\\|\\:])/g,"\\\\$1");v t&&1p 4v("(^|\\\\s)"+c+"(\\\\s|$)").1n(t)}},4d:u(e,o,f){P(B i 1x o){e.1q["1N"+i]=e.1q[i];e.1q[i]=o[i]}f.14(e,[]);P(B i 1x o)e.1q[i]=e.1q["1N"+i]},1m:u(e,p){7(p=="28"||p=="3V"){B 1N={},46,3P,d=["7d","8C","8D","8E"];6.K(d,u(){1N["8F"+q]=0;1N["8G"+q+"8H"]=0});6.4d(e,1N,u(){7(6.1m(e,"1h")!="1Z"){46=e.8I;3P=e.8J}J{e=6(e.3l(U)).2o(":4j").5l("2Z").4g().1m({4n:"1G",45:"8K",1h:"2D",7I:"0",8M:"0"}).5z(e.11)[0];B 3d=6.1m(e.11,"45");7(3d==""||3d=="4b")e.11.1q.45="6x";46=e.6y;3P=e.6z;7(3d==""||3d=="4b")e.11.1q.45="4b";e.11.33(e)}});v p=="28"?46:3P}v 6.30(e,p)},30:u(D,H,53){B L;7(H=="1d"&&6.W.1j)v 6.1I(D.1q,"1d");7(H=="4h"||H=="2v")H=6.W.1j?"3T":"2v";7(!53&&D.1q[H])L=D.1q[H];J 7(17.44&&17.44.4W){7(H=="2v"||H=="3T")H="4h";H=H.1R(/([A-Z])/g,"-$1").4m();B Q=17.44.4W(D,16);7(Q)L=Q.55(H);J 7(H=="1h")L="1Z";J 6.4d(D,{1h:"2D"},u(){B c=17.44.4W(q,"");L=c&&c.55(H)||""})}J 7(D.51){B 56=H.1R(/\\-(\\w)/g,u(m,c){v c.3K()});L=D.51[H]||D.51[56]}v L},3k:u(a){B r=[];6.K(a,u(i,1r){7(!1r)v;7(1r.1l==3Q)1r=1r.6C();7(1C 1r=="23"){B s=6.35(1r),1V=17.6n("1V"),2i=[];B 2K=!s.18("<1u")&&[1,"<42>","</42>"]||(!s.18("<6D")||!s.18("<20")||!s.18("<6E"))&&[1,"<1P>","</1P>"]||!s.18("<3m")&&[2,"<1P><20>","</20></1P>"]||(!s.18("<6F")||!s.18("<6G"))&&[3,"<1P><20><3m>","</3m></20></1P>"]||[0,"",""];1V.2t=2K[1]+s+2K[2];22(2K[0]--)1V=1V.1b;7(6.W.1j){7(!s.18("<1P")&&s.18("<20")<0)2i=1V.1b&&1V.1b.2I;J 7(2K[1]=="<1P>"&&s.18("<20")<0)2i=1V.2I;P(B n=2i.I-1;n>=0;--n)7(6.1f(2i[n],"20")&&!2i[n].2I.I)2i[n].11.33(2i[n])}1r=[];P(B i=0,l=1V.2I.I;i<l;i++)1r.1g(1V.2I[i])}7(1r.I===0&&!6.1f(1r,"3w"))v;7(1r[0]==T||6.1f(1r,"3w"))r.1g(1r);J r=6.2k(r,1r)});v r},1I:u(D,Y,O){B 2j=6.4B(D)?{}:{"P":"6J","6L":"19","4h":6.W.1j?"3T":"2v",2v:6.W.1j?"3T":"2v",2t:"2t",19:"19",O:"O",2W:"2W",2Z:"2Z",89:"6N",2Y:"2Y"};7(Y=="1d"&&6.W.1j&&O!=T){D.58=1;v D.1D=D.1D.1R(/4i\\([^\\)]*\\)/6O,"")+(O==1?"":"4i(1d="+O*6g+")")}J 7(Y=="1d"&&6.W.1j)v D.1D?4T(D.1D.6P(/4i\\(1d=(.*)\\)/)[1])/6g:1;7(Y=="1d"&&6.W.3h&&O==1)O=0.6R;7(2j[Y]){7(O!=T)D[2j[Y]]=O;v D[2j[Y]]}J 7(O==T&&6.W.1j&&6.1f(D,"3w")&&(Y=="81"||Y=="80"))v D.6T(Y).60;J 7(D.66){7(O!=T)D.6V(Y,O);7(6.W.1j&&/5E|3e/.1n(Y)&&!6.4B(D))v D.36(Y,2);v D.36(Y)}J{Y=Y.1R(/-([a-z])/6W,u(z,b){v b.3K()});7(O!=T)D[Y]=O;v D[Y]}},35:u(t){v t.1R(/^\\s+|\\s+$/g,"")},3M:u(a){B r=[];7(a.1l!=2y)P(B i=0,2R=a.I;i<2R;i++)r.1g(a[i]);J r=a.3N(0);v r},3y:u(b,a){P(B i=0,2R=a.I;i<2R;i++)7(a[i]==b)v i;v-1},2k:u(2u,3H){B r=[].3N.3n(2u,0);P(B i=0,5b=3H.I;i<5b;i++)7(6.3y(3H[i],r)==-1)2u.1g(3H[i]);v 2u},2q:u(1U,E,4k){7(1C E=="23")E=1p 4w("a","i","v "+E);B 1i=[];P(B i=0,2z=1U.I;i<2z;i++)7(!4k&&E(1U[i],i)||4k&&!E(1U[i],i))1i.1g(1U[i]);v 1i},31:u(1U,E){7(1C E=="23")E=1p 4w("a","v "+E);B 1i=[],r=[];P(B i=0,2z=1U.I;i<2z;i++){B 1a=E(1U[i],i);7(1a!==16&&1a!=T){7(1a.1l!=2y)1a=[1a];1i=1i.6Z(1a)}}B r=1i.I?[1i[0]]:[];5f:P(B i=1,5e=1i.I;i<5e;i++){P(B j=0;j<i;j++)7(1i[i]==r[j])5F 5f;r.1g(1i[i])}v r}});1p u(){B b=7L.71.4m();6.W={2N:/5D/.1n(b),3f:/3f/.1n(b),1j:/1j/.1n(b)&&!/3f/.1n(b),3h:/3h/.1n(b)&&!/(72|5D)/.1n(b)};6.7H=!6.W.1j||17.74=="75"};6.K({5u:"a.11",4z:"6.4z(a)",76:"6.2a(a,2,\'2e\')",7D:"6.2a(a,2,\'5s\')",78:"6.2B(a.11.1b,a)",79:"6.2B(a.1b)"},u(i,n){6.E[i]=u(a){B L=6.31(q,n);7(a&&1C a=="23")L=6.3z(a,L);v q.2r(L)}});6.K({5z:"3r",7b:"5i",2X:"5j",7e:"5t"},u(i,n){6.E[i]=u(){B a=1A;v q.K(u(){P(B j=0,2R=a.I;j<2R;j++)6(a[j])[n](q)})}});6.K({5l:u(1Y){6.1I(q,1Y,"");q.7g(1Y)},7h:u(c){6.19.1M(q,c)},7i:u(c){6.19.2f(q,c)},7k:u(c){6.19[6.19.2V(q,c)?"2f":"1M"](q,c)},2f:u(a){7(!a||6.1D(a,[q]).r.I)q.11.33(q)},3u:u(){22(q.1b)q.33(q.1b)}},u(i,n){6.E[i]=u(){v q.K(n,1A)}});6.K(["5q","5n","5p","5v"],u(i,n){6.E[n]=u(1T,E){v q.1D(":"+n+"("+1T+")",E)}});6.K(["28","3V"],u(i,n){6.E[n]=u(h){v h==T?(q.I?6.1m(q[0],n):16):q.1m(n,h.1l==3t?h:h+"4S")}});6.1z({1s:{"":"m[2]==\'*\'||6.1f(a,m[2])","#":"a.36(\'2J\')==m[2]",":":{5n:"i<m[3]-0",5p:"i>m[3]-0",2a:"m[3]-0==i",5q:"m[3]-0==i",2u:"i==0",2T:"i==r.I-1",5R:"i%2==0",5S:"i%2","2a-3s":"6.2a(a.11.1b,m[3],\'2e\',a)==a","2u-3s":"6.2a(a.11.1b,1,\'2e\')==a","2T-3s":"6.2a(a.11.7n,1,\'5s\')==a","7p-3s":"6.2B(a.11.1b).I==1",5u:"a.1b",3u:"!a.1b",5v:"6.E.2L.14([a]).18(m[3])>=0",3i:\'a.C!="1G"&&6.1m(a,"1h")!="1Z"&&6.1m(a,"4n")!="1G"\',1G:\'a.C=="1G"||6.1m(a,"1h")=="1Z"||6.1m(a,"4n")=="1G"\',7v:"!a.2W",2W:"a.2W",2Z:"a.2Z",2Y:"a.2Y||6.1I(a,\'2Y\')",2L:"a.C==\'2L\'",4j:"a.C==\'4j\'",5x:"a.C==\'5x\'",4G:"a.C==\'4G\'",5y:"a.C==\'5y\'",4R:"a.C==\'4R\'",5A:"a.C==\'5A\'",5B:"a.C==\'5B\'",3x:\'a.C=="3x"||6.1f(a,"3x")\',5C:"/5C|42|7A|3x/i.1n(a.1f)"},".":"6.19.2V(a,m[2])","@":{"=":"z==m[4]","!=":"z!=m[4]","^=":"z&&!z.18(m[4])","$=":"z&&z.2U(z.I - m[4].I,m[4].I)==m[4]","*=":"z&&z.18(m[4])>=0","":"z",4u:u(m){v["",m[1],m[3],m[2],m[5]]},5P:"z=a[m[3]];7(!z||/5E|3e/.1n(m[3]))z=6.1I(a,m[3]);"},"[":"6.2o(m[2],a).I"},5M:[/^\\[ *(@)([a-2m-3C-]*) *([!*$^=]*) *(\'?"?)(.*?)\\4 *\\]/i,/^(\\[)\\s*(.*?(\\[.*?\\])?[^[]*?)\\s*\\]/,/^(:)([a-2m-3C-]*)\\("?\'?(.*?(\\(.*?\\))?[^(]*?)"?\'?\\)/i,/^([:.#]*)([a-2m-3C*-]*)/i],1Q:[/^(\\/?\\.\\.)/,"a.11",/^(>|\\/)/,"6.2B(a.1b)",/^(\\+)/,"6.2a(a,2,\'2e\')",/^(~)/,u(a){B s=6.2B(a.11.1b);v s.3N(6.3y(a,s)+1)}],3z:u(1s,1U,2g){B 1N,Q=[];22(1s&&1s!=1N){1N=1s;B f=6.1D(1s,1U,2g);1s=f.t.1R(/^\\s*,\\s*/,"");Q=2g?1U=f.r:6.2k(Q,f.r)}v Q},2o:u(t,1B){7(1C t!="23")v[t];7(1B&&!1B.24)1B=16;1B=1B||17;7(!t.18("//")){1B=1B.4H;t=t.2U(2,t.I)}J 7(!t.18("/")){1B=1B.4H;t=t.2U(1,t.I);7(t.18("/")>=1)t=t.2U(t.18("/"),t.I)}B L=[1B],2c=[],2T=16;22(t&&2T!=t){B r=[];2T=t;t=6.35(t).1R(/^\\/\\//i,"");B 3B=12;B 1J=/^[\\/>]\\s*([a-2m-9*-]+)/i;B m=1J.2Q(t);7(m){6.K(L,u(){P(B c=q.1b;c;c=c.2e)7(c.24==1&&(6.1f(c,m[1])||m[1]=="*"))r.1g(c)});L=r;t=t.1R(1J,"");7(t.18(" ")==0)5F;3B=U}J{P(B i=0;i<6.1Q.I;i+=2){B 1J=6.1Q[i];B m=1J.2Q(t);7(m){r=L=6.31(L,6.1t(6.1Q[i+1])?6.1Q[i+1]:u(a){v 40(6.1Q[i+1])});t=6.35(t.1R(1J,""));3B=U;3O}}}7(t&&!3B){7(!t.18(",")){7(L[0]==1B)L.4L();6.2k(2c,L);r=L=[1B];t=" "+t.2U(1,t.I)}J{B 34=/^([a-2m-3C-]+)(#)([a-2m-9\\\\*2S-]*)/i;B m=34.2Q(t);7(m){m=[0,m[2],m[3],m[1]]}J{34=/^([#.]?)([a-2m-9\\\\*2S-]*)/i;m=34.2Q(t)}7(m[1]=="#"&&L[L.I-1].4X){B 2l=L[L.I-1].4X(m[2]);7(6.W.1j&&2l&&2l.2J!=m[2])2l=6(\'[@2J="\'+m[2]+\'"]\',L[L.I-1])[0];L=r=2l&&(!m[3]||6.1f(2l,m[3]))?[2l]:[]}J{7(m[1]==".")B 4r=1p 4v("(^|\\\\s)"+m[2]+"(\\\\s|$)");6.K(L,u(){B 3E=m[1]!=""||m[0]==""?"*":m[2];7(6.1f(q,"7J")&&3E=="*")3E="3g";6.2k(r,m[1]!=""&&L.I!=1?6.4x(q,[],m[1],m[2],4r):q.5J(3E))});7(m[1]=="."&&L.I==1)r=6.2q(r,u(e){v 4r.1n(e.19)});7(m[1]=="#"&&L.I==1){B 5K=r;r=[];6.K(5K,u(){7(q.36("2J")==m[2]){r=[q];v 12}})}L=r}t=t.1R(34,"")}}7(t){B 1a=6.1D(t,r);L=r=1a.r;t=6.35(1a.t)}}7(L&&L[0]==1B)L.4L();6.2k(2c,L);v 2c},1D:u(t,r,2g){22(t&&/^[a-z[({<*:.#]/i.1n(t)){B p=6.5M,m;6.K(p,u(i,1J){m=1J.2Q(t);7(m){t=t.7M(m[0].I);7(6.1s[m[1]].4u)m=6.1s[m[1]].4u(m);v 12}});7(m[1]==":"&&m[2]=="2g")r=6.1D(m[3],r,U).r;J 7(m[1]=="."){B 1J=1p 4v("(^|\\\\s)"+m[2]+"(\\\\s|$)");r=6.2q(r,u(e){v 1J.1n(e.19||"")},2g)}J{B f=6.1s[m[1]];7(1C f!="23")f=6.1s[m[1]][m[2]];40("f = u(a,i){"+(6.1s[m[1]].5P||"")+"v "+f+"}");r=6.2q(r,f,2g)}}v{r:r,t:t}},4x:u(o,r,1Q,Y,1J){P(B s=o.1b;s;s=s.2e)7(s.24==1){B 1M=U;7(1Q==".")1M=s.19&&1J.1n(s.19);J 7(1Q=="#")1M=s.36("2J")==Y;7(1M)r.1g(s);7(1Q=="#"&&r.I)3O;7(s.1b)6.4x(s,r,1Q,Y,1J)}v r},4z:u(D){B 4A=[];B Q=D.11;22(Q&&Q!=17){4A.1g(Q);Q=Q.11}v 4A},2a:u(Q,1i,3Z,D){1i=1i||1;B 1T=0;P(;Q;Q=Q[3Z]){7(Q.24==1)1T++;7(1T==1i||1i=="5R"&&1T%2==0&&1T>1&&Q==D||1i=="5S"&&1T%2==1&&Q==D)v Q}},2B:u(n,D){B r=[];P(;n;n=n.2e){7(n.24==1&&(!D||n!=D))r.1g(n)}v r}});6.G={1M:u(S,C,1o,F){7(6.W.1j&&S.3L!=T)S=1w;7(F)1o.F=F;7(!1o.2A)1o.2A=q.2A++;7(!S.$1H)S.$1H={};B 38=S.$1H[C];7(!38){38=S.$1H[C]={};7(S["39"+C])38[0]=S["39"+C]}38[1o.2A]=1o;S["39"+C]=q.5Y;7(!q.1k[C])q.1k[C]=[];q.1k[C].1g(S)},2A:1,1k:{},2f:u(S,C,1o){7(S.$1H){B i,j,k;7(C&&C.C){1o=C.1o;C=C.C}7(C&&S.$1H[C])7(1o)5U S.$1H[C][1o.2A];J P(i 1x S.$1H[C])5U S.$1H[C][i];J P(j 1x S.$1H)q.2f(S,j);P(k 1x S.$1H[C])7(k){k=U;3O}7(!k)S["39"+C]=16}},1S:u(C,F,S){F=6.3M(F||[]);7(!S)6.K(q.1k[C]||[],u(){6.G.1S(C,F,q)});J{B 1o=S["39"+C],1a,E=6.1t(S[C]);7(1o){F.61(q.2j({C:C,1O:S}));7((1a=1o.14(S,F))!==12)q.4F=U}7(E&&1a!==12)S[C]();q.4F=12}},5Y:u(G){7(1C 6=="T"||6.G.4F)v;G=6.G.2j(G||1w.G||{});B 3R;B c=q.$1H[G.C];B 1E=[].3N.3n(1A,1);1E.61(G);P(B j 1x c){1E[0].1o=c[j];1E[0].F=c[j].F;7(c[j].14(q,1E)===12){G.2n();G.2H();3R=12}}7(6.W.1j)G.1O=G.2n=G.2H=G.1o=G.F=16;v 3R},2j:u(G){7(!G.1O&&G.63)G.1O=G.63;7(G.65==T&&G.67!=T){B e=17.4H,b=17.64;G.65=G.67+(e.68||b.68);G.7Y=G.7Z+(e.6c||b.6c)}7(6.W.2N&&G.1O.24==3){B 3a=G;G=6.1z({},3a);G.1O=3a.1O.11;G.2n=u(){v 3a.2n()};G.2H=u(){v 3a.2H()}}7(!G.2n)G.2n=u(){q.3R=12};7(!G.2H)G.2H=u(){q.82=U};v G}};6.E.1z({3U:u(C,F,E){v q.K(u(){6.G.1M(q,C,E||F,F)})},6u:u(C,F,E){v q.K(u(){6.G.1M(q,C,u(G){6(q).6f(G);v(E||F).14(q,1A)},F)})},6f:u(C,E){v q.K(u(){6.G.2f(q,C,E)})},1S:u(C,F){v q.K(u(){6.G.1S(C,F,q)})},3X:u(){B a=1A;v q.6j(u(e){q.4M=q.4M==0?1:0;e.2n();v a[q.4M].14(q,[e])||12})},83:u(f,g){u 4O(e){B p=(e.C=="41"?e.84:e.85)||e.86;22(p&&p!=q)2G{p=p.11}2w(e){p=q};7(p==q)v 12;v(e.C=="41"?f:g).14(q,[e])}v q.41(4O).6k(4O)},27:u(f){7(6.3W)f.14(17,[6]);J{6.3c.1g(u(){v f.14(q,[6])})}v q}});6.1z({3W:12,3c:[],27:u(){7(!6.3W){6.3W=U;7(6.3c){6.K(6.3c,u(){q.14(17)});6.3c=16}7(6.W.3h||6.W.3f)17.87("6o",6.27,12)}}});1p u(){6.K(("88,8a,2O,8b,8d,52,6j,8e,"+"8f,8g,8h,41,6k,8j,42,"+"4R,8k,8l,8m,2C").3o(","),u(i,o){6.E[o]=u(f){v f?q.3U(o,f):q.1S(o)}});7(6.W.3h||6.W.3f)17.8n("6o",6.27,12);J 7(6.W.1j){17.8o("<8r"+"8s 2J=62 8u=U "+"3e=//:><\\/2d>");B 2d=17.4X("62");7(2d)2d.37=u(){7(q.3D!="1X")v;q.11.33(q);6.27()};2d=16}J 7(6.W.2N)6.50=3L(u(){7(17.3D=="8y"||17.3D=="1X"){4p(6.50);6.50=16;6.27()}},10);6.G.1M(1w,"2O",6.27)};7(6.W.1j)6(1w).6u("52",u(){B 1k=6.G.1k;P(B C 1x 1k){B 4Z=1k[C],i=4Z.I;7(i&&C!=\'52\')6w 6.G.2f(4Z[i-1],C);22(--i)}});6.E.1z({6A:u(V,21,M){q.2O(V,21,M,1)},2O:u(V,21,M,1W){7(6.1t(V))v q.3U("2O",V);M=M||u(){};B C="5d";7(21)7(6.1t(21)){M=21;21=16}J{21=6.3g(21);C="5V"}B 4e=q;6.3v({V:V,C:C,F:21,1W:1W,1X:u(2P,15){7(15=="2M"||!1W&&15=="5L")4e.1I("2t",2P.3G).4V().K(M,[2P.3G,15,2P]);J M.14(4e,[2P.3G,15,2P])}});v q},6B:u(){v 6.3g(q)},4V:u(){v q.2o("2d").K(u(){7(q.3e)6.59(q.3e);J 6.4a(q.2L||q.6H||q.2t||"")}).4g()}});7(!1w.3p)3p=u(){v 1p 6I("6K.6M")};6.K("5m,5Q,5O,5W,5N,5H".3o(","),u(i,o){6.E[o]=u(f){v q.3U(o,f)}});6.1z({2b:u(V,F,M,C,1W){7(6.1t(F)){M=F;F=16}v 6.3v({V:V,F:F,2M:M,4t:C,1W:1W})},6Q:u(V,F,M,C){v 6.2b(V,F,M,C,1)},59:u(V,M){v 6.2b(V,16,M,"2d")},6S:u(V,F,M){v 6.2b(V,F,M,"6m")},6U:u(V,F,M,C){7(6.1t(F)){M=F;F={}}v 6.3v({C:"5V",V:V,F:F,2M:M,4t:C})},6X:u(29){6.3q.29=29},6Y:u(5c){6.1z(6.3q,5c)},3q:{1k:U,C:"5d",29:0,5r:"70/x-73-3w-77",5h:U,48:U,F:16},3S:{},3v:u(s){s=6.1z({},6.3q,s);7(s.F){7(s.5h&&1C s.F!="23")s.F=6.3g(s.F);7(s.C.4m()=="2b"){s.V+=((s.V.18("?")>-1)?"&":"?")+s.F;s.F=16}}7(s.1k&&!6.4E++)6.G.1S("5m");B 4y=12;B N=1p 3p();N.7j(s.C,s.V,s.48);7(s.F)N.3A("7l-7m",s.5r);7(s.1W)N.3A("7o-4K-7q",6.3S[s.V]||"7s, 7t 7w 7x 4o:4o:4o 7z");N.3A("X-7B-7C","3p");7(N.7E)N.3A("7F","7G");7(s.5G)s.5G(N);7(s.1k)6.G.1S("5H",[N,s]);B 37=u(4s){7(N&&(N.3D==4||4s=="29")){4y=U;7(3I){4p(3I);3I=16}B 15;2G{15=6.5Z(N)&&4s!="29"?s.1W&&6.69(N,s.V)?"5L":"2M":"2C";7(15!="2C"){B 3F;2G{3F=N.4P("6b-4K")}2w(e){}7(s.1W&&3F)6.3S[s.V]=3F;B F=6.6i(N,s.4t);7(s.2M)s.2M(F,15);7(s.1k)6.G.1S("5N",[N,s])}J 6.3J(s,N,15)}2w(e){15="2C";6.3J(s,N,15,e)}7(s.1k)6.G.1S("5O",[N,s]);7(s.1k&&!--6.4E)6.G.1S("5Q");7(s.1X)s.1X(N,15);7(s.48)N=16}};B 3I=3L(37,13);7(s.29>0)57(u(){7(N){N.7N();7(!4y)37("29")}},s.29);2G{N.7Q(s.F)}2w(e){6.3J(s,N,16,e)}7(!s.48)37();v N},3J:u(s,N,15,e){7(s.2C)s.2C(N,15,e);7(s.1k)6.G.1S("5W",[N,s,e])},4E:0,5Z:u(r){2G{v!r.15&&7V.7W=="4G:"||(r.15>=5X&&r.15<7X)||r.15==6d||6.W.2N&&r.15==T}2w(e){}v 12},69:u(N,V){2G{B 6e=N.4P("6b-4K");v N.15==6d||6e==6.3S[V]||6.W.2N&&N.15==T}2w(e){}v 12},6i:u(r,C){B 4Q=r.4P("8c-C");B F=!C&&4Q&&4Q.18("N")>=0;F=C=="N"||F?r.8i:r.3G;7(C=="2d")6.4a(F);7(C=="6m")40("F = "+F);7(C=="4U")6("<1V>").4U(F).4V();v F},3g:u(a){B s=[];7(a.1l==2y||a.3Y)6.K(a,u(){s.1g(2x(q.Y)+"="+2x(q.O))});J P(B j 1x a)7(a[j]&&a[j].1l==2y)6.K(a[j],u(){s.1g(2x(j)+"="+2x(q))});J s.1g(2x(j)+"="+2x(a[j]));v s.6t("&")},4a:u(F){7(1w.54)1w.54(F);J 7(6.W.2N)1w.57(F,0);J 40.3n(1w,F)}});6.E.1z({1L:u(R,M){B 1G=q.1D(":1G");R?1G.26({28:"1L",3V:"1L",1d:"1L"},R,M):1G.K(u(){q.1q.1h=q.2E?q.2E:"";7(6.1m(q,"1h")=="1Z")q.1q.1h="2D"});v q},1K:u(R,M){B 3i=q.1D(":3i");R?3i.26({28:"1K",3V:"1K",1d:"1K"},R,M):3i.K(u(){q.2E=q.2E||6.1m(q,"1h");7(q.2E=="1Z")q.2E="2D";q.1q.1h="1Z"});v q},5g:6.E.3X,3X:u(E,4I){B 1E=1A;v 6.1t(E)&&6.1t(4I)?q.5g(E,4I):q.K(u(){6(q)[6(q).4l(":1G")?"1L":"1K"].14(6(q),1E)})},7a:u(R,M){v q.26({28:"1L"},R,M)},7c:u(R,M){v q.26({28:"1K"},R,M)},7f:u(R,M){v q.K(u(){B 5k=6(q).4l(":1G")?"1L":"1K";6(q).26({28:5k},R,M)})},7r:u(R,M){v q.26({1d:"1L"},R,M)},7u:u(R,M){v q.26({1d:"1K"},R,M)},7y:u(R,43,M){v q.26({1d:43},R,M)},26:u(H,R,1v,M){v q.1F(u(){q.2F=6.1z({},H);B 1u=6.R(R,1v,M);P(B p 1x H){B e=1p 6.3b(q,1u,p);7(H[p].1l==3Q)e.2s(e.Q(),H[p]);J e[H[p]](H)}})},1F:u(C,E){7(!E){E=C;C="3b"}v q.K(u(){7(!q.1F)q.1F={};7(!q.1F[C])q.1F[C]=[];q.1F[C].1g(E);7(q.1F[C].I==1)E.14(q)})}});6.1z({R:u(R,1v,E){B 1u=R&&R.1l==7K?R:{1X:E||!E&&1v||6.1t(R)&&R,25:R,1v:E&&1v||1v&&1v.1l!=4w&&1v};1u.25=(1u.25&&1u.25.1l==3Q?1u.25:{7R:7S,7T:5X}[1u.25])||7U;1u.1N=1u.1X;1u.1X=u(){6.6a(q,"3b");7(6.1t(1u.1N))1u.1N.14(q)};v 1u},1v:{},1F:{},6a:u(D,C){C=C||"3b";7(D.1F&&D.1F[C]){D.1F[C].4L();B f=D.1F[C][0];7(f)f.14(D)}},3b:u(D,1e,H){B z=q;B y=D.1q;B 4D=6.1m(D,"1h");y.5T="1G";z.a=u(){7(1e.49)1e.49.14(D,[z.2p]);7(H=="1d")6.1I(y,"1d",z.2p);J 7(6l(z.2p))y[H]=6l(z.2p)+"4S";y.1h="2D"};z.6v=u(){v 4T(6.1m(D,H))};z.Q=u(){B r=4T(6.30(D,H));v r&&r>-8z?r:z.6v()};z.2s=u(4f,43){z.4J=(1p 5o()).5w();z.2p=4f;z.a();z.4q=3L(u(){z.49(4f,43)},13)};z.1L=u(){7(!D.1y)D.1y={};D.1y[H]=q.Q();1e.1L=U;z.2s(0,D.1y[H]);7(H!="1d")y[H]="5a"};z.1K=u(){7(!D.1y)D.1y={};D.1y[H]=q.Q();1e.1K=U;z.2s(D.1y[H],0)};z.3X=u(){7(!D.1y)D.1y={};D.1y[H]=q.Q();7(4D=="1Z"){1e.1L=U;7(H!="1d")y[H]="5a";z.2s(0,D.1y[H])}J{1e.1K=U;z.2s(D.1y[H],0)}};z.49=u(32,47){B t=(1p 5o()).5w();7(t>1e.25+z.4J){4p(z.4q);z.4q=16;z.2p=47;z.a();7(D.2F)D.2F[H]=U;B 2c=U;P(B i 1x D.2F)7(D.2F[i]!==U)2c=12;7(2c){y.5T="";y.1h=4D;7(6.1m(D,"1h")=="1Z")y.1h="2D";7(1e.1K)y.1h="1Z";7(1e.1K||1e.1L)P(B p 1x D.2F)7(p=="1d")6.1I(y,p,D.1y[p]);J y[p]=""}7(2c&&6.1t(1e.1X))1e.1X.14(D)}J{B n=t-q.4J;B p=n/1e.25;z.2p=1e.1v&&6.1v[1e.1v]?6.1v[1e.1v](p,n,32,(47-32),1e.25):((-6h.7O(p*6h.8L)/2)+0.5)*(47-32)+32;z.a()}}}})}',62,545,'||||||jQuery|if|||||||||||||||||||this||||function|return||||||var|type|elem|fn|data|event|prop|length|else|each|ret|callback|xml|value|for|cur|speed|element|undefined|true|url|browser||name|||parentNode|false||apply|status|null|document|indexOf|className|val|firstChild|obj|opacity|options|nodeName|push|display|result|msie|global|constructor|css|test|handler|new|style|arg|expr|isFunction|opt|easing|window|in|orig|extend|arguments|context|typeof|filter|args|queue|hidden|events|attr|re|hide|show|add|old|target|table|token|replace|trigger|num|elems|div|ifModified|complete|key|none|tbody|params|while|string|nodeType|duration|animate|ready|height|timeout|nth|get|done|script|nextSibling|remove|not|index|tb|fix|merge|oid|z0|preventDefault|find|now|grep|pushStack|custom|innerHTML|first|cssFloat|catch|encodeURIComponent|Array|el|guid|sibling|error|block|oldblock|curAnim|try|stopPropagation|childNodes|id|wrap|text|success|safari|load|res|exec|al|_|last|substr|has|disabled|insertBefore|selected|checked|curCSS|map|firstNum|removeChild|re2|trim|getAttribute|onreadystatechange|handlers|on|originalEvent|fx|readyList|parPos|src|opera|param|mozilla|visible|domManip|clean|cloneNode|tr|call|split|XMLHttpRequest|ajaxSettings|append|child|String|empty|ajax|form|button|inArray|multiFilter|setRequestHeader|foundToken|9_|readyState|tag|modRes|responseText|second|ival|handleError|toUpperCase|setInterval|makeArray|slice|break|oWidth|Number|returnValue|lastModified|styleFloat|bind|width|isReady|toggle|jquery|dir|eval|mouseover|select|to|defaultView|position|oHeight|lastNum|async|step|globalEval|static|pos|swap|self|from|end|float|alpha|radio|inv|is|toLowerCase|visibility|00|clearInterval|timer|rec|isTimeout|dataType|_resort|RegExp|Function|getAll|requestDone|parents|matched|isXMLDoc|appendChild|oldDisplay|active|triggered|file|documentElement|fn2|startTime|Modified|shift|lastToggle|deep|handleHover|getResponseHeader|ct|submit|px|parseFloat|html|evalScripts|getComputedStyle|getElementById|clone|els|safariTimer|currentStyle|unload|force|execScript|getPropertyValue|newProp|setTimeout|zoom|getScript|1px|sl|settings|GET|rl|check|_toggle|processData|prepend|before|state|removeAttr|ajaxStart|lt|Date|gt|eq|contentType|previousSibling|after|parent|contains|getTime|checkbox|password|appendTo|image|reset|input|webkit|href|continue|beforeSend|ajaxSend|ownerDocument|getElementsByTagName|tmp|notmodified|parse|ajaxSuccess|ajaxComplete|_prefix|ajaxStop|even|odd|overflow|delete|POST|ajaxError|200|handle|httpSuccess|nodeValue|unshift|__ie_init|srcElement|body|pageX|tagName|clientX|scrollLeft|httpNotModified|dequeue|Last|scrollTop|304|xmlRes|unbind|100|Math|httpData|click|mouseout|parseInt|json|createElement|DOMContentLoaded|prevObject|ol|setArray|exclude|join|one|max|do|relative|clientHeight|clientWidth|loadIfModified|serialize|toString|thead|tfoot|td|th|textContent|ActiveXObject|htmlFor|Microsoft|class|XMLHTTP|readOnly|gi|match|getIfModified|9999|getJSON|getAttributeNode|post|setAttribute|ig|ajaxTimeout|ajaxSetup|concat|application|userAgent|compatible|www|compatMode|CSS1Compat|next|urlencoded|siblings|children|slideDown|prependTo|slideUp|Top|insertAfter|slideToggle|removeAttribute|addClass|removeClass|open|toggleClass|Content|Type|lastChild|If|only|Since|fadeIn|Thu|01|fadeOut|enabled|Jan|1970|fadeTo|GMT|textarea|Requested|With|prev|overrideMimeType|Connection|close|boxModel|right|object|Object|navigator|substring|abort|cos|font|send|slow|600|fast|400|location|protocol|300|pageY|clientY|method|action|cancelBubble|hover|fromElement|toElement|relatedTarget|removeEventListener|blur|readonly|focus|resize|content|scroll|dblclick|mousedown|mouseup|mousemove|responseXML|change|keydown|keypress|keyup|addEventListener|write|prototype|size|scr|ipt|createTextNode|defer|FORM|reverse|noConflict|loaded|10000|weight|line|Bottom|Right|Left|padding|border|Width|offsetHeight|offsetWidth|absolute|PI|left'.split('|'),0,{}))
0 /*
1 * jQuery 1.2.3 - New Wave Javascript
2 *
3 * Copyright (c) 2008 John Resig (jquery.com)
4 * Dual licensed under the MIT (MIT-LICENSE.txt)
5 * and GPL (GPL-LICENSE.txt) licenses.
6 *
7 * $Date: 2008-02-06 00:21:25 -0500 (Wed, 06 Feb 2008) $
8 * $Rev: 4663 $
9 */
10 eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(J(){7(1e.3N)L w=1e.3N;L E=1e.3N=J(a,b){K 1B E.2l.4T(a,b)};7(1e.$)L D=1e.$;1e.$=E;L u=/^[^<]*(<(.|\\s)+>)[^>]*$|^#(\\w+)$/;L G=/^.[^:#\\[\\.]*$/;E.1n=E.2l={4T:J(d,b){d=d||T;7(d.15){6[0]=d;6.M=1;K 6}N 7(1o d=="25"){L c=u.2O(d);7(c&&(c[1]||!b)){7(c[1])d=E.4a([c[1]],b);N{L a=T.5J(c[3]);7(a)7(a.2w!=c[3])K E().2s(d);N{6[0]=a;6.M=1;K 6}N d=[]}}N K 1B E(b).2s(d)}N 7(E.1q(d))K 1B E(T)[E.1n.21?"21":"3U"](d);K 6.6E(d.1k==1M&&d||(d.5h||d.M&&d!=1e&&!d.15&&d[0]!=10&&d[0].15)&&E.2I(d)||[d])},5h:"1.2.3",87:J(){K 6.M},M:0,22:J(a){K a==10?E.2I(6):6[a]},2F:J(b){L a=E(b);a.54=6;K a},6E:J(a){6.M=0;1M.2l.1g.1i(6,a);K 6},R:J(a,b){K E.R(6,a,b)},4X:J(b){L a=-1;6.R(J(i){7(6==b)a=i});K a},1J:J(c,a,b){L d=c;7(c.1k==4e)7(a==10)K 6.M&&E[b||"1J"](6[0],c)||10;N{d={};d[c]=a}K 6.R(J(i){Q(c 1p d)E.1J(b?6.W:6,c,E.1l(6,d[c],b,i,c))})},1j:J(b,a){7((b==\'27\'||b==\'1R\')&&2M(a)<0)a=10;K 6.1J(b,a,"2o")},1u:J(b){7(1o b!="3V"&&b!=V)K 6.4x().3t((6[0]&&6[0].2i||T).5r(b));L a="";E.R(b||6,J(){E.R(6.3p,J(){7(6.15!=8)a+=6.15!=1?6.6K:E.1n.1u([6])})});K a},5m:J(b){7(6[0])E(b,6[0].2i).5k().3o(6[0]).2c(J(){L a=6;2b(a.1C)a=a.1C;K a}).3t(6);K 6},8w:J(a){K 6.R(J(){E(6).6z().5m(a)})},8p:J(a){K 6.R(J(){E(6).5m(a)})},3t:J(){K 6.3O(18,P,S,J(a){7(6.15==1)6.38(a)})},6q:J(){K 6.3O(18,P,P,J(a){7(6.15==1)6.3o(a,6.1C)})},6o:J(){K 6.3O(18,S,S,J(a){6.1a.3o(a,6)})},5a:J(){K 6.3O(18,S,P,J(a){6.1a.3o(a,6.2B)})},3h:J(){K 6.54||E([])},2s:J(b){L c=E.2c(6,J(a){K E.2s(b,a)});K 6.2F(/[^+>] [^+>]/.17(b)||b.1f("..")>-1?E.57(c):c)},5k:J(e){L f=6.2c(J(){7(E.14.1d&&!E.3E(6)){L a=6.69(P),4Y=T.3s("1x");4Y.38(a);K E.4a([4Y.3d])[0]}N K 6.69(P)});L d=f.2s("*").4R().R(J(){7(6[F]!=10)6[F]=V});7(e===P)6.2s("*").4R().R(J(i){7(6.15==3)K;L c=E.O(6,"2R");Q(L a 1p c)Q(L b 1p c[a])E.16.1b(d[i],a,c[a][b],c[a][b].O)});K f},1E:J(b){K 6.2F(E.1q(b)&&E.3y(6,J(a,i){K b.1P(a,i)})||E.3e(b,6))},56:J(b){7(b.1k==4e)7(G.17(b))K 6.2F(E.3e(b,6,P));N b=E.3e(b,6);L a=b.M&&b[b.M-1]!==10&&!b.15;K 6.1E(J(){K a?E.33(6,b)<0:6!=b})},1b:J(a){K!a?6:6.2F(E.37(6.22(),a.1k==4e?E(a).22():a.M!=10&&(!a.12||E.12(a,"3u"))?a:[a]))},3H:J(a){K a?E.3e(a,6).M>0:S},7j:J(a){K 6.3H("."+a)},5O:J(b){7(b==10){7(6.M){L c=6[0];7(E.12(c,"2k")){L e=c.3T,5I=[],11=c.11,2X=c.U=="2k-2X";7(e<0)K V;Q(L i=2X?e:0,2f=2X?e+1:11.M;i<2f;i++){L d=11[i];7(d.2p){b=E.14.1d&&!d.9J.1A.9y?d.1u:d.1A;7(2X)K b;5I.1g(b)}}K 5I}N K(6[0].1A||"").1r(/\\r/g,"")}K 10}K 6.R(J(){7(6.15!=1)K;7(b.1k==1M&&/5u|5t/.17(6.U))6.3k=(E.33(6.1A,b)>=0||E.33(6.31,b)>=0);N 7(E.12(6,"2k")){L a=b.1k==1M?b:[b];E("98",6).R(J(){6.2p=(E.33(6.1A,a)>=0||E.33(6.1u,a)>=0)});7(!a.M)6.3T=-1}N 6.1A=b})},3q:J(a){K a==10?(6.M?6[0].3d:V):6.4x().3t(a)},6S:J(a){K 6.5a(a).1V()},6Z:J(i){K 6.2K(i,i+1)},2K:J(){K 6.2F(1M.2l.2K.1i(6,18))},2c:J(b){K 6.2F(E.2c(6,J(a,i){K b.1P(a,i,a)}))},4R:J(){K 6.1b(6.54)},O:J(d,b){L a=d.23(".");a[1]=a[1]?"."+a[1]:"";7(b==V){L c=6.5n("8P"+a[1]+"!",[a[0]]);7(c==10&&6.M)c=E.O(6[0],d);K c==V&&a[1]?6.O(a[0]):c}N K 6.1N("8K"+a[1]+"!",[a[0],b]).R(J(){E.O(6,d,b)})},35:J(a){K 6.R(J(){E.35(6,a)})},3O:J(g,f,h,d){L e=6.M>1,3n;K 6.R(J(){7(!3n){3n=E.4a(g,6.2i);7(h)3n.8D()}L b=6;7(f&&E.12(6,"1O")&&E.12(3n[0],"4v"))b=6.3S("1U")[0]||6.38(6.2i.3s("1U"));L c=E([]);E.R(3n,J(){L a=e?E(6).5k(P)[0]:6;7(E.12(a,"1m")){c=c.1b(a)}N{7(a.15==1)c=c.1b(E("1m",a).1V());d.1P(b,a)}});c.R(6A)})}};E.2l.4T.2l=E.2l;J 6A(i,a){7(a.3Q)E.3P({1c:a.3Q,3l:S,1H:"1m"});N E.5g(a.1u||a.6x||a.3d||"");7(a.1a)a.1a.34(a)}E.1s=E.1n.1s=J(){L b=18[0]||{},i=1,M=18.M,5c=S,11;7(b.1k==8d){5c=b;b=18[1]||{};i=2}7(1o b!="3V"&&1o b!="J")b={};7(M==1){b=6;i=0}Q(;i<M;i++)7((11=18[i])!=V)Q(L a 1p 11){7(b===11[a])6w;7(5c&&11[a]&&1o 11[a]=="3V"&&b[a]&&!11[a].15)b[a]=E.1s(b[a],11[a]);N 7(11[a]!=10)b[a]=11[a]}K b};L F="3N"+(1B 3v()).3L(),6t=0,5b={};L H=/z-?4X|86-?84|1w|6k|7Z-?1R/i;E.1s({7Y:J(a){1e.$=D;7(a)1e.3N=w;K E},1q:J(a){K!!a&&1o a!="25"&&!a.12&&a.1k!=1M&&/J/i.17(a+"")},3E:J(a){K a.1F&&!a.1h||a.28&&a.2i&&!a.2i.1h},5g:J(a){a=E.3g(a);7(a){L b=T.3S("6f")[0]||T.1F,1m=T.3s("1m");1m.U="1u/4m";7(E.14.1d)1m.1u=a;N 1m.38(T.5r(a));b.38(1m);b.34(1m)}},12:J(b,a){K b.12&&b.12.2E()==a.2E()},1T:{},O:J(c,d,b){c=c==1e?5b:c;L a=c[F];7(!a)a=c[F]=++6t;7(d&&!E.1T[a])E.1T[a]={};7(b!=10)E.1T[a][d]=b;K d?E.1T[a][d]:a},35:J(c,b){c=c==1e?5b:c;L a=c[F];7(b){7(E.1T[a]){2V E.1T[a][b];b="";Q(b 1p E.1T[a])1Q;7(!b)E.35(c)}}N{1S{2V c[F]}1X(e){7(c.52)c.52(F)}2V E.1T[a]}},R:J(c,a,b){7(b){7(c.M==10){Q(L d 1p c)7(a.1i(c[d],b)===S)1Q}N Q(L i=0,M=c.M;i<M;i++)7(a.1i(c[i],b)===S)1Q}N{7(c.M==10){Q(L d 1p c)7(a.1P(c[d],d,c[d])===S)1Q}N Q(L i=0,M=c.M,1A=c[0];i<M&&a.1P(1A,i,1A)!==S;1A=c[++i]){}}K c},1l:J(b,a,c,i,d){7(E.1q(a))a=a.1P(b,i);K a&&a.1k==51&&c=="2o"&&!H.17(d)?a+"2S":a},1t:{1b:J(c,b){E.R((b||"").23(/\\s+/),J(i,a){7(c.15==1&&!E.1t.3Y(c.1t,a))c.1t+=(c.1t?" ":"")+a})},1V:J(c,b){7(c.15==1)c.1t=b!=10?E.3y(c.1t.23(/\\s+/),J(a){K!E.1t.3Y(b,a)}).6a(" "):""},3Y:J(b,a){K E.33(a,(b.1t||b).3X().23(/\\s+/))>-1}},68:J(b,c,a){L e={};Q(L d 1p c){e[d]=b.W[d];b.W[d]=c[d]}a.1P(b);Q(L d 1p c)b.W[d]=e[d]},1j:J(d,e,c){7(e=="27"||e=="1R"){L b,46={43:"4W",4U:"1Z",19:"3D"},3c=e=="27"?["7O","7M"]:["7J","7I"];J 5E(){b=e=="27"?d.7H:d.7F;L a=0,2N=0;E.R(3c,J(){a+=2M(E.2o(d,"7E"+6,P))||0;2N+=2M(E.2o(d,"2N"+6+"5X",P))||0});b-=24.7C(a+2N)}7(E(d).3H(":4d"))5E();N E.68(d,46,5E);K 24.2f(0,b)}K E.2o(d,e,c)},2o:J(e,k,j){L d;J 3x(b){7(!E.14.2d)K S;L a=T.4c.4K(b,V);K!a||a.4M("3x")==""}7(k=="1w"&&E.14.1d){d=E.1J(e.W,"1w");K d==""?"1":d}7(E.14.2z&&k=="19"){L c=e.W.50;e.W.50="0 7r 7o";e.W.50=c}7(k.1D(/4g/i))k=y;7(!j&&e.W&&e.W[k])d=e.W[k];N 7(T.4c&&T.4c.4K){7(k.1D(/4g/i))k="4g";k=k.1r(/([A-Z])/g,"-$1").2h();L h=T.4c.4K(e,V);7(h&&!3x(e))d=h.4M(k);N{L f=[],2C=[];Q(L a=e;a&&3x(a);a=a.1a)2C.4J(a);Q(L i=0;i<2C.M;i++)7(3x(2C[i])){f[i]=2C[i].W.19;2C[i].W.19="3D"}d=k=="19"&&f[2C.M-1]!=V?"2H":(h&&h.4M(k))||"";Q(L i=0;i<f.M;i++)7(f[i]!=V)2C[i].W.19=f[i]}7(k=="1w"&&d=="")d="1"}N 7(e.4n){L g=k.1r(/\\-(\\w)/g,J(a,b){K b.2E()});d=e.4n[k]||e.4n[g];7(!/^\\d+(2S)?$/i.17(d)&&/^\\d/.17(d)){L l=e.W.26,3K=e.3K.26;e.3K.26=e.4n.26;e.W.26=d||0;d=e.W.7f+"2S";e.W.26=l;e.3K.26=3K}}K d},4a:J(l,h){L k=[];h=h||T;7(1o h.3s==\'10\')h=h.2i||h[0]&&h[0].2i||T;E.R(l,J(i,d){7(!d)K;7(d.1k==51)d=d.3X();7(1o d=="25"){d=d.1r(/(<(\\w+)[^>]*?)\\/>/g,J(b,a,c){K c.1D(/^(aa|a6|7e|a5|4D|7a|a0|3m|9W|9U|9S)$/i)?b:a+"></"+c+">"});L f=E.3g(d).2h(),1x=h.3s("1x");L e=!f.1f("<9P")&&[1,"<2k 74=\'74\'>","</2k>"]||!f.1f("<9M")&&[1,"<73>","</73>"]||f.1D(/^<(9G|1U|9E|9B|9x)/)&&[1,"<1O>","</1O>"]||!f.1f("<4v")&&[2,"<1O><1U>","</1U></1O>"]||(!f.1f("<9w")||!f.1f("<9v"))&&[3,"<1O><1U><4v>","</4v></1U></1O>"]||!f.1f("<7e")&&[2,"<1O><1U></1U><6V>","</6V></1O>"]||E.14.1d&&[1,"1x<1x>","</1x>"]||[0,"",""];1x.3d=e[1]+d+e[2];2b(e[0]--)1x=1x.5o;7(E.14.1d){L g=!f.1f("<1O")&&f.1f("<1U")<0?1x.1C&&1x.1C.3p:e[1]=="<1O>"&&f.1f("<1U")<0?1x.3p:[];Q(L j=g.M-1;j>=0;--j)7(E.12(g[j],"1U")&&!g[j].3p.M)g[j].1a.34(g[j]);7(/^\\s/.17(d))1x.3o(h.5r(d.1D(/^\\s*/)[0]),1x.1C)}d=E.2I(1x.3p)}7(d.M===0&&(!E.12(d,"3u")&&!E.12(d,"2k")))K;7(d[0]==10||E.12(d,"3u")||d.11)k.1g(d);N k=E.37(k,d)});K k},1J:J(d,e,c){7(!d||d.15==3||d.15==8)K 10;L f=E.3E(d)?{}:E.46;7(e=="2p"&&E.14.2d)d.1a.3T;7(f[e]){7(c!=10)d[f[e]]=c;K d[f[e]]}N 7(E.14.1d&&e=="W")K E.1J(d.W,"9u",c);N 7(c==10&&E.14.1d&&E.12(d,"3u")&&(e=="9r"||e=="9o"))K d.9m(e).6K;N 7(d.28){7(c!=10){7(e=="U"&&E.12(d,"4D")&&d.1a)6Q"U 9i 9h\'t 9g 9e";d.9b(e,""+c)}7(E.14.1d&&/6O|3Q/.17(e)&&!E.3E(d))K d.4z(e,2);K d.4z(e)}N{7(e=="1w"&&E.14.1d){7(c!=10){d.6k=1;d.1E=(d.1E||"").1r(/6M\\([^)]*\\)/,"")+(2M(c).3X()=="96"?"":"6M(1w="+c*6L+")")}K d.1E&&d.1E.1f("1w=")>=0?(2M(d.1E.1D(/1w=([^)]*)/)[1])/6L).3X():""}e=e.1r(/-([a-z])/95,J(a,b){K b.2E()});7(c!=10)d[e]=c;K d[e]}},3g:J(a){K(a||"").1r(/^\\s+|\\s+$/g,"")},2I:J(b){L a=[];7(1o b!="93")Q(L i=0,M=b.M;i<M;i++)a.1g(b[i]);N a=b.2K(0);K a},33:J(b,a){Q(L i=0,M=a.M;i<M;i++)7(a[i]==b)K i;K-1},37:J(a,b){7(E.14.1d){Q(L i=0;b[i];i++)7(b[i].15!=8)a.1g(b[i])}N Q(L i=0;b[i];i++)a.1g(b[i]);K a},57:J(a){L c=[],2r={};1S{Q(L i=0,M=a.M;i<M;i++){L b=E.O(a[i]);7(!2r[b]){2r[b]=P;c.1g(a[i])}}}1X(e){c=a}K c},3y:J(c,a,d){L b=[];Q(L i=0,M=c.M;i<M;i++)7(!d&&a(c[i],i)||d&&!a(c[i],i))b.1g(c[i]);K b},2c:J(d,a){L c=[];Q(L i=0,M=d.M;i<M;i++){L b=a(d[i],i);7(b!==V&&b!=10){7(b.1k!=1M)b=[b];c=c.71(b)}}K c}});L v=8Y.8W.2h();E.14={5K:(v.1D(/.+(?:8T|8S|8R|8O)[\\/: ]([\\d.]+)/)||[])[1],2d:/77/.17(v),2z:/2z/.17(v),1d:/1d/.17(v)&&!/2z/.17(v),48:/48/.17(v)&&!/(8L|77)/.17(v)};L y=E.14.1d?"6H":"75";E.1s({8I:!E.14.1d||T.6F=="79",46:{"Q":"8F","8E":"1t","4g":y,75:y,6H:y,3d:"3d",1t:"1t",1A:"1A",2Y:"2Y",3k:"3k",8C:"8B",2p:"2p",8A:"8z",3T:"3T",6C:"6C",28:"28",12:"12"}});E.R({6B:J(a){K a.1a},8y:J(a){K E.4u(a,"1a")},8x:J(a){K E.2Z(a,2,"2B")},8v:J(a){K E.2Z(a,2,"4t")},8u:J(a){K E.4u(a,"2B")},8t:J(a){K E.4u(a,"4t")},8s:J(a){K E.5i(a.1a.1C,a)},8r:J(a){K E.5i(a.1C)},6z:J(a){K E.12(a,"8q")?a.8o||a.8n.T:E.2I(a.3p)}},J(c,d){E.1n[c]=J(b){L a=E.2c(6,d);7(b&&1o b=="25")a=E.3e(b,a);K 6.2F(E.57(a))}});E.R({6y:"3t",8m:"6q",3o:"6o",8l:"5a",8k:"6S"},J(c,b){E.1n[c]=J(){L a=18;K 6.R(J(){Q(L i=0,M=a.M;i<M;i++)E(a[i])[b](6)})}});E.R({8j:J(a){E.1J(6,a,"");7(6.15==1)6.52(a)},8i:J(a){E.1t.1b(6,a)},8h:J(a){E.1t.1V(6,a)},8g:J(a){E.1t[E.1t.3Y(6,a)?"1V":"1b"](6,a)},1V:J(a){7(!a||E.1E(a,[6]).r.M){E("*",6).1b(6).R(J(){E.16.1V(6);E.35(6)});7(6.1a)6.1a.34(6)}},4x:J(){E(">*",6).1V();2b(6.1C)6.34(6.1C)}},J(a,b){E.1n[a]=J(){K 6.R(b,18)}});E.R(["8f","5X"],J(i,c){L b=c.2h();E.1n[b]=J(a){K 6[0]==1e?E.14.2z&&T.1h["5e"+c]||E.14.2d&&1e["8e"+c]||T.6F=="79"&&T.1F["5e"+c]||T.1h["5e"+c]:6[0]==T?24.2f(24.2f(T.1h["5d"+c],T.1F["5d"+c]),24.2f(T.1h["5L"+c],T.1F["5L"+c])):a==10?(6.M?E.1j(6[0],b):V):6.1j(b,a.1k==4e?a:a+"2S")}});L C=E.14.2d&&4s(E.14.5K)<8c?"(?:[\\\\w*4r-]|\\\\\\\\.)":"(?:[\\\\w\\8b-\\8a*4r-]|\\\\\\\\.)",6v=1B 4q("^>\\\\s*("+C+"+)"),6u=1B 4q("^("+C+"+)(#)("+C+"+)"),6s=1B 4q("^([#.]?)("+C+"*)");E.1s({6r:{"":J(a,i,m){K m[2]=="*"||E.12(a,m[2])},"#":J(a,i,m){K a.4z("2w")==m[2]},":":{89:J(a,i,m){K i<m[3]-0},88:J(a,i,m){K i>m[3]-0},2Z:J(a,i,m){K m[3]-0==i},6Z:J(a,i,m){K m[3]-0==i},3j:J(a,i){K i==0},3J:J(a,i,m,r){K i==r.M-1},6n:J(a,i){K i%2==0},6l:J(a,i){K i%2},"3j-4p":J(a){K a.1a.3S("*")[0]==a},"3J-4p":J(a){K E.2Z(a.1a.5o,1,"4t")==a},"83-4p":J(a){K!E.2Z(a.1a.5o,2,"4t")},6B:J(a){K a.1C},4x:J(a){K!a.1C},82:J(a,i,m){K(a.6x||a.81||E(a).1u()||"").1f(m[3])>=0},4d:J(a){K"1Z"!=a.U&&E.1j(a,"19")!="2H"&&E.1j(a,"4U")!="1Z"},1Z:J(a){K"1Z"==a.U||E.1j(a,"19")=="2H"||E.1j(a,"4U")=="1Z"},80:J(a){K!a.2Y},2Y:J(a){K a.2Y},3k:J(a){K a.3k},2p:J(a){K a.2p||E.1J(a,"2p")},1u:J(a){K"1u"==a.U},5u:J(a){K"5u"==a.U},5t:J(a){K"5t"==a.U},59:J(a){K"59"==a.U},3I:J(a){K"3I"==a.U},58:J(a){K"58"==a.U},6j:J(a){K"6j"==a.U},6i:J(a){K"6i"==a.U},2G:J(a){K"2G"==a.U||E.12(a,"2G")},4D:J(a){K/4D|2k|6h|2G/i.17(a.12)},3Y:J(a,i,m){K E.2s(m[3],a).M},7X:J(a){K/h\\d/i.17(a.12)},7W:J(a){K E.3y(E.3G,J(b){K a==b.Y}).M}}},6g:[/^(\\[) *@?([\\w-]+) *([!*$^~=]*) *(\'?"?)(.*?)\\4 *\\]/,/^(:)([\\w-]+)\\("?\'?(.*?(\\(.*?\\))?[^(]*?)"?\'?\\)/,1B 4q("^([:.#]*)("+C+"+)")],3e:J(a,c,b){L d,2m=[];2b(a&&a!=d){d=a;L f=E.1E(a,c,b);a=f.t.1r(/^\\s*,\\s*/,"");2m=b?c=f.r:E.37(2m,f.r)}K 2m},2s:J(t,p){7(1o t!="25")K[t];7(p&&p.15!=1&&p.15!=9)K[];p=p||T;L d=[p],2r=[],3J,12;2b(t&&3J!=t){L r=[];3J=t;t=E.3g(t);L o=S;L g=6v;L m=g.2O(t);7(m){12=m[1].2E();Q(L i=0;d[i];i++)Q(L c=d[i].1C;c;c=c.2B)7(c.15==1&&(12=="*"||c.12.2E()==12))r.1g(c);d=r;t=t.1r(g,"");7(t.1f(" ")==0)6w;o=P}N{g=/^([>+~])\\s*(\\w*)/i;7((m=g.2O(t))!=V){r=[];L l={};12=m[2].2E();m=m[1];Q(L j=0,3f=d.M;j<3f;j++){L n=m=="~"||m=="+"?d[j].2B:d[j].1C;Q(;n;n=n.2B)7(n.15==1){L h=E.O(n);7(m=="~"&&l[h])1Q;7(!12||n.12.2E()==12){7(m=="~")l[h]=P;r.1g(n)}7(m=="+")1Q}}d=r;t=E.3g(t.1r(g,""));o=P}}7(t&&!o){7(!t.1f(",")){7(p==d[0])d.4l();2r=E.37(2r,d);r=d=[p];t=" "+t.6e(1,t.M)}N{L k=6u;L m=k.2O(t);7(m){m=[0,m[2],m[3],m[1]]}N{k=6s;m=k.2O(t)}m[2]=m[2].1r(/\\\\/g,"");L f=d[d.M-1];7(m[1]=="#"&&f&&f.5J&&!E.3E(f)){L q=f.5J(m[2]);7((E.14.1d||E.14.2z)&&q&&1o q.2w=="25"&&q.2w!=m[2])q=E(\'[@2w="\'+m[2]+\'"]\',f)[0];d=r=q&&(!m[3]||E.12(q,m[3]))?[q]:[]}N{Q(L i=0;d[i];i++){L a=m[1]=="#"&&m[3]?m[3]:m[1]!=""||m[0]==""?"*":m[2];7(a=="*"&&d[i].12.2h()=="3V")a="3m";r=E.37(r,d[i].3S(a))}7(m[1]==".")r=E.55(r,m[2]);7(m[1]=="#"){L e=[];Q(L i=0;r[i];i++)7(r[i].4z("2w")==m[2]){e=[r[i]];1Q}r=e}d=r}t=t.1r(k,"")}}7(t){L b=E.1E(t,r);d=r=b.r;t=E.3g(b.t)}}7(t)d=[];7(d&&p==d[0])d.4l();2r=E.37(2r,d);K 2r},55:J(r,m,a){m=" "+m+" ";L c=[];Q(L i=0;r[i];i++){L b=(" "+r[i].1t+" ").1f(m)>=0;7(!a&&b||a&&!b)c.1g(r[i])}K c},1E:J(t,r,h){L d;2b(t&&t!=d){d=t;L p=E.6g,m;Q(L i=0;p[i];i++){m=p[i].2O(t);7(m){t=t.7V(m[0].M);m[2]=m[2].1r(/\\\\/g,"");1Q}}7(!m)1Q;7(m[1]==":"&&m[2]=="56")r=G.17(m[3])?E.1E(m[3],r,P).r:E(r).56(m[3]);N 7(m[1]==".")r=E.55(r,m[2],h);N 7(m[1]=="["){L g=[],U=m[3];Q(L i=0,3f=r.M;i<3f;i++){L a=r[i],z=a[E.46[m[2]]||m[2]];7(z==V||/6O|3Q|2p/.17(m[2]))z=E.1J(a,m[2])||\'\';7((U==""&&!!z||U=="="&&z==m[5]||U=="!="&&z!=m[5]||U=="^="&&z&&!z.1f(m[5])||U=="$="&&z.6e(z.M-m[5].M)==m[5]||(U=="*="||U=="~=")&&z.1f(m[5])>=0)^h)g.1g(a)}r=g}N 7(m[1]==":"&&m[2]=="2Z-4p"){L e={},g=[],17=/(-?)(\\d*)n((?:\\+|-)?\\d*)/.2O(m[3]=="6n"&&"2n"||m[3]=="6l"&&"2n+1"||!/\\D/.17(m[3])&&"7U+"+m[3]||m[3]),3j=(17[1]+(17[2]||1))-0,d=17[3]-0;Q(L i=0,3f=r.M;i<3f;i++){L j=r[i],1a=j.1a,2w=E.O(1a);7(!e[2w]){L c=1;Q(L n=1a.1C;n;n=n.2B)7(n.15==1)n.4k=c++;e[2w]=P}L b=S;7(3j==0){7(j.4k==d)b=P}N 7((j.4k-d)%3j==0&&(j.4k-d)/3j>=0)b=P;7(b^h)g.1g(j)}r=g}N{L f=E.6r[m[1]];7(1o f=="3V")f=f[m[2]];7(1o f=="25")f=6c("S||J(a,i){K "+f+";}");r=E.3y(r,J(a,i){K f(a,i,m,r)},h)}}K{r:r,t:t}},4u:J(b,c){L d=[];L a=b[c];2b(a&&a!=T){7(a.15==1)d.1g(a);a=a[c]}K d},2Z:J(a,e,c,b){e=e||1;L d=0;Q(;a;a=a[c])7(a.15==1&&++d==e)1Q;K a},5i:J(n,a){L r=[];Q(;n;n=n.2B){7(n.15==1&&(!a||n!=a))r.1g(n)}K r}});E.16={1b:J(f,i,g,e){7(f.15==3||f.15==8)K;7(E.14.1d&&f.53!=10)f=1e;7(!g.2D)g.2D=6.2D++;7(e!=10){L h=g;g=J(){K h.1i(6,18)};g.O=e;g.2D=h.2D}L j=E.O(f,"2R")||E.O(f,"2R",{}),1v=E.O(f,"1v")||E.O(f,"1v",J(){L a;7(1o E=="10"||E.16.5f)K a;a=E.16.1v.1i(18.3R.Y,18);K a});1v.Y=f;E.R(i.23(/\\s+/),J(c,b){L a=b.23(".");b=a[0];g.U=a[1];L d=j[b];7(!d){d=j[b]={};7(!E.16.2y[b]||E.16.2y[b].4j.1P(f)===S){7(f.3F)f.3F(b,1v,S);N 7(f.6b)f.6b("4i"+b,1v)}}d[g.2D]=g;E.16.2a[b]=P});f=V},2D:1,2a:{},1V:J(e,h,f){7(e.15==3||e.15==8)K;L i=E.O(e,"2R"),29,4X;7(i){7(h==10||(1o h=="25"&&h.7T(0)=="."))Q(L g 1p i)6.1V(e,g+(h||""));N{7(h.U){f=h.2q;h=h.U}E.R(h.23(/\\s+/),J(b,a){L c=a.23(".");a=c[0];7(i[a]){7(f)2V i[a][f.2D];N Q(f 1p i[a])7(!c[1]||i[a][f].U==c[1])2V i[a][f];Q(29 1p i[a])1Q;7(!29){7(!E.16.2y[a]||E.16.2y[a].4h.1P(e)===S){7(e.67)e.67(a,E.O(e,"1v"),S);N 7(e.66)e.66("4i"+a,E.O(e,"1v"))}29=V;2V i[a]}}})}Q(29 1p i)1Q;7(!29){L d=E.O(e,"1v");7(d)d.Y=V;E.35(e,"2R");E.35(e,"1v")}}},1N:J(g,c,d,f,h){c=E.2I(c||[]);7(g.1f("!")>=0){g=g.2K(0,-1);L a=P}7(!d){7(6.2a[g])E("*").1b([1e,T]).1N(g,c)}N{7(d.15==3||d.15==8)K 10;L b,29,1n=E.1q(d[g]||V),16=!c[0]||!c[0].36;7(16)c.4J(6.4Z({U:g,2L:d}));c[0].U=g;7(a)c[0].65=P;7(E.1q(E.O(d,"1v")))b=E.O(d,"1v").1i(d,c);7(!1n&&d["4i"+g]&&d["4i"+g].1i(d,c)===S)b=S;7(16)c.4l();7(h&&E.1q(h)){29=h.1i(d,b==V?c:c.71(b));7(29!==10)b=29}7(1n&&f!==S&&b!==S&&!(E.12(d,\'a\')&&g=="4V")){6.5f=P;1S{d[g]()}1X(e){}}6.5f=S}K b},1v:J(c){L a;c=E.16.4Z(c||1e.16||{});L b=c.U.23(".");c.U=b[0];L f=E.O(6,"2R")&&E.O(6,"2R")[c.U],42=1M.2l.2K.1P(18,1);42.4J(c);Q(L j 1p f){L d=f[j];42[0].2q=d;42[0].O=d.O;7(!b[1]&&!c.65||d.U==b[1]){L e=d.1i(6,42);7(a!==S)a=e;7(e===S){c.36();c.44()}}}7(E.14.1d)c.2L=c.36=c.44=c.2q=c.O=V;K a},4Z:J(c){L a=c;c=E.1s({},a);c.36=J(){7(a.36)a.36();a.7S=S};c.44=J(){7(a.44)a.44();a.7R=P};7(!c.2L)c.2L=c.7Q||T;7(c.2L.15==3)c.2L=a.2L.1a;7(!c.4S&&c.5w)c.4S=c.5w==c.2L?c.7P:c.5w;7(c.64==V&&c.63!=V){L b=T.1F,1h=T.1h;c.64=c.63+(b&&b.2v||1h&&1h.2v||0)-(b.62||0);c.7N=c.7L+(b&&b.2x||1h&&1h.2x||0)-(b.60||0)}7(!c.3c&&((c.4f||c.4f===0)?c.4f:c.5Z))c.3c=c.4f||c.5Z;7(!c.7b&&c.5Y)c.7b=c.5Y;7(!c.3c&&c.2G)c.3c=(c.2G&1?1:(c.2G&2?3:(c.2G&4?2:0)));K c},2y:{21:{4j:J(){5M();K},4h:J(){K}},3C:{4j:J(){7(E.14.1d)K S;E(6).2j("4P",E.16.2y.3C.2q);K P},4h:J(){7(E.14.1d)K S;E(6).3w("4P",E.16.2y.3C.2q);K P},2q:J(a){7(I(a,6))K P;18[0].U="3C";K E.16.1v.1i(6,18)}},3B:{4j:J(){7(E.14.1d)K S;E(6).2j("4O",E.16.2y.3B.2q);K P},4h:J(){7(E.14.1d)K S;E(6).3w("4O",E.16.2y.3B.2q);K P},2q:J(a){7(I(a,6))K P;18[0].U="3B";K E.16.1v.1i(6,18)}}}};E.1n.1s({2j:J(c,a,b){K c=="4H"?6.2X(c,a,b):6.R(J(){E.16.1b(6,c,b||a,b&&a)})},2X:J(d,b,c){K 6.R(J(){E.16.1b(6,d,J(a){E(6).3w(a);K(c||b).1i(6,18)},c&&b)})},3w:J(a,b){K 6.R(J(){E.16.1V(6,a,b)})},1N:J(c,a,b){K 6.R(J(){E.16.1N(c,a,6,P,b)})},5n:J(c,a,b){7(6[0])K E.16.1N(c,a,6[0],S,b);K 10},2g:J(){L b=18;K 6.4V(J(a){6.4N=0==6.4N?1:0;a.36();K b[6.4N].1i(6,18)||S})},7D:J(a,b){K 6.2j(\'3C\',a).2j(\'3B\',b)},21:J(a){5M();7(E.2Q)a.1P(T,E);N E.3A.1g(J(){K a.1P(6,E)});K 6}});E.1s({2Q:S,3A:[],21:J(){7(!E.2Q){E.2Q=P;7(E.3A){E.R(E.3A,J(){6.1i(T)});E.3A=V}E(T).5n("21")}}});L x=S;J 5M(){7(x)K;x=P;7(T.3F&&!E.14.2z)T.3F("5W",E.21,S);7(E.14.1d&&1e==3b)(J(){7(E.2Q)K;1S{T.1F.7B("26")}1X(3a){3z(18.3R,0);K}E.21()})();7(E.14.2z)T.3F("5W",J(){7(E.2Q)K;Q(L i=0;i<T.4L.M;i++)7(T.4L[i].2Y){3z(18.3R,0);K}E.21()},S);7(E.14.2d){L a;(J(){7(E.2Q)K;7(T.39!="5V"&&T.39!="1y"){3z(18.3R,0);K}7(a===10)a=E("W, 7a[7A=7z]").M;7(T.4L.M!=a){3z(18.3R,0);K}E.21()})()}E.16.1b(1e,"3U",E.21)}E.R(("7y,7x,3U,7w,5d,4H,4V,7v,"+"7G,7u,7t,4P,4O,7s,2k,"+"58,7K,7q,7p,3a").23(","),J(i,b){E.1n[b]=J(a){K a?6.2j(b,a):6.1N(b)}});L I=J(a,c){L b=a.4S;2b(b&&b!=c)1S{b=b.1a}1X(3a){b=c}K b==c};E(1e).2j("4H",J(){E("*").1b(T).3w()});E.1n.1s({3U:J(g,d,c){7(E.1q(g))K 6.2j("3U",g);L e=g.1f(" ");7(e>=0){L i=g.2K(e,g.M);g=g.2K(0,e)}c=c||J(){};L f="4Q";7(d)7(E.1q(d)){c=d;d=V}N{d=E.3m(d);f="61"}L h=6;E.3P({1c:g,U:f,1H:"3q",O:d,1y:J(a,b){7(b=="1W"||b=="5U")h.3q(i?E("<1x/>").3t(a.4b.1r(/<1m(.|\\s)*?\\/1m>/g,"")).2s(i):a.4b);h.R(c,[a.4b,b,a])}});K 6},7n:J(){K E.3m(6.5T())},5T:J(){K 6.2c(J(){K E.12(6,"3u")?E.2I(6.7m):6}).1E(J(){K 6.31&&!6.2Y&&(6.3k||/2k|6h/i.17(6.12)||/1u|1Z|3I/i.17(6.U))}).2c(J(i,c){L b=E(6).5O();K b==V?V:b.1k==1M?E.2c(b,J(a,i){K{31:c.31,1A:a}}):{31:c.31,1A:b}}).22()}});E.R("5S,6d,5R,6D,5Q,6m".23(","),J(i,o){E.1n[o]=J(f){K 6.2j(o,f)}});L B=(1B 3v).3L();E.1s({22:J(d,b,a,c){7(E.1q(b)){a=b;b=V}K E.3P({U:"4Q",1c:d,O:b,1W:a,1H:c})},7l:J(b,a){K E.22(b,V,a,"1m")},7k:J(c,b,a){K E.22(c,b,a,"3i")},7i:J(d,b,a,c){7(E.1q(b)){a=b;b={}}K E.3P({U:"61",1c:d,O:b,1W:a,1H:c})},85:J(a){E.1s(E.4I,a)},4I:{2a:P,U:"4Q",2U:0,5P:"4o/x-7h-3u-7g",5N:P,3l:P,O:V,6p:V,3I:V,49:{3M:"4o/3M, 1u/3M",3q:"1u/3q",1m:"1u/4m, 4o/4m",3i:"4o/3i, 1u/4m",1u:"1u/a7",4G:"*/*"}},4F:{},3P:J(s){L f,2W=/=\\?(&|$)/g,1z,O;s=E.1s(P,s,E.1s(P,{},E.4I,s));7(s.O&&s.5N&&1o s.O!="25")s.O=E.3m(s.O);7(s.1H=="4E"){7(s.U.2h()=="22"){7(!s.1c.1D(2W))s.1c+=(s.1c.1D(/\\?/)?"&":"?")+(s.4E||"7d")+"=?"}N 7(!s.O||!s.O.1D(2W))s.O=(s.O?s.O+"&":"")+(s.4E||"7d")+"=?";s.1H="3i"}7(s.1H=="3i"&&(s.O&&s.O.1D(2W)||s.1c.1D(2W))){f="4E"+B++;7(s.O)s.O=(s.O+"").1r(2W,"="+f+"$1");s.1c=s.1c.1r(2W,"="+f+"$1");s.1H="1m";1e[f]=J(a){O=a;1W();1y();1e[f]=10;1S{2V 1e[f]}1X(e){}7(h)h.34(g)}}7(s.1H=="1m"&&s.1T==V)s.1T=S;7(s.1T===S&&s.U.2h()=="22"){L i=(1B 3v()).3L();L j=s.1c.1r(/(\\?|&)4r=.*?(&|$)/,"$a4="+i+"$2");s.1c=j+((j==s.1c)?(s.1c.1D(/\\?/)?"&":"?")+"4r="+i:"")}7(s.O&&s.U.2h()=="22"){s.1c+=(s.1c.1D(/\\?/)?"&":"?")+s.O;s.O=V}7(s.2a&&!E.5H++)E.16.1N("5S");7((!s.1c.1f("a3")||!s.1c.1f("//"))&&s.1H=="1m"&&s.U.2h()=="22"){L h=T.3S("6f")[0];L g=T.3s("1m");g.3Q=s.1c;7(s.7c)g.a2=s.7c;7(!f){L l=S;g.9Z=g.9Y=J(){7(!l&&(!6.39||6.39=="5V"||6.39=="1y")){l=P;1W();1y();h.34(g)}}}h.38(g);K 10}L m=S;L k=1e.78?1B 78("9X.9V"):1B 76();k.9T(s.U,s.1c,s.3l,s.6p,s.3I);1S{7(s.O)k.4C("9R-9Q",s.5P);7(s.5C)k.4C("9O-5A-9N",E.4F[s.1c]||"9L, 9K 9I 9H 5z:5z:5z 9F");k.4C("X-9C-9A","76");k.4C("9z",s.1H&&s.49[s.1H]?s.49[s.1H]+", */*":s.49.4G)}1X(e){}7(s.6Y)s.6Y(k);7(s.2a)E.16.1N("6m",[k,s]);L c=J(a){7(!m&&k&&(k.39==4||a=="2U")){m=P;7(d){6I(d);d=V}1z=a=="2U"&&"2U"||!E.6X(k)&&"3a"||s.5C&&E.6J(k,s.1c)&&"5U"||"1W";7(1z=="1W"){1S{O=E.6W(k,s.1H)}1X(e){1z="5x"}}7(1z=="1W"){L b;1S{b=k.5q("6U-5A")}1X(e){}7(s.5C&&b)E.4F[s.1c]=b;7(!f)1W()}N E.5v(s,k,1z);1y();7(s.3l)k=V}};7(s.3l){L d=53(c,13);7(s.2U>0)3z(J(){7(k){k.9t();7(!m)c("2U")}},s.2U)}1S{k.9s(s.O)}1X(e){E.5v(s,k,V,e)}7(!s.3l)c();J 1W(){7(s.1W)s.1W(O,1z);7(s.2a)E.16.1N("5Q",[k,s])}J 1y(){7(s.1y)s.1y(k,1z);7(s.2a)E.16.1N("5R",[k,s]);7(s.2a&&!--E.5H)E.16.1N("6d")}K k},5v:J(s,a,b,e){7(s.3a)s.3a(a,b,e);7(s.2a)E.16.1N("6D",[a,s,e])},5H:0,6X:J(r){1S{K!r.1z&&9q.9p=="59:"||(r.1z>=6T&&r.1z<9n)||r.1z==6R||r.1z==9l||E.14.2d&&r.1z==10}1X(e){}K S},6J:J(a,c){1S{L b=a.5q("6U-5A");K a.1z==6R||b==E.4F[c]||E.14.2d&&a.1z==10}1X(e){}K S},6W:J(r,b){L c=r.5q("9k-U");L d=b=="3M"||!b&&c&&c.1f("3M")>=0;L a=d?r.9j:r.4b;7(d&&a.1F.28=="5x")6Q"5x";7(b=="1m")E.5g(a);7(b=="3i")a=6c("("+a+")");K a},3m:J(a){L s=[];7(a.1k==1M||a.5h)E.R(a,J(){s.1g(3r(6.31)+"="+3r(6.1A))});N Q(L j 1p a)7(a[j]&&a[j].1k==1M)E.R(a[j],J(){s.1g(3r(j)+"="+3r(6))});N s.1g(3r(j)+"="+3r(a[j]));K s.6a("&").1r(/%20/g,"+")}});E.1n.1s({1G:J(c,b){K c?6.2e({1R:"1G",27:"1G",1w:"1G"},c,b):6.1E(":1Z").R(J(){6.W.19=6.5s||"";7(E.1j(6,"19")=="2H"){L a=E("<"+6.28+" />").6y("1h");6.W.19=a.1j("19");7(6.W.19=="2H")6.W.19="3D";a.1V()}}).3h()},1I:J(b,a){K b?6.2e({1R:"1I",27:"1I",1w:"1I"},b,a):6.1E(":4d").R(J(){6.5s=6.5s||E.1j(6,"19");6.W.19="2H"}).3h()},6N:E.1n.2g,2g:J(a,b){K E.1q(a)&&E.1q(b)?6.6N(a,b):a?6.2e({1R:"2g",27:"2g",1w:"2g"},a,b):6.R(J(){E(6)[E(6).3H(":1Z")?"1G":"1I"]()})},9f:J(b,a){K 6.2e({1R:"1G"},b,a)},9d:J(b,a){K 6.2e({1R:"1I"},b,a)},9c:J(b,a){K 6.2e({1R:"2g"},b,a)},9a:J(b,a){K 6.2e({1w:"1G"},b,a)},99:J(b,a){K 6.2e({1w:"1I"},b,a)},97:J(c,a,b){K 6.2e({1w:a},c,b)},2e:J(l,k,j,h){L i=E.6P(k,j,h);K 6[i.2P===S?"R":"2P"](J(){7(6.15!=1)K S;L g=E.1s({},i);L f=E(6).3H(":1Z"),4A=6;Q(L p 1p l){7(l[p]=="1I"&&f||l[p]=="1G"&&!f)K E.1q(g.1y)&&g.1y.1i(6);7(p=="1R"||p=="27"){g.19=E.1j(6,"19");g.32=6.W.32}}7(g.32!=V)6.W.32="1Z";g.40=E.1s({},l);E.R(l,J(c,a){L e=1B E.2t(4A,g,c);7(/2g|1G|1I/.17(a))e[a=="2g"?f?"1G":"1I":a](l);N{L b=a.3X().1D(/^([+-]=)?([\\d+-.]+)(.*)$/),1Y=e.2m(P)||0;7(b){L d=2M(b[2]),2A=b[3]||"2S";7(2A!="2S"){4A.W[c]=(d||1)+2A;1Y=((d||1)/e.2m(P))*1Y;4A.W[c]=1Y+2A}7(b[1])d=((b[1]=="-="?-1:1)*d)+1Y;e.45(1Y,d,2A)}N e.45(1Y,a,"")}});K P})},2P:J(a,b){7(E.1q(a)||(a&&a.1k==1M)){b=a;a="2t"}7(!a||(1o a=="25"&&!b))K A(6[0],a);K 6.R(J(){7(b.1k==1M)A(6,a,b);N{A(6,a).1g(b);7(A(6,a).M==1)b.1i(6)}})},94:J(b,c){L a=E.3G;7(b)6.2P([]);6.R(J(){Q(L i=a.M-1;i>=0;i--)7(a[i].Y==6){7(c)a[i](P);a.72(i,1)}});7(!c)6.5p();K 6}});L A=J(b,c,a){7(!b)K 10;c=c||"2t";L q=E.O(b,c+"2P");7(!q||a)q=E.O(b,c+"2P",a?E.2I(a):[]);K q};E.1n.5p=J(a){a=a||"2t";K 6.R(J(){L q=A(6,a);q.4l();7(q.M)q[0].1i(6)})};E.1s({6P:J(b,a,c){L d=b&&b.1k==92?b:{1y:c||!c&&a||E.1q(b)&&b,2u:b,3Z:c&&a||a&&a.1k!=91&&a};d.2u=(d.2u&&d.2u.1k==51?d.2u:{90:8Z,9D:6T}[d.2u])||8X;d.5y=d.1y;d.1y=J(){7(d.2P!==S)E(6).5p();7(E.1q(d.5y))d.5y.1i(6)};K d},3Z:{70:J(p,n,b,a){K b+a*p},5j:J(p,n,b,a){K((-24.8V(p*24.8U)/2)+0.5)*a+b}},3G:[],3W:V,2t:J(b,c,a){6.11=c;6.Y=b;6.1l=a;7(!c.47)c.47={}}});E.2t.2l={4y:J(){7(6.11.30)6.11.30.1i(6.Y,[6.2J,6]);(E.2t.30[6.1l]||E.2t.30.4G)(6);7(6.1l=="1R"||6.1l=="27")6.Y.W.19="3D"},2m:J(a){7(6.Y[6.1l]!=V&&6.Y.W[6.1l]==V)K 6.Y[6.1l];L r=2M(E.1j(6.Y,6.1l,a));K r&&r>-8Q?r:2M(E.2o(6.Y,6.1l))||0},45:J(c,b,d){6.5B=(1B 3v()).3L();6.1Y=c;6.3h=b;6.2A=d||6.2A||"2S";6.2J=6.1Y;6.4B=6.4w=0;6.4y();L e=6;J t(a){K e.30(a)}t.Y=6.Y;E.3G.1g(t);7(E.3W==V){E.3W=53(J(){L a=E.3G;Q(L i=0;i<a.M;i++)7(!a[i]())a.72(i--,1);7(!a.M){6I(E.3W);E.3W=V}},13)}},1G:J(){6.11.47[6.1l]=E.1J(6.Y.W,6.1l);6.11.1G=P;6.45(0,6.2m());7(6.1l=="27"||6.1l=="1R")6.Y.W[6.1l]="8N";E(6.Y).1G()},1I:J(){6.11.47[6.1l]=E.1J(6.Y.W,6.1l);6.11.1I=P;6.45(6.2m(),0)},30:J(a){L t=(1B 3v()).3L();7(a||t>6.11.2u+6.5B){6.2J=6.3h;6.4B=6.4w=1;6.4y();6.11.40[6.1l]=P;L b=P;Q(L i 1p 6.11.40)7(6.11.40[i]!==P)b=S;7(b){7(6.11.19!=V){6.Y.W.32=6.11.32;6.Y.W.19=6.11.19;7(E.1j(6.Y,"19")=="2H")6.Y.W.19="3D"}7(6.11.1I)6.Y.W.19="2H";7(6.11.1I||6.11.1G)Q(L p 1p 6.11.40)E.1J(6.Y.W,p,6.11.47[p])}7(b&&E.1q(6.11.1y))6.11.1y.1i(6.Y);K S}N{L n=t-6.5B;6.4w=n/6.11.2u;6.4B=E.3Z[6.11.3Z||(E.3Z.5j?"5j":"70")](6.4w,n,0,1,6.11.2u);6.2J=6.1Y+((6.3h-6.1Y)*6.4B);6.4y()}K P}};E.2t.30={2v:J(a){a.Y.2v=a.2J},2x:J(a){a.Y.2x=a.2J},1w:J(a){E.1J(a.Y.W,"1w",a.2J)},4G:J(a){a.Y.W[a.1l]=a.2J+a.2A}};E.1n.5L=J(){L b=0,3b=0,Y=6[0],5l;7(Y)8M(E.14){L d=Y.1a,41=Y,1K=Y.1K,1L=Y.2i,5D=2d&&4s(5K)<8J&&!/a1/i.17(v),2T=E.1j(Y,"43")=="2T";7(Y.6G){L c=Y.6G();1b(c.26+24.2f(1L.1F.2v,1L.1h.2v),c.3b+24.2f(1L.1F.2x,1L.1h.2x));1b(-1L.1F.62,-1L.1F.60)}N{1b(Y.5G,Y.5F);2b(1K){1b(1K.5G,1K.5F);7(48&&!/^t(8H|d|h)$/i.17(1K.28)||2d&&!5D)2N(1K);7(!2T&&E.1j(1K,"43")=="2T")2T=P;41=/^1h$/i.17(1K.28)?41:1K;1K=1K.1K}2b(d&&d.28&&!/^1h|3q$/i.17(d.28)){7(!/^8G|1O.*$/i.17(E.1j(d,"19")))1b(-d.2v,-d.2x);7(48&&E.1j(d,"32")!="4d")2N(d);d=d.1a}7((5D&&(2T||E.1j(41,"43")=="4W"))||(48&&E.1j(41,"43")!="4W"))1b(-1L.1h.5G,-1L.1h.5F);7(2T)1b(24.2f(1L.1F.2v,1L.1h.2v),24.2f(1L.1F.2x,1L.1h.2x))}5l={3b:3b,26:b}}J 2N(a){1b(E.2o(a,"a8",P),E.2o(a,"a9",P))}J 1b(l,t){b+=4s(l)||0;3b+=4s(t)||0}K 5l}})();',62,631,'||||||this|if||||||||||||||||||||||||||||||||||||||function|return|var|length|else|data|true|for|each|false|document|type|null|style||elem||undefined|options|nodeName||browser|nodeType|event|test|arguments|display|parentNode|add|url|msie|window|indexOf|push|body|apply|css|constructor|prop|script|fn|typeof|in|isFunction|replace|extend|className|text|handle|opacity|div|complete|status|value|new|firstChild|match|filter|documentElement|show|dataType|hide|attr|offsetParent|doc|Array|trigger|table|call|break|height|try|cache|tbody|remove|success|catch|start|hidden||ready|get|split|Math|string|left|width|tagName|ret|global|while|map|safari|animate|max|toggle|toLowerCase|ownerDocument|bind|select|prototype|cur||curCSS|selected|handler|done|find|fx|duration|scrollLeft|id|scrollTop|special|opera|unit|nextSibling|stack|guid|toUpperCase|pushStack|button|none|makeArray|now|slice|target|parseFloat|border|exec|queue|isReady|events|px|fixed|timeout|delete|jsre|one|disabled|nth|step|name|overflow|inArray|removeChild|removeData|preventDefault|merge|appendChild|readyState|error|top|which|innerHTML|multiFilter|rl|trim|end|json|first|checked|async|param|elems|insertBefore|childNodes|html|encodeURIComponent|createElement|append|form|Date|unbind|color|grep|setTimeout|readyList|mouseleave|mouseenter|block|isXMLDoc|addEventListener|timers|is|password|last|runtimeStyle|getTime|xml|jQuery|domManip|ajax|src|callee|getElementsByTagName|selectedIndex|load|object|timerId|toString|has|easing|curAnim|offsetChild|args|position|stopPropagation|custom|props|orig|mozilla|accepts|clean|responseText|defaultView|visible|String|charCode|float|teardown|on|setup|nodeIndex|shift|javascript|currentStyle|application|child|RegExp|_|parseInt|previousSibling|dir|tr|state|empty|update|getAttribute|self|pos|setRequestHeader|input|jsonp|lastModified|_default|unload|ajaxSettings|unshift|getComputedStyle|styleSheets|getPropertyValue|lastToggle|mouseout|mouseover|GET|andSelf|relatedTarget|init|visibility|click|absolute|index|container|fix|outline|Number|removeAttribute|setInterval|prevObject|classFilter|not|unique|submit|file|after|windowData|deep|scroll|client|triggered|globalEval|jquery|sibling|swing|clone|results|wrapAll|triggerHandler|lastChild|dequeue|getResponseHeader|createTextNode|oldblock|checkbox|radio|handleError|fromElement|parsererror|old|00|Modified|startTime|ifModified|safari2|getWH|offsetTop|offsetLeft|active|values|getElementById|version|offset|bindReady|processData|val|contentType|ajaxSuccess|ajaxComplete|ajaxStart|serializeArray|notmodified|loaded|DOMContentLoaded|Width|ctrlKey|keyCode|clientTop|POST|clientLeft|clientX|pageX|exclusive|detachEvent|removeEventListener|swap|cloneNode|join|attachEvent|eval|ajaxStop|substr|head|parse|textarea|reset|image|zoom|odd|ajaxSend|even|before|username|prepend|expr|quickClass|uuid|quickID|quickChild|continue|textContent|appendTo|contents|evalScript|parent|defaultValue|ajaxError|setArray|compatMode|getBoundingClientRect|styleFloat|clearInterval|httpNotModified|nodeValue|100|alpha|_toggle|href|speed|throw|304|replaceWith|200|Last|colgroup|httpData|httpSuccess|beforeSend|eq|linear|concat|splice|fieldset|multiple|cssFloat|XMLHttpRequest|webkit|ActiveXObject|CSS1Compat|link|metaKey|scriptCharset|callback|col|pixelLeft|urlencoded|www|post|hasClass|getJSON|getScript|elements|serialize|black|keyup|keypress|solid|change|mousemove|mouseup|dblclick|resize|focus|blur|stylesheet|rel|doScroll|round|hover|padding|offsetHeight|mousedown|offsetWidth|Bottom|Top|keydown|clientY|Right|pageY|Left|toElement|srcElement|cancelBubble|returnValue|charAt|0n|substring|animated|header|noConflict|line|enabled|innerText|contains|only|weight|ajaxSetup|font|size|gt|lt|uFFFF|u0128|417|Boolean|inner|Height|toggleClass|removeClass|addClass|removeAttr|replaceAll|insertAfter|prependTo|contentWindow|contentDocument|wrap|iframe|children|siblings|prevAll|nextAll|prev|wrapInner|next|parents|maxLength|maxlength|readOnly|readonly|reverse|class|htmlFor|inline|able|boxModel|522|setData|compatible|with|1px|ie|getData|10000|ra|it|rv|PI|cos|userAgent|400|navigator|600|slow|Function|Object|array|stop|ig|NaN|fadeTo|option|fadeOut|fadeIn|setAttribute|slideToggle|slideUp|changed|slideDown|be|can|property|responseXML|content|1223|getAttributeNode|300|method|protocol|location|action|send|abort|cssText|th|td|cap|specified|Accept|With|colg|Requested|fast|tfoot|GMT|thead|1970|Jan|attributes|01|Thu|leg|Since|If|opt|Type|Content|embed|open|area|XMLHTTP|hr|Microsoft|onreadystatechange|onload|meta|adobeair|charset|http|1_|img|br|plain|borderLeftWidth|borderTopWidth|abbr'.split('|'),0,{}))
0 body {
1 font-family: 'Arial', sans-serif;
2 font-size: 15px;
3 margin: 0;
4 padding: 1.3em;
5 background-color: #07131d;
6 }
0 body, input { font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
1 'Verdana', sans-serif; background-color: #AFC1C4; color: #000;
2 text-align: center; margin: 1em; padding: 0; font-size: 15px; }
3 input { background-color: #fff; margin: 0; text-align: left; }
4 a { color: #11557C; }
5 a:hover { color: #177199; }
6 pre, code, table.source,
7 textarea { font-family: 'Consolas', 'Deja Vu Sans Mono',
8 'Bitstream Vera Sans Mono', monospace; font-size: 13px; }
79
8 * {
9 margin: 0;
10 padding: 0;
11 }
10 div.debugger { text-align: left; padding: 12px; margin: auto;
11 border: 1px solid #aaa; background-color: white; }
12 h1 { color: #11557C; font-size: 30px; margin: 0 0 0.3em 0; }
13 div.detail p { margin: 0 0 8px 13px; font-size: 14px; }
14 div.explanation { margin: 13px; font-size: 11px; }
15 div.footer { background-color: #E3EFF1; font-size: 0.8em; text-align: right;
16 padding: 6px 8px 6px 0; margin: 30px -12px -12px -12px;
17 color: #86989B; }
1218
13 #footer {
14 margin: 1em;
15 font-size: 13px;
16 letter-spacing: 0.1em;
17 color: #eee;
18 text-align: right;
19 }
19 h2 { font-size: 16px; margin: 1.3em 0 0.0 0; padding: 5px;
20 background-color: #11557C; color: white; }
21 h2 em { font-style: normal; color: #A5D6D9; font-weight: normal; }
2022
21 #footer .arthur {
22 font-weight: bold;
23 }
23 div.traceback, div.plain { background-color: #eee!important; border: 1px solid #ccc;
24 margin: 0 0 1em 0; padding: 10px; }
25 div.plain p { margin: 0; }
26 div.plain textarea,
27 div.plain pre { margin: 10px 0 0 0; padding: 4px; background-color: #333;
28 border: 1px solid #111; color: white; }
29 div.plain textarea { width: 99%; height: 300px; }
30 div.traceback h3 { font-size: 1em; margin: 0 0 0.8em 0; }
31 div.traceback ul { list-style: none; margin: 0; padding: 0 0 0 1em; }
32 div.traceback h4 { font-size: 13px; font-weight: normal; margin: 0.7em 0 0.1em 0; }
33 div.traceback pre { margin: 0; padding: 5px 0 3px 15px;
34 background-color: #ccc; border-top: 1px solid #aaa;
35 border-left: 1px solid #aaa; border-right: 1px solid #fafafa;
36 border-bottom: 1px solid #fafafa; }
37 div.traceback pre,
38 div.box table.source { white-space: pre-wrap; /* css-3 should we be so lucky... */
39 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
40 white-space: -pre-wrap; /* Opera 4-6 ?? */
41 white-space: -o-pre-wrap; /* Opera 7 ?? */
42 word-wrap: break-word; /* Internet Explorer 5.5+ */
43 _white-space: pre; /* IE only hack to re-specify in
44 addition to word-wrap */ }
45 div.traceback pre:hover { background-color: #fafafa; color: black; cursor: pointer; }
46 div.traceback blockquote { margin: 1em 0 0 0; padding: 0; }
47 div.traceback img { float: right; padding: 2px; margin: -3px 2px 0 0; display: none; }
48 div.traceback img:hover { background-color: #ddd; cursor: pointer; }
49 div.traceback pre:hover img { display: block; }
2450
25 .traceback_wrapper {
26 margin: 1em;
27 border: 1px solid #1b3042;
28 background-color: #F6F6F6;
29 }
51 pre.console { background-color: #fafafa!important; color: black; padding: 5px!important;
52 margin: 3px 0 0 0!important; cursor: default!important;
53 max-height: 400px; overflow: auto; }
54 pre.console form { color: #555; }
55 pre.console input { background-color: #fafafa; color: #555;
56 width: 90%; font-family: 'Consolas', 'Deja Vu Sans Mono',
57 'Bitstream Vera Sans Mono', monospace; font-size: 13px;
58 border: none!important; }
3059
31 h1 {
32 background-color: #1b3042;
33 font-size: 24px;
34 color: #FFFFFF;
35 padding: 0.3em;
36 margin: 0 0 0.2em 0;
37 }
60 span.string { color: #30799B; }
61 span.number { color: #9C1A1C; }
62 span.object { color: #485F6E; }
63 span.extended { opacity: 0.5; }
64 span.extended:hover { opacity: 1; }
65 a.toggle { text-decoration: none; background-repeat: no-repeat;
66 background-position: center center;
67 background-image: url(./__debugger__?cmd=resource&f=more.png); }
68 a.toggle:hover { background-color: #444; }
69 a.open { background-image: url(./__debugger__?cmd=resource&f=less.png); }
3870
39 h2 {
40 background-color: #3F7CA4;
41 font-size: 16px;
42 color: #FFFFFF;
43 padding: 0.3em;
44 margin: 1em 0 0.2em 0;
45 clear: both;
46 }
71 pre.console div.traceback { margin: 5px 0 5px 25px; white-space: normal; }
72 pre.console div.traceback h3 { background-color: #555; color: white;
73 margin: -10px -10px 5px -10px; padding: 5px; }
74 pre.console div.traceback pre:hover { background-color: #ccc; cursor: default; }
4775
48 h2.tb {
49 cursor: pointer;
50 }
51
52 h2.tb span {
53 display: none;
54 color: #d2f2f3;
55 font-weight: normal;
56 }
57
58 h2.tb:hover span {
59 display: inline;
60 }
61
62 h3 {
63 font-size: 14px;
64 padding: 0;
65 }
66
67 h3.fn {
68 margin-top: 0.8em;
69 clear: both;
70 font-weight: normal;
71 }
72
73 h3.fn em {
74 font-style: normal;
75 font-weight: bold;
76 }
77
78 h3.fn tt {
79 color: #444;
80 font-size: 0.9em;
81 font-family: 'Arial', sans-serif;
82 }
83
84 a {
85 color: #333;
86 }
87
88 a.eval, a.locals {
89 display: block;
90 float: right;
91 margin: -12px 0 0 8px;
92 font-size: 12px;
93 color: #333;
94 text-decoration: none;
95 }
96
97 a.eval:hover, a.locals:hover {
98 color: black;
99 background-color: #ccc;
100 }
101
102 p.text {
103 padding: 0.4em 1em 0.4em 1em;
104 }
105
106 p.errormsg {
107 padding: 0.1em 0.5em 0.1em 0.5em;
108 font-size: 16px;
109 }
110
111 p.errorline {
112 padding: 0.1em 0.5em 0.1em 2em;
113 font-size: 15px;
114 }
115
116 div.frame {
117 margin: 0 2em 0 1em;
118 }
119
120 table.code {
121 margin: 0.4em 0 0 0.5em;
122 background-color: #eee;
123 width: 100%;
124 font-family: monospace;
125 font-size: 13px;
126 border: 1px solid #ccc;
127 border-collapse: collapse;
128 cursor: pointer;
129 }
130
131 table.code td.lineno {
132 width: 42px;
133 text-align: right;
134 padding: 0 5px 0 0;
135 color: #444;
136 background-color: #ddd;
137 font-weight: bold;
138 border-right: 1px solid #888;
139 }
140
141 table.code td.code {
142 background-color: #f6f6f6;
143 padding: 1px 0 1px 5px;
144 white-space: pre;
145 }
146
147 table.code tr.cur td.code {
148 background-color: #fff;
149 border-top: 1px solid #ccc;
150 border-bottom: 1px solid #ccc;
151 white-space: pre;
152 }
153
154 table.code tr.cur td.lineno {
155 background-color: #bbb;
156 border: 1px solid #999;
157 }
158
159 pre.plain {
160 margin: 0.5em 1em 1em 1em;
161 padding: 0.5em;
162 border:1px solid #999999;
163 background-color: #FFFFFF;
164 font-family: monospace;
165 font-size: 13px;
166 }
167
168 form.exec_code {
169 display: block;
170 width: 100%;
171 margin: 0.3em 1em 0.3em 1em;
172 }
173
174 form.exec_code input.input {
175 width: 100%;
176 height: 1.5em;
177 border: 1px solid #999999;
178 font-family: monospace;
179 font-size: 13px;
180 }
181
182 form.exec_code pre.output,
183 table.vars td.value {
184 font-family: monospace;
185 font-size: 13px;
186 white-space: pre-wrap; /* css-3 should we be so lucky... */
187 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
188 white-space: -pre-wrap; /* Opera 4-6 ?? */
189 white-space: -o-pre-wrap; /* Opera 7 ?? */
190 word-wrap: break-word; /* Internet Explorer 5.5+ */
191 _white-space: pre; /* IE only hack to re-specify in addition to word-wrap */
192 }
193
194 pre.output {
195 background-color: #222;
196 color: #fff;
197 padding: 4px;
198 border: 1px solid #666;
199 max-height: 350px;
200 overflow: auto;
201 }
202
203 table.vars {
204 margin: 0.3em 1.5em 0.3em 1.5em;
205 border-collapse: collapse;
206 font-size: 13px;
207 width: 99%;
208 }
209
210 table.vars td {
211 font-family: 'Bitstream Vera Sans Mono', 'Courier New', monospace;
212 padding: 0.3em;
213 border: 1px solid #ddd;
214 vertical-align: top;
215 background-color: white;
216 }
217
218 table.vars .name {
219 font-style: italic;
220 }
221
222 table.vars .value {
223 color: #555;
224 }
225
226 table.vars th {
227 padding: 0.2em;
228 border: 1px solid #ddd;
229 background-color: #f2f2f2;
230 text-align: left;
231 }
232
233 #plain {
234 display: none;
235 }
236
237 dl dt {
238 padding: 0.2em 0 0.2em 1em;
239 font-weight: bold;
240 cursor: pointer;
241 background-color: #ddd;
242 border-top: 1px solid #ccc;
243 }
244
245 dl dt:hover {
246 background-color: #bbb;
247 color: white;
248 }
249
250 dl dd {
251 padding: 0 0 0 2em;
252 background-color: #eee;
253 }
254
255 span.p-kw {
256 font-weight: bold;
257 color: #1d6170;
258 }
259
260 span.p-cmt {
261 color: #7bbc2e;
262 }
263
264 span.p-str {
265 color: #ab7219;
266 }
267
268 span.p-num {
269 color: #3f7719;
270 font-weight: bold;
271 }
272
273 span.p-op {
274 color: black;
275 }
76 pre.console div.box { margin: 5px 0 5px 25px; white-space: normal;
77 border: 1px solid #ddd; }
78 pre.console div.box h3 { background-color: #555; color: white;
79 margin: 0; padding: 5px; }
80 pre.console div.box div.repr { padding: 8px; background-color: white; }
81 pre.console div.box table { margin-top: 6px; }
82 pre.console div.box pre.help { background-color: white; font-size: 12px; }
83 pre.console div.box pre.help:hover { cursor: default; }
84 pre.console table tr { vertical-align: top; }
85 div.box table.source { background-color: #fafafa; font-size: 12px;
86 border-collapse: collapse; width: 100%; }
87 div.box table.source td { border-top: 1px solid #eee; padding: 4px 0 4px 10px; }
88 div.box table.source td.lineno { color: #999; padding-right: 10px; width: 1px; }
89 div.box table.source tr.in-frame { background-color: #D6EEFF; }
90 div.box table.source tr.current { background-color: white; }
91 div.sourceview { max-height: 400px; overflow: auto; border: 1px solid #ccc; }
92 div.console { border: 1px solid #ccc; padding: 4px; background-color: #fafafa; }
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.debug.tbtools
3 ~~~~~~~~~~~~~~~~~~~~~~
4
5 This module provides various traceback related utility functions.
6
7 :copyright: Copyright 2008 by Armin Ronacher.
8 :license: BSD.
9 """
10 import re
11 import os
12 import sys
13 import inspect
14 import traceback
15 import codecs
16 from tokenize import TokenError
17 from werkzeug.utils import cached_property
18 from werkzeug.debug.console import Console
19 from werkzeug.debug.utils import render_template
20
21 _coding_re = re.compile(r'coding[:=]\s*([-\w.]+)')
22 _line_re = re.compile(r'^(.*?)$(?m)')
23 _funcdef_re = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)')
24 UTF8_COOKIE = '\xef\xbb\xbf'
25
26 system_exceptions = (SystemExit, KeyboardInterrupt)
27 try:
28 system_exceptions += (GeneratorExit,)
29 except NameError:
30 pass
31
32
33 def get_current_traceback(ignore_system_exceptions=False,
34 show_hidden_frames=False, skip=0):
35 """Get the current exception info as `Traceback` object. Per default
36 calling this method will reraise system exceptions such as generator exit,
37 system exit or others. This behavior can be disabled by passing `False`
38 to the function as first parameter.
39 """
40 exc_type, exc_value, tb = sys.exc_info()
41 if ignore_system_exceptions and exc_type in system_exceptions:
42 raise
43 for x in xrange(skip):
44 if tb.tb_next is None:
45 break
46 tb = tb.tb_next
47 tb = Traceback(exc_type, exc_value, tb)
48 if not show_hidden_frames:
49 tb.filter_hidden_frames()
50 return tb
51
52
53 class Line(object):
54 """Helper for the source renderer."""
55 __slots__ = ('lineno', 'code', 'in_frame', 'current')
56
57 def __init__(self, lineno, code):
58 self.lineno = lineno
59 self.code = code
60 self.in_frame = False
61 self.current = False
62
63 def classes(self):
64 rv = ['line']
65 if self.in_frame:
66 rv.append('in-frame')
67 if self.current:
68 rv.append('current')
69 return rv
70 classes = property(classes)
71
72
73 class Traceback(object):
74 """Wraps a traceback."""
75
76 def __init__(self, exc_type, exc_value, tb):
77 self.exc_type = exc_type
78 self.exc_value = exc_value
79 if not isinstance(exc_type, str):
80 exception_type = exc_type.__name__
81 if exc_type.__module__ not in ('__builtin__', 'exceptions'):
82 exception_type = exc_type.__module__ + '.' + exception_type
83 else:
84 exception_type = exc_type
85 self.exception_type = exception_type
86
87 # we only add frames to the list that are not hidden. This follows
88 # the the magic variables as defined by paste.exceptions.collector
89 self.frames = []
90 while tb:
91 self.frames.append(Frame(exc_type, exc_value, tb))
92 tb = tb.tb_next
93
94 def filter_hidden_frames(self):
95 """Remove the frames according to the paste spec."""
96 new_frames = []
97 hidden = False
98 for frame in self.frames:
99 hide = frame.hide
100 if hide in ('before', 'before_and_this'):
101 new_frames = []
102 hidden = False
103 if hide == 'before_and_this':
104 continue
105 elif hide in ('reset', 'reset_and_this'):
106 hidden = False
107 if hide == 'reset_and_this':
108 continue
109 elif hide in ('after', 'after_and_this'):
110 hidden = True
111 if hide == 'after_and_this':
112 continue
113 elif hide or hidden:
114 continue
115 new_frames.append(frame)
116
117 # if the last frame is missing something went terrible wrong :(
118 if self.frames[-1] in new_frames:
119 self.frames[:] = new_frames
120
121 def is_syntax_error(self):
122 """Is it a syntax error?"""
123 return isinstance(self.exc_value, SyntaxError)
124 is_syntax_error = property(is_syntax_error)
125
126 def exception(self):
127 """String representation of the exception."""
128 buf = traceback.format_exception_only(self.exc_type, self.exc_value)
129 return ''.join(buf).strip().decode('utf-8', 'replace')
130 exception = property(exception)
131
132 def log(self, logfile=None):
133 """Log the ASCII traceback into a file object."""
134 if logfile is None:
135 logfile = sys.stderr
136 tb = self.plaintext.encode('utf-8', 'replace').rstrip() + '\n'
137 logfile.write(tb)
138
139 def paste(self):
140 """Create a paste and return the paste id."""
141 from xmlrpclib import ServerProxy
142 srv = ServerProxy('http://paste.pocoo.org/xmlrpc/')
143 return srv.pastes.newPaste('pytb', self.plaintext)
144 return '{"url": "http://paste.pocoo.org/show/%d/", "id": %d}' % \
145 (paste_id, paste_id)
146
147 def render_summary(self, include_title=True):
148 """Render the traceback for the interactive console."""
149 return render_template('traceback_summary.html', traceback=self,
150 include_title=include_title)
151
152 def render_full(self, evalex=False):
153 """Render the Full HTML page with the traceback info."""
154 return render_template('traceback_full.html', traceback=self,
155 evalex=evalex)
156
157 def plaintext(self):
158 return render_template('traceback_plaintext.html', traceback=self)
159 plaintext = cached_property(plaintext)
160
161 id = property(lambda x: id(x))
162
163
164 class Frame(object):
165 """A single frame in a traceback."""
166
167 def __init__(self, exc_type, exc_value, tb):
168 self.lineno = tb.tb_lineno
169 self.function_name = tb.tb_frame.f_code.co_name
170 self.locals = tb.tb_frame.f_locals
171 self.globals = tb.tb_frame.f_globals
172
173 fn = inspect.getsourcefile(tb) or inspect.getfile(tb)
174 if fn[-4:] in ('.pyo', '.pyc'):
175 fn = fn[:-1]
176 # if it's a file on the file system resolve the real filename.
177 if os.path.isfile(fn):
178 fn = os.path.realpath(fn)
179 self.filename = fn
180 self.module = self.globals.get('__name__')
181 self.loader = self.globals.get('__loader__')
182 self.code = tb.tb_frame.f_code
183
184 # support for paste's traceback extensions
185 self.hide = self.locals.get('__traceback_hide__', False)
186 info = self.locals.get('__traceback_info__')
187 if info is not None:
188 try:
189 info = unicode(info)
190 except UnicodeError:
191 info = str(info).decode('utf-8', 'replace')
192 self.info = info
193
194 def render(self):
195 """Render a single frame in a traceback."""
196 return render_template('frame.html', frame=self)
197
198 def render_source(self):
199 """Render the sourcecode."""
200 lines = [Line(idx + 1, x) for idx, x in enumerate(self.sourcelines)]
201
202 # find function definition and mark lines
203 if hasattr(self.code, 'co_firstlineno'):
204 lineno = self.code.co_firstlineno - 1
205 while lineno > 0:
206 if _funcdef_re.match(lines[lineno].code):
207 break
208 lineno -= 1
209 try:
210 offset = len(inspect.getblock([x.code + '\n' for x
211 in lines[lineno:]]))
212 except TokenError:
213 offset = 0
214 for line in lines[lineno:lineno + offset]:
215 line.in_frame = True
216
217 # mark current line
218 try:
219 lines[self.lineno - 1].current = True
220 except IndexError:
221 pass
222
223 return render_template('source.html', frame=self, lines=lines)
224
225 def eval(self, code, mode='single'):
226 """Evaluate code in the context of the frame."""
227 if isinstance(code, basestring):
228 if isinstance(code, unicode):
229 code = UTF8_COOKIE + code.encode('utf-8')
230 code = compile(code, '<interactive>', mode)
231 if mode != 'exec':
232 return eval(code, self.globals, self.locals)
233 exec code in self.globals, self.locals
234
235 def sourcelines(self):
236 """The sourcecode of the file as list of unicode strings."""
237 # get sourcecode from loader or file
238 source = None
239 if self.loader is not None:
240 if hasattr(self.loader, 'get_source'):
241 source = self.loader.get_source(self.module)
242 elif hasattr(self.loader, 'get_source_by_code'):
243 source = self.loader.get_source_by_code(self.code)
244 if source is None:
245 try:
246 f = file(self.filename)
247 except IOError:
248 return []
249 try:
250 source = f.read()
251 finally:
252 f.close()
253
254 # already unicode? return right away
255 if isinstance(source, unicode):
256 return source.splitlines()
257
258 # yes. it should be ascii, but we don't want to reject too many
259 # characters in the debugger if something breaks
260 charset = 'utf-8'
261 if source.startswith(UTF8_COOKIE):
262 source = source[3:]
263 else:
264 for idx, match in enumerate(_line_re.finditer(source)):
265 match = _line_re.search(match.group())
266 if match is not None:
267 charset = match.group(1)
268 break
269 if idx > 1:
270 break
271
272 # on broken cookies we fall back to utf-8 too
273 try:
274 codecs.lookup(charset)
275 except LookupError:
276 charset = 'utf-8'
277
278 return source.decode(charset, 'replace').splitlines()
279 sourcelines = cached_property(sourcelines)
280
281 def current_line(self):
282 try:
283 return self.sourcelines[self.lineno - 1]
284 except IndexError:
285 return u''
286 current_line = property(current_line)
287
288 def console(self):
289 return Console(self.globals, self.locals)
290 console = cached_property(console)
291
292 id = property(lambda x: id(x))
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
1 "http://www.w3.org/TR/html4/loose.dtd">
2 <html>
3 <head>
4 <title>Console // Werkzeug Debugger</title>
5 <link rel="stylesheet" href="./__debugger__?cmd=resource&amp;f=style.css" type="text/css">
6 <script type="text/javascript" src="./__debugger__?cmd=resource&amp;f=jquery.js"></script>
7 <script type="text/javascript" src="./__debugger__?cmd=resource&amp;f=debugger.js"></script>
8 <script type="text/javascript">
9 var EVALEX = true,
10 CONSOLE_MODE = true;
11 </script>
12 </head>
13 <body>
14 <div class="debugger">
15 <h1>Interactive Console</h1>
16 <div class="explanation">
17 In this console you can execute Python expressions in the context of the
18 application. The initial namespace was created by the debugger automatically.
19 </div>
20 <div class="console"><div class="inner">The Console requires JavaScript.</div></div>
21 <div class="footer">
22 Brought to you by <strong class="arthur">DON'T PANIC</strong>, your
23 friendly Werkzeug powered traceback interpreter.
24 </div>
25 </div>
26 </body>
27 </html>
0 <div class="box">
1 <h3>$escape(title)</h3>
2 <% if repr %>
3 <div class="repr">$repr</div>
4 <% endif %>
5 <table>
6 <% for key, value in items %>
7 <tr>
8 <th>$escape(key)</th>
9 <td>$value</td>
10 </tr>
11 <% endfor %>
12 </table>
13 </div>
0 <div class="frame" id="frame-$frame.id">
1 <h4>File <cite>"$escape(frame.filename)"</cite>, line <em>$frame.lineno</em>,
2 in <code>$escape(frame.function_name)</code></h4>
3 <pre>${escape(frame.current_line.strip())}</pre>
4 </div>
0 <%py missing = object() %>
1 <div class="box">
2 <% if title and text %>
3 <h3>$title</h3>
4 <pre class="help">$text</pre>
5 <% else %>
6 <h3>Help</h3>
7 <p>Type help(object) for help abuot object.</p>
8 <% endif %>
9 </div>
0 <table class="source">
1 <% for line in lines %>
2 <tr class="${' '.join(line.classes)}">
3 <td class="lineno">${line.lineno}</td>
4 <td>$escape(line.code)</td>
5 </tr>
6 <% endfor %>
7 </table>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
1 "http://www.w3.org/TR/html4/loose.dtd">
2 <html>
3 <head>
4 <title>$escape(traceback.exception) // Werkzeug Debugger</title>
5 <link rel="stylesheet" href="./__debugger__?cmd=resource&amp;f=style.css" type="text/css">
6 <script type="text/javascript" src="./__debugger__?cmd=resource&amp;f=jquery.js"></script>
7 <script type="text/javascript" src="./__debugger__?cmd=resource&amp;f=debugger.js"></script>
8 <script type="text/javascript">
9 var TRACEBACK = $traceback.id,
10 CONSOLE_MODE = false,
11 EVALEX = ${evalex and 'true' or 'false'};
12 </script>
13 </head>
14 <body>
15 <div class="debugger">
16 <h1>$escape(traceback.exception_type)</h1>
17 <div class="detail">
18 <p class="errormsg">$escape(traceback.exception)</p>
19 </div>
20 <h2 class="traceback">Traceback <em>(most recent call last)</em></h2>
21 $traceback.render_summary(include_title=False)
22 <div class="plain">
23 <form action="http://paste.pocoo.org/" method="post">
24 <p>
25 <input type="hidden" name="language" value="pytb">
26 This is the Copy/Paste friendly version of the traceback. <span
27 class="pastemessage">You can also paste this traceback into the public
28 lodgeit pastebin: <input type="submit" value="create paste"></span>
29 </p>
30 <textarea cols="50" rows="10" name="code" readonly>$escape(traceback.plaintext)</textarea>
31 </form>
32 </div>
33 <div class="explanation">
34 The debugger cought an exception in your WSGI application. You can now
35 look at the traceback which lead to the error. <span class="nojavascript">
36 If you enable JavaScript you can also use additional features such as code
37 execution (if the evalex feature is enabled), automatic pasting of the
38 exceptions and much more.</span>
39 </div>
40 <div class="footer">
41 Brought to you by <strong class="arthur">DON'T PANIC</strong>, your
42 friendly Werkzeug powered traceback interpreter.
43 </div>
44 </div>
45 </body>
46 </html>
47 <!--
48
49 <%py
50 import re
51 print re.sub('-{2,}', '-', traceback.plaintext)
52 %>
53
54 -->
0 Traceback (most recent call last):
1 <% for frame in traceback.frames %>
2 File "$frame.filename", line $frame.lineno, in $frame.function_name
3 $frame.current_line.strip()
4 <% endfor %>
5 $traceback.exception
0 <div class="traceback">
1 <% if traceback.is_syntax_error %>
2 <% if include_title %>
3 <h3>Syntax Error</h3>
4 <% endif %>
5 <ul>
6 <% for frame in traceback.frames %>
7 <li>$frame.render()</li>
8 <% endfor %>
9 </ul>
10 <pre>$escape(traceback.exception)</pre>
11 <% else %>
12 <% if include_title %>
13 <h3>Traceback <em>(most recent call last)</em>:</h3>
14 <% endif %>
15 <ul>
16 <% for frame in traceback.frames %>
17 <li<% if frame.info %> title="$escape(frame.info, True)"<% endif %>>$frame.render()</li>
18 <% endfor %>
19 </ul>
20 <blockquote>$escape(traceback.exception)</blockquote>
21 <% endif %>
22 </div>
+0
-274
werkzeug/debug/util.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.debug.util
3 ~~~~~~~~~~~~~~~~~~~
4
5 Utilities for the debugger.
6
7 :copyright: 2007 by Georg Brandl, Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import os
11 import re
12 import sys
13 import token
14 import inspect
15 import keyword
16 import tokenize
17 import traceback
18 from cgi import escape
19 from random import random
20 from cStringIO import StringIO
21 from werkzeug.local import Local
22
23
24 local = Local()
25
26
27 class ExceptionRepr(object):
28
29 def __init__(self, exc):
30 exclines = traceback.format_exception_only(type(exc), exc)
31 self.repr = 'got %s' % exclines[-1].strip()
32
33 def __repr__(self):
34 return self.repr
35
36
37 class Namespace(object):
38
39 def __init__(self, **kwds):
40 self.__dict__.update(kwds)
41
42 def to_dict(self):
43 return self.__dict__.copy()
44
45
46 class ThreadedStream(object):
47 """
48 Thin wrapper around sys.stdout so that we can dispatch access
49 to it for different threads.
50 """
51
52 def push():
53 local.stream = StringIO()
54 push = staticmethod(push)
55
56 def fetch():
57 try:
58 stream = local.stream
59 except AttributeError:
60 return ''
61 stream.reset()
62 return stream.read()
63 fetch = staticmethod(fetch)
64
65 def install(cls):
66 sys.stdout = cls()
67 install = classmethod(install)
68
69 def __setattr__(self, name, value):
70 raise AttributeError('read only attribute %s' % name)
71
72 def __getattribute__(self, name):
73 if name == '__members__':
74 return dir(sys.__stdout__)
75 try:
76 stream = local.stream
77 except AttributeError:
78 stream = sys.__stdout__
79 return getattr(stream, name)
80
81 def __repr__(self):
82 return repr(sys.__stdout__)
83
84
85 def get_uid():
86 """Return a random unique ID."""
87 return str(random()).encode('base64')[3:11]
88
89
90 def highlight_python(source):
91 """Highlight some python code. Return a list of lines"""
92 parser = PythonParser(source)
93 parser.parse()
94 return parser.get_html_output()
95
96
97 class PythonParser(object):
98 """
99 Simple python sourcecode highlighter.
100
101 Usage::
102
103 p = PythonParser(source)
104 p.parse()
105 for line in p.get_html_output():
106 print line
107 """
108
109 _KEYWORD = token.NT_OFFSET + 1
110 _TEXT = token.NT_OFFSET + 2
111 _classes = {
112 token.NUMBER: 'num',
113 token.OP: 'op',
114 token.STRING: 'str',
115 tokenize.COMMENT: 'cmt',
116 token.NAME: 'id',
117 token.ERRORTOKEN: 'error',
118 _KEYWORD: 'kw',
119 _TEXT: 'txt',
120 }
121
122 def __init__(self, raw):
123 self.raw = raw.expandtabs(8)
124 if isinstance(self.raw, unicode):
125 self.raw = self.raw.encode('utf-8', 'ignore')
126 self.out = StringIO()
127
128 def parse(self):
129 self.lines = [0, 0]
130 pos = 0
131 while 1:
132 pos = self.raw.find('\n', pos) + 1
133 if not pos:
134 break
135 self.lines.append(pos)
136 self.lines.append(len(self.raw))
137
138 self.pos = 0
139 text = StringIO(self.raw)
140 try:
141 tokenize.tokenize(text.readline, self)
142 except (SyntaxError, tokenize.TokenError):
143 self.error = True
144 else:
145 self.error = False
146
147 def get_html_output(self):
148 """ Return line generator. """
149 def html_splitlines(lines):
150 # this cool function was taken from trac.
151 # http://projects.edgewall.com/trac/
152 open_tag_re = re.compile(r'<(\w+)(\s.*)?[^/]?>')
153 close_tag_re = re.compile(r'</(\w+)>')
154 open_tags = []
155 for line in lines:
156 for tag in open_tags:
157 line = tag.group(0) + line
158 open_tags = []
159 for tag in open_tag_re.finditer(line):
160 open_tags.append(tag)
161 open_tags.reverse()
162 for ctag in close_tag_re.finditer(line):
163 for otag in open_tags:
164 if otag.group(1) == ctag.group(1):
165 open_tags.remove(otag)
166 break
167 for tag in open_tags:
168 line += '</%s>' % tag.group(1)
169 yield line
170
171 if self.error:
172 return escape(self.raw).splitlines()
173 return list(html_splitlines(self.out.getvalue().splitlines()))
174
175 def __call__(self, toktype, toktext, (srow,scol), (erow,ecol), line):
176 oldpos = self.pos
177 newpos = self.lines[srow] + scol
178 self.pos = newpos + len(toktext)
179
180 if toktype in [token.NEWLINE, tokenize.NL]:
181 self.out.write('\n')
182 return
183
184 if newpos > oldpos:
185 self.out.write(self.raw[oldpos:newpos])
186
187 if toktype in [token.INDENT, token.DEDENT]:
188 self.pos = newpos
189 return
190
191 if token.LPAR <= toktype and toktype <= token.OP:
192 toktype = token.OP
193 elif toktype == token.NAME and keyword.iskeyword(toktext):
194 toktype = self._KEYWORD
195 clsname = self._classes.get(toktype, 'txt')
196
197 self.out.write('<span class="code-item p-%s">%s</span>' % (
198 clsname,
199 escape(toktext)
200 ))
201
202
203 def get_frame_info(tb, context_lines=7, simple=False):
204 """
205 Return a dict of information about a given traceback.
206 """
207 # line numbers / function / variables
208 lineno = tb.tb_lineno
209 function = tb.tb_frame.f_code.co_name
210 variables = tb.tb_frame.f_locals
211
212 # get filename
213 if simple:
214 fn = tb.tb_frame.f_code.co_filename
215 else:
216 fn = tb.tb_frame.f_globals.get('__file__')
217 if not fn:
218 fn = os.path.realpath(inspect.getsourcefile(tb) or
219 inspect.getfile(tb))
220 if fn[-4:] in ('.pyc', '.pyo'):
221 fn = fn[:-1]
222
223 # module name
224 modname = tb.tb_frame.f_globals.get('__name__')
225
226 # get loader
227 loader = tb.tb_frame.f_globals.get('__loader__')
228
229 # sourcecode
230 source = ''
231 pre_context, post_context = [], []
232 context_line = raw_context_line = context_lineno = None
233 try:
234 if not loader is None and hasattr(loader, 'get_source'):
235 source = loader.get_source(modname) or ''
236 else:
237 source = file(fn).read()
238 except:
239 pass
240 else:
241 try:
242 raw_context_line = source.splitlines()[lineno - 1].strip()
243 except IndexError:
244 pass
245 if not simple:
246 parsed_source = highlight_python(source)
247 lbound = max(0, lineno - context_lines - 1)
248 ubound = lineno + context_lines
249 try:
250 context_line = parsed_source[lineno - 1]
251 pre_context = parsed_source[lbound:lineno - 1]
252 post_context = parsed_source[lineno:ubound]
253 except IndexError, e:
254 pass
255 context_lineno = lbound
256
257 if isinstance(fn, unicode):
258 fn = fn.encode('utf-8')
259 return {
260 'tb': tb,
261 'filename': fn,
262 'basename': os.path.basename(fn),
263 'loader': loader,
264 'function': function,
265 'lineno': lineno,
266 'vars': variables,
267 'pre_context': pre_context,
268 'context_line': context_line,
269 'raw_context_line': raw_context_line,
270 'post_context': post_context,
271 'context_lineno': context_lineno,
272 'source': source
273 }
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.debug.utils
3 ~~~~~~~~~~~~~~~~~~~~
4
5 Various other utilities.
6
7 :copyright: Copyright 2008 by Armin Ronacher.
8 :license: BSD.
9 """
10 from os.path import join, dirname
11 from werkzeug.templates import Template
12
13
14 def get_template(filename):
15 return Template.from_file(join(dirname(__file__), 'templates', filename))
16
17
18 def render_template(template_filename, **context):
19 return get_template(template_filename).render(**context)
5656 :copyright: 2007-2008 by Armin Ronacher.
5757 :license: BSD, see LICENSE for more details.
5858 """
59 from werkzeug.utils import escape
60 from werkzeug.wrappers import BaseResponse
61 from werkzeug.http import HTTP_STATUS_CODES
59 import sys
60 from werkzeug._internal import HTTP_STATUS_CODES
6261
6362
6463 class HTTPException(Exception):
7574 Exception.__init__(self, '%d %s' % (self.code, self.name))
7675 if description is not None:
7776 self.description = description
77
78 def wrap(cls, exception, name=None):
79 """
80 This method returns a new subclass of the exception provided that
81 also is a subclass of `BadRequest`.
82 """
83 class newcls(cls, exception):
84 def __init__(self, arg=None, description=None):
85 cls.__init__(self, description)
86 exception.__init__(self, arg)
87 newcls.__module__ = sys._getframe(1).f_globals.get('__name__')
88 newcls.__name__ = name or cls.__name__ + exception.__name__
89 return newcls
90 wrap = classmethod(wrap)
7891
7992 def name(self):
8093 """The status name."""
104117
105118 def get_response(self, environ):
106119 """Get a response object."""
120 # lazyly imported for various reasons. For one can use the exceptions
121 # with custom responses (testing exception instances against types) and
122 # so we don't ever have to import the wrappers, but also because there
123 # are ciruclar dependencies when bootstrapping the module.
124 from werkzeug.wrappers import BaseResponse
107125 headers = self.get_headers(environ)
108126 return BaseResponse(self.get_body(environ), self.code, headers)
109127
115133
116134 class _ProxyException(HTTPException):
117135 """
118 An http exception that expands renders a WSGI application on error.
136 An HTTP exception that expands renders a WSGI application on error.
119137 """
120138
121139 def __init__(self, response):
200218 code = 405
201219
202220 def __init__(self, valid_methods=None, description=None):
203 """
204 takes an optional list of valid http methods
205 starting with werkzeug 0.3 the list will be mandatory
206 """
221 """Takes an optional list of valid http methods
222 starting with werkzeug 0.3 the list will be mandatory."""
207223 HTTPException.__init__(self, description)
208224 self.valid_methods = valid_methods
209225
388404 )
389405
390406
407 default_exceptions = {}
408 __all__ = ['HTTPException']
409
391410 def _find_exceptions():
392 rv = {}
393411 for name, obj in globals().iteritems():
394412 try:
395413 if getattr(obj, 'code', None) is not None:
396 rv[obj.code] = obj
414 default_exceptions[obj.code] = obj
415 __all__.append(obj.__name__)
397416 except TypeError:
398417 continue
399 return rv
400 default_exceptions = _find_exceptions()
418 _find_exceptions()
401419 del _find_exceptions
420
421
422 #: raised by the request functions if they were unable to decode the
423 #: incomding data properly.
424 HTTPUnicodeError = BadRequest.wrap(UnicodeError, 'HTTPUnicodeError')
402425
403426
404427 class Aborter(object):
426449 raise self.mapping[code](*args, **kwargs)
427450
428451 abort = Aborter()
452
453
454 # imported here because of circular dependencies of werkzeug.utils
455 from werkzeug.utils import escape
1717 """
1818 import re
1919 import rfc822
20 from urllib2 import parse_http_list as _parse_list_header
2021 from datetime import datetime
2122 try:
2223 from hashlib import md5
2728 frozenset = frozenset
2829 except NameError:
2930 from sets import Set as set, ImmutableSet as frozenset
31 from werkzeug._internal import _patch_wrapper, _UpdateDict, HTTP_STATUS_CODES
3032
3133
3234 _accept_re = re.compile(r'([^\s;,]+)(?:[^,]*?;\s*q=(\d*(?:\.\d+)?))?')
3335 _token_chars = frozenset("!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3436 '^_`abcdefghijklmnopqrstuvwxyz|~')
35 _token = '[%s]' % ''.join(_token_chars).replace('-', '\\-')
36 _cachecontrol_re = re.compile(r'(%s+)(?:=(?:(%s+|".*?")))?' %
37 (_token, _token))
3837 _etag_re = re.compile(r'([Ww]/)?(?:"(.*?)"|(.*?))(?:\s*,\s*|$)')
3938
4039
41 HTTP_STATUS_CODES = {
42 100: 'Continue',
43 101: 'Switching Protocols',
44 102: 'Processing',
45 200: 'OK',
46 201: 'Created',
47 202: 'Accepted',
48 203: 'Non Authoritative Information',
49 204: 'No Content',
50 205: 'Reset Content',
51 206: 'Partial Content',
52 207: 'Multi Status',
53 226: 'IM Used', # see RFC 3229
54 300: 'Multiple Choices',
55 301: 'Moved Permanently',
56 302: 'Found',
57 303: 'See Other',
58 304: 'Not Modified',
59 305: 'Use Proxy',
60 307: 'Temporary Redirect',
61 400: 'Bad Request',
62 401: 'Unauthorized',
63 402: 'Payment Required', # unused
64 403: 'Forbidden',
65 404: 'Not Found',
66 405: 'Method Not Allowed',
67 406: 'Not Acceptable',
68 407: 'Proxy Authentication Required',
69 408: 'Request Timeout',
70 409: 'Conflict',
71 410: 'Gone',
72 411: 'Length Required',
73 412: 'Precondition Failed',
74 413: 'Request Entity Too Large',
75 414: 'Request URI Too Long',
76 415: 'Unsupported Media Type',
77 416: 'Requested Range Not Satisfiable',
78 417: 'Expectation Failed',
79 422: 'Unprocessable Entity',
80 423: 'Locked',
81 424: 'Failed Dependency',
82 426: 'Upgrade Required',
83 449: 'Retry With', # propritary MS extension
84 500: 'Internal Server Error',
85 501: 'Not Implemented',
86 502: 'Bad Gateway',
87 503: 'Service Unavailable',
88 504: 'Gateway Timeout',
89 505: 'HTTP Version Not Supported',
90 507: 'Insufficient Storage',
91 510: 'Not Extended'
92 }
93
94
9540 class Accept(list):
96 """
97 An `Accept` object is just a list subclass for lists of
41 """An `Accept` object is just a list subclass for lists of
9842 ``(value, quality)`` tuples. It is automatically sorted by quality.
9943 """
10044
11054 list.__init__(self, [(a, b) for b, a in values])
11155
11256 def __getitem__(self, key):
113 """
114 Beside index lookup (getting item n) you can also pass it a string to
115 get the quality for the item. If the item is not in the list, the
57 """Beside index lookup (getting item n) you can also pass it a string
58 to get the quality for the item. If the item is not in the list, the
11659 returned quality is ``0``.
11760 """
11861 if isinstance(key, basestring):
14992
15093 def values(self):
15194 """Return a list of the values, not the qualities."""
152 return [x[1] for x in self]
95 return [x[0] for x in self]
15396
15497 def itervalues(self):
15598 """Iterate over all values."""
163106
164107
165108 class HeaderSet(object):
166 """
167 Similar to the `ETags` class this implements a set like structure.
109 """Similar to the `ETags` class this implements a set like structure.
168110 Unlike `ETags` this is case insensitive and used for vary, allow, and
169111 content-language headers.
170112
186128 self.update((header,))
187129
188130 def remove(self, header):
189 """
190 Remove a layer from the set. This raises an `IndexError` if the
191 header is not in the set.
192 """
131 """Remove a layer from the set. This raises an `IndexError` if the
132 header is not in the set."""
193133 key = header.lower()
194134 if key not in self._set:
195135 raise IndexError(header)
243183 self.on_update(self)
244184
245185 def as_set(self, preserve_casing=False):
246 """
247 Return the set as real python set structure. When calling this all
248 the items are converted to lowercase and the ordering is lost.
186 """Return the set as real python set structure. When calling this
187 all the items are converted to lowercase and the ordering is lost.
249188
250189 If `preserve_casing` is `True` the items in the set returned will
251190 have the original case like in the `HeaderSet`, otherwise they will
257196
258197 def to_header(self):
259198 """Convert the header set into an HTTP header string."""
260 return ', '.join(self._headers)
199 return ', '.join(map(quote_header_value, self._headers))
261200
262201 def __getitem__(self, idx):
263202 return self._headers[idx]
298237 )
299238
300239
301 class CacheControl(dict):
302 """
303 Subclass of a dict that stores values for a Cache-Control header. It has
304 accesors for all the cache-control directives specified in RFC 2616. The
305 class does not differentiate between request and response directives.
240 class CacheControl(_UpdateDict):
241 """Subclass of a dict that stores values for a Cache-Control header. It
242 has accesors for all the cache-control directives specified in RFC 2616.
243 The class does not differentiate between request and response directives.
306244
307245 Because the cache-control directives in the HTTP header use dashes the
308246 python descriptors use underscores for that.
316254
317255 `no_cache`, `no_store`, `max_age`, `max_stale`, `min_fresh`,
318256 `no_transform`, `only_if_cached`, `public`, `private`, `must_revalidate`,
319 `proxy_revalidate`, and `s_maxage`
320 """
257 `proxy_revalidate`, and `s_maxage`"""
321258
322259 def cache_property(key, default, type):
323 """
324 Return a new property object for a cache header. Useful if you
325 want to add support for a cache extension in a subclass.
326 """
260 """Return a new property object for a cache header. Useful if you
261 want to add support for a cache extension in a subclass."""
327262 return property(lambda x: x._get_cache_value(key, default, type),
328263 lambda x, v: x._set_cache_value(key, v, type),
329264 'accessor for %r' % key)
342277 s_maxage = cache_property('s-maxage', None, None)
343278
344279 def __init__(self, values=(), on_update=None):
345 self.on_update = on_update
346 if values is None:
347 dict.__init__(self)
348 self.provided = False
349 else:
350 dict.__init__(self, values)
351 self.provided = True
352
353 def calls_update(f):
354 def oncall(self, *args, **kw):
355 rv = f(self, *args, **kw)
356 if self.on_update is not None:
357 self.on_update(self)
358 return rv
359 try:
360 oncall.__name__ = f.__name__
361 oncall.__module__ = f.__module__
362 oncall.__doc__ = f.__doc__
363 except:
364 pass
365 return oncall
366
367 __setitem__ = calls_update(dict.__setitem__)
368 __delitem__ = calls_update(dict.__delitem__)
369 clear = calls_update(dict.clear)
370 pop = calls_update(dict.pop)
371 popitem = calls_update(dict.popitem)
372 setdefault = calls_update(dict.setdefault)
373 update = calls_update(dict.update)
280 _UpdateDict.__init__(self, values or (), on_update)
281 self.provided = values is not None
374282
375283 def _get_cache_value(self, key, default, type):
376284 """Used internally be the accessor properties."""
399307 self[key] = value
400308 else:
401309 self.pop(key, None)
402 _set_cache_value = calls_update(_set_cache_value)
403310
404311 def to_header(self):
405312 """Convert the stored values into a cache control header."""
406 items = []
407 for key, value in self.iteritems():
408 if value is None:
409 items.append(key)
410 else:
411 value = str(value)
412 if not set(value).issubset(_token_chars):
413 value = '"%s"' % value.replace('"', "'")
414 items.append('%s=%s' % (key, value))
415 return ', '.join(items)
313 return dump_header(self)
416314
417315 def __str__(self):
418316 return self.to_header()
419317
420318 def __repr__(self):
421 return '%s(%s)' % (
319 return '<%s %r>' % (
422320 self.__class__.__name__,
423 dict.__repr__(self)
321 self.to_header()
424322 )
425323
426324 # make cache_property a staticmethod so that subclasses of
427325 # `CacheControl` can use it for new properties.
428326 cache_property = staticmethod(cache_property)
429 del calls_update
430327
431328
432329 class ETags(object):
433 """
434 A set that can be used to check if one etag is present in a collection
330 """A set that can be used to check if one etag is present in a collection
435331 of etags.
436332 """
437333
441337 self.star_tag = star_tag
442338
443339 def as_set(self, include_weak=False):
444 """
445 Convert the `ETags` object into a python set. Per default all the
446 weak etags are not part of this set.
447 """
340 """Convert the `ETags` object into a python set. Per default all the
341 weak etags are not part of this set."""
448342 rv = set(self._strong)
449343 if include_weak:
450344 rv.update(self._weak)
465359 return etag in self._strong
466360
467361 def contains_raw(self, etag):
468 """
469 When passed a quoted tag it will check if this tag is part of the set.
470 If the tag is weak it is checked against weak and strong tags, otherwise
471 weak only.
472 """
362 """When passed a quoted tag it will check if this tag is part of the
363 set. If the tag is weak it is checked against weak and strong tags,
364 otherwise weak only."""
473365 etag, weak = unquote_etag(etag)
474366 if weak:
475367 return self.contains_weak(etag)
479371 """Convert the etags set into a HTTP header string."""
480372 if self.star_tag:
481373 return '*'
482 return ', '.join(['"%s"' % item for item in self.as_set(True)])
374 return ', '.join(
375 ['"%s"' % x for x in self._strong] +
376 ['w/"%s"' % x for x in self._weak]
377 )
483378
484379 def __call__(self, etag=None, data=None, include_weak=False):
485380 if [etag, data].count(None) != 1:
507402 return '<%s %r>' % (self.__class__.__name__, str(self))
508403
509404
405 class Authorization(dict):
406 """Represents an `Authorization` header sent by the client. You should
407 not create this kind of object yourself but use it when it's returned by
408 the `parse_authorization_header` function.
409
410 This object is a dict subclass and can be altered by setting dict items
411 but it should be considered immutable as it's returned by the client and
412 not meant for modifications.
413 """
414
415 def __init__(self, auth_type, data=None):
416 dict.__init__(self, data or {})
417 self.type = auth_type
418
419 username = property(lambda x: x.get('username'), doc='''
420 The username transmitted. This is set for both basic and digest
421 auth all the time.''')
422 password = property(lambda x: x.get('password'), doc='''
423 When the authentication type is basic this is the password
424 transmitted by the client, else `None`.''')
425 realm = property(lambda x: x.get('realm'), doc='''
426 This is the server realm send back for digest auth. For HTTP
427 digest auth.''')
428 nonce = property(lambda x: x.get('nonce'), doc='''
429 The nonce the server send for digest auth, send back by the client.
430 A nonce should be unique for every 401 response for HTTP digest
431 auth.''')
432 uri = property(lambda x: x.get('uri'), doc='''
433 The URI from Request-URI of the Request-Line; duplicated because
434 proxies are allowed to change the Request-Line in transit. HTTP
435 digest auth only.''')
436 nc = property(lambda x: x.get('nc'), doc='''
437 The nonce count value transmitted by clients if a qop-header is
438 also transmitted. HTTP digest auth only.''')
439 cnonce = property(lambda x: x.get('cnonce'), doc='''
440 If the server sent a qop-header in the ``WWW-Authenticate``
441 header, the client has to provide this value for HTTP digest auth.
442 See the RFC for more details.''')
443 response = property(lambda x: x.get('response'), doc='''
444 A string of 32 hex digits computed as defined in RFC 2617, which
445 proves that the user knows a password. Digest auth only.''')
446 opaque = property(lambda x: x.get('opaque'), doc='''
447 The opaque header from the server returned unchanged by the client.
448 It is recommended that this string be base64 or hexadecimal data.
449 Digest auth only.''')
450
451 def qop(self):
452 """Indicates what "quality of protection" the client has applied to
453 the message for HTTP digest auth."""
454 def on_update(header_set):
455 if not header_set and name in self:
456 del self['qop']
457 elif header_set:
458 self['qop'] = header_set.to_header()
459 return parse_set_header(self.get('qop'), on_update)
460 qop = property(qop, doc=qop.__doc__)
461
462
463 class WWWAuthenticate(_UpdateDict):
464 """Provides simple access to `WWW-Authenticate` headers."""
465
466 #: list of keys that require quoting in the generated header
467 _require_quoting = frozenset(['domain', 'nonce', 'opaque', 'realm'])
468
469 def __init__(self, auth_type=None, values=None, on_update=None):
470 _UpdateDict.__init__(self, values or (), on_update)
471 if auth_type:
472 self['__auth_type__'] = auth_type
473
474 def set_basic(self, realm='authentication required'):
475 """Clear the auth info and enable basic auth."""
476 dict.clear(self)
477 dict.update(self, {'__auth_type__': 'basic', 'realm': realm})
478 if self.on_update:
479 self.on_update(self)
480
481 def set_digest(self, realm, nonce, qop=('auth',), opaque=None,
482 algorithm=None, stale=False):
483 """Clear the auth info and enable digest auth."""
484 d = {
485 '__auth_type__': 'digest',
486 'realm': realm,
487 'nonce': nonce,
488 'qop': dump_header(qop)
489 }
490 if stale:
491 d['stale'] = 'TRUE'
492 if opaque is not None:
493 d['opaque'] = opaque
494 if algorithm is not None:
495 d['algorithm'] = algorithm
496 dict.clear(self)
497 dict.update(self, d)
498 if self.on_update:
499 self.on_update(self)
500
501 def to_header(self):
502 """Convert the stored values into a WWW-Authenticate header."""
503 d = dict(self)
504 auth_type = d.pop('__auth_type__', None) or 'basic'
505 return '%s %s' % (auth_type.title(), ', '.join([
506 '%s=%s' % (key, quote_header_value(value,
507 allow_token=key not in self._require_quoting))
508 for key, value in d.iteritems()
509 ]))
510
511 def __str__(self):
512 return self.to_header()
513
514 def __repr__(self):
515 return '<%s %r>' % (
516 self.__class__.__name__,
517 self.to_header()
518 )
519
520 def auth_property(name, doc=None):
521 def _set_value(self, value):
522 if value is None:
523 self.pop(name, None)
524 else:
525 self[name] = str(value)
526 return property(lambda x: x.get(name), _set_value, doc=doc)
527
528 def _set_property(name, doc=None):
529 def fget(self):
530 def on_update(header_set):
531 if not header_set and name in self:
532 del self[name]
533 elif header_set:
534 self[name] = header_set.to_header()
535 return parse_set_header(self.get(name), on_update)
536 return property(fget, doc=doc)
537
538 type = auth_property('__auth_type__', doc='''
539 The type of the auth machanism. HTTP currently specifies
540 `Basic` and `Digest`.''')
541 realm = auth_property('realm', doc='''
542 A string to be displayed to users so they know which username and
543 password to use. This string should contain at least the name of
544 the host performing the authentication and might additionally
545 indicate the collection of users who might have access.''')
546 domain = _set_property('domain', doc='''
547 A list of URIs that define the protection space. If a URI is an
548 absolte path, it is relative to the canonical root URL of the
549 server being accessed.''')
550 nonce = auth_property('nonce', doc='''
551 A server-specified data string which should be uniquely generated
552 each time a 401 response is made. It is recommended that this
553 string be base64 or hexadecimal data.''')
554 opaque = auth_property('opaque', doc='''
555 A string of data, specified by the server, which should be returned
556 by the client unchanged in the Authorization header of subsequent
557 requests with URIs in the same protection space. It is recommended
558 that this string be base64 or hexadecimal data.''')
559 algorithm = auth_property('algorithm', doc='''
560 A string indicating a pair of algorithms used to produce the digest
561 and a checksum. If this is not present it is assumed to be "MD5".
562 If the algorithm is not understood, the challenge should be ignored
563 (and a different one used, if there is more than one).''')
564 qop = _set_property('qop', doc='''
565 A set of quality-of-privacy modifies such as auth and auth-int.''')
566
567 def _get_stale(self):
568 val = self.get('stale')
569 if val is not None:
570 return val.lower() == 'true'
571 def _set_stale(self, value):
572 if value is None:
573 self.pop('stale', None)
574 else:
575 self['stale'] = value and 'TRUE' or 'FALSE'
576 stale = property(_get_stale, _set_stale, doc='''
577 A flag, indicating that the previous request from the client was
578 rejected because the nonce value was stale.''')
579 del _get_stale, _set_stale
580
581 # make auth_property a staticmethod so that subclasses of
582 # `WWWAuthenticate` can use it for new properties.
583 auth_property = staticmethod(auth_property)
584 del _set_property
585
586
587 def quote_header_value(value, extra_chars='', allow_token=True):
588 """Quote a header value if necessary."""
589 value = str(value)
590 if allow_token:
591 token_chars = _token_chars | set(extra_chars)
592 if set(value).issubset(token_chars):
593 return value
594 return '"%s"' % value.replace('\\', '\\\\').replace('"', '\\"')
595
596
597 def dump_header(iterable, allow_token=True):
598 """Dump an HTTP header again. This is the reversal of
599 `parse_list_header`, `parse_set_header` and `parse_dict_header`. This
600 also quotes strings that include an equals sign unless you pass it as dict
601 of key, value pairs.
602
603 The `allow_token` parameter can be set to `False` to disallow tokens as
604 values. If this is enabled all values are quoted.
605 """
606 if isinstance(iterable, dict):
607 items = []
608 for key, value in iterable.iteritems():
609 if value is None:
610 items.append(key)
611 else:
612 items.append('%s=%s' % (
613 key,
614 quote_header_value(value, allow_token=allow_token)
615 ))
616 else:
617 items = [quote_header_value(x, allow_token=allow_token)
618 for x in iterable]
619 return ', '.join(items)
620
621
622 def parse_list_header(value):
623 """Parse lists as described by RFC 2068 Section 2.
624
625 In particular, parse comma-separated lists where the elements of
626 the list may include quoted-strings. A quoted-string could
627 contain a comma. A non-quoted string could have quotes in the
628 middle. Quotes are removed automatically after parsing.
629 """
630 result = []
631 for item in _parse_list_header(value):
632 if item[:1] == item[-1:] == '"':
633 item = item[1:-1]
634 result.append(item)
635 return result
636
637
638 def parse_dict_header(value):
639 """Parse lists of key, value paits as described by RFC 2068 Section 2 and
640 convert them into a python dict. If there is no value for a key it will
641 be `None`.
642 """
643 result = {}
644 for item in _parse_list_header(value):
645 if '=' not in item:
646 result[item] = None
647 continue
648 name, value = item.split('=', 1)
649 if value[:1] == value[-1:] == '"':
650 value = value[1:-1]
651 result[name] = value
652 return result
653
654
510655 def parse_accept_header(value):
511 """
512 Parses an HTTP Accept-* header. This does not implement a complete valid
513 algorithm but one that supports at least value and quality extraction.
656 """Parses an HTTP Accept-* header. This does not implement a complete
657 valid algorithm but one that supports at least value and quality
658 extraction.
514659
515660 Returns a new `Accept` object (basicly a list of ``(value, quality)``
516 tuples sorted by the quality with some additional accessor methods)
661 tuples sorted by the quality with some additional accessor methods).
517662 """
518663 if not value:
519664 return Accept(None)
529674
530675
531676 def parse_cache_control_header(value, on_update=None):
532 """
533 Parse a cache control header. The RFC differs between response and
677 """Parse a cache control header. The RFC differs between response and
534678 request cache control, this method does not. It's your responsibility
535679 to not use the wrong control statements.
536680 """
537681 if not value:
538682 return CacheControl(None, on_update)
539 result = {}
540 for match in _cachecontrol_re.finditer(value):
541 name, value = match.group(1, 2)
542 if value and value[0] == value[-1] == '"':
543 value = value[1:-1]
544 result[name] = value
545 return CacheControl(result, on_update)
683 return CacheControl(parse_dict_header(value), on_update)
546684
547685
548686 def parse_set_header(value, on_update=None):
549 """
550 Parse a set like header and return a `HeaderSet` object. The return
687 """Parse a set like header and return a `HeaderSet` object. The return
551688 value is an object that treats the items case insensitive and keeps the
552689 order of the items.
553690 """
554691 if not value:
555692 return HeaderSet(None, on_update)
556 return HeaderSet([x.strip() for x in value.split(',')], on_update)
693 return HeaderSet(parse_list_header(value), on_update)
694
695
696 def parse_authorization_header(value):
697 """Parse an HTTP basic/digest authorization header transmitted by the web
698 browser. The return value is either `None` if the header was invalid or
699 not given, otherwise an `Authorization` object.
700 """
701 if not value:
702 return
703 try:
704 auth_type, auth_info = value.split(None, 1)
705 auth_type = auth_type.lower()
706 except ValueError:
707 return
708 if auth_type == 'basic':
709 try:
710 username, password = auth_info.decode('base64').split(':', 1)
711 except Exception, e:
712 return
713 return Authorization('basic', {'username': username,
714 'password': password})
715 elif auth_type == 'digest':
716 auth_map = parse_dict_header(auth_info)
717 for key in 'username', 'realm', 'nonce', 'uri', 'nc', 'cnonce', \
718 'response':
719 if not key in auth_map:
720 return
721 return Authorization('digest', auth_map)
722
723
724 def parse_www_authenticate_header(value, on_update=None):
725 """Parse an HTTP WWW-Authenticate header into a `WWWAuthenticate`
726 object."""
727 if not value:
728 return WWWAuthenticate(on_update=on_update)
729 try:
730 auth_type, auth_info = value.split(None, 1)
731 auth_type = auth_type.lower()
732 except (ValueError, AttributeError):
733 return WWWAuthenticate(value.lower(), on_update=on_update)
734 return WWWAuthenticate(auth_type, parse_dict_header(auth_info),
735 on_update)
557736
558737
559738 def quote_etag(etag, weak=False):
611790
612791
613792 def parse_date(value):
614 """
615 Parse one of the following date formats into a datetime object:
793 """Parse one of the following date formats into a datetime object:
616794
617795 .. sourcecode:: text
618796
623801 If parsing fails the return value is `None`.
624802 """
625803 if value:
626 t = rfc822.parsedate_tz(value)
804 t = rfc822.parsedate_tz(value.strip())
627805 if t is not None:
806 # if no timezone is part of the string we assume UTC
807 if t[-1] is None:
808 t = t[:-1] + (0,)
628809 return datetime.utcfromtimestamp(rfc822.mktime_tz(t))
629810
630811
7676 from thread import get_ident as get_current_thread, allocate_lock
7777 except ImportError:
7878 from dummy_thread import get_ident as get_current_thread, allocate_lock
79 from werkzeug.utils import ClosingIterator, _patch_wrapper
79 from werkzeug.utils import ClosingIterator
80 from werkzeug._internal import _patch_wrapper
8081
8182
8283 # get the best ident function. if greenlets are not installed we can
136137
137138
138139 class LocalManager(object):
139 """
140 Local objects cannot manage themselves. For that you need a local manager.
141 You can pass a local manager multiple locals or add them later by
142 appending them to `manager.locals`. Everytime the manager cleans up it,
143 will clean up all the data left in the locals for this context.
140 """Local objects cannot manage themselves. For that you need a local
141 manager. You can pass a local manager multiple locals or add them later
142 by appending them to `manager.locals`. Everytime the manager cleans up
143 it, will clean up all the data left in the locals for this context.
144144 """
145145
146146 def __init__(self, locals=None):
153153 self.locals = [locals]
154154
155155 def get_ident(self):
156 """
157 Return the context identifier the local objects use internally for
156 """Return the context identifier the local objects use internally for
158157 this context. You cannot override this method to change the behavior
159158 but use it to link other context local objects (such as SQLAlchemy's
160159 scoped sessions) to the Werkzeug locals.
162161 return get_ident()
163162
164163 def cleanup(self):
165 """
166 Manually clean up the data in the locals for this context. Call this
167 at the end of the request or use `make_middleware()`.
164 """Manually clean up the data in the locals for this context. Call
165 this at the end of the request or use `make_middleware()`.
168166 """
169167 ident = self.get_ident()
170168 for local in self.locals:
171169 local.__storage__.pop(ident, None)
172170
173171 def make_middleware(self, app):
174 """
175 Wrap a WSGI application so that cleaning up happens after
172 """Wrap a WSGI application so that cleaning up happens after
176173 request end.
177174 """
178175 def application(environ, start_response):
180177 return application
181178
182179 def middleware(self, func):
183 """
184 Like `make_middleware` but for decorating functions. Example
185 usage::
180 """Like `make_middleware` but for decorating functions.
181
182 Example usage::
186183
187184 @manager.middleware
188185 def application(environ, start_response):
202199
203200
204201 class LocalProxy(object):
205 """
206 Acts as a proxy for a werkzeug local. Forwards all operations to
202 """Acts as a proxy for a werkzeug local. Forwards all operations to
207203 a proxied object. The only operations not supported for forwarding
208204 are right handed operands and any kind of assignment.
209205
224220 object.__setattr__(self, '_LocalProxy__local', local)
225221 object.__setattr__(self, '__name__', name)
226222
227 def __current_object(self):
223 def _get_current_object(self):
224 """Return the current object. This is useful if you want the real
225 object behind the proxy at a time for performance reasons or because
226 you want to pass the object into a different context.
227 """
228228 try:
229229 return getattr(self.__local, self.__name__)
230230 except AttributeError:
231231 raise RuntimeError('no object bound to %s' % self.__name__)
232 __current_object = property(__current_object)
232 __current_object = property(_get_current_object)
233233
234234 def __dict__(self):
235235 try:
305305 __divmod__ = lambda x, o: x.__current_object.__divmod__(o)
306306 __pow__ = lambda x, o: x.__current_object ** o
307307 __lshift__ = lambda x, o: x.__current_object << o
308 __rshift__ = lambda x, o: x.__current_bject >> o
309 __and__ = lambda x, o: x.__current_obejct & o
308 __rshift__ = lambda x, o: x.__current_object >> o
309 __and__ = lambda x, o: x.__current_object & o
310310 __xor__ = lambda x, o: x.__current_object ^ o
311311 __or__ = lambda x, o: x.__current_object | o
312312 __div__ = lambda x, o: x.__current_object.__div__(o)
3232 If the application doesn't use subdomains it's perfectly fine to not set
3333 the default subdomain and use the `Subdomain` rule factory. The endpoint
3434 in the rules can be anything, for example import paths or unique
35 identitifers. The WSGI application can use those endpoints to get the
35 identifiers. The WSGI application can use those endpoints to get the
3636 handler for that URL. It doesn't have to be a string at all but it's
3737 recommended.
3838
118118 (?P<variable>[a-zA-Z][a-zA-Z0-9_]*) # variable name
119119 >
120120 ''', re.VERBOSE)
121 _simple_rule_re = re.compile(r'<([^>]+)>')
121122
122123
123124 def parse_rule(rule):
124 """
125 Parse a rule and return it as generator. Each iteration yields tuples in the
126 form ``(converter, arguments, variable)``. If the converter is `None` it's a
127 static url part, otherwise it's a dynamic one.
125 """Parse a rule and return it as generator. Each iteration yields tuples
126 in the form ``(converter, arguments, variable)``. If the converter is
127 `None` it's a static url part, otherwise it's a dynamic one.
128128
129129 :internal:
130130 """
154154
155155
156156 def get_converter(map, name, args):
157 """
158 Create a new converter for the given arguments or raise
157 """Create a new converter for the given arguments or raise
159158 exception if the converter does not exist.
160159
161160 :internal:
172171
173172
174173 class RoutingException(Exception):
175 """
176 Special exceptions that require the application to redirect, notifies him
177 about missing urls etc.
174 """Special exceptions that require the application to redirect, notifies
175 him about missing urls etc.
178176
179177 :internal:
180178 """
181179
182180
183181 class RequestRedirect(HTTPException, RoutingException):
184 """
185 Raise if the map requests a redirect. This is for example the case if
182 """Raise if the map requests a redirect. This is for example the case if
186183 `strict_slashes` are activated and an url that requires a leading slash.
187184
188185 The attribute `new_url` contains the absolute desitination url.
198195
199196
200197 class RequestSlash(RoutingException):
201 """
202 Internal exception.
203 """
198 """Internal exception."""
204199
205200
206201 class BuildError(RoutingException, LookupError):
207 """
208 Raised if the build system cannot find a URL for an endpoint with the
202 """Raised if the build system cannot find a URL for an endpoint with the
209203 values provided.
210204 """
211205
217211
218212
219213 class ValidationError(ValueError):
220 """
221 Validation error. If a rule converter raises this exception the rule
214 """Validation error. If a rule converter raises this exception the rule
222215 does not match the current URL and the next URL is tried.
223216 """
224217
225218
226219 class RuleFactory(object):
227 """
228 As soon as you have more complex URL setups it's a good idea to use rule
220 """As soon as you have more complex URL setups it's a good idea to use rule
229221 factories to avoid repetitive tasks. Some of them are builtin, others can
230222 be added by subclassing `RuleFactory` and overriding `get_rules`.
231223 """
232224
233225 def get_rules(self, map):
234 """
235 Subclasses of `RuleFactory` have to override this method and return
236 an iterable of rules.
237 """
226 """Subclasses of `RuleFactory` have to override this method and return
227 an iterable of rules."""
238228 raise NotImplementedError()
239229
240230
241231 class Subdomain(RuleFactory):
242 """
243 All URLs provided by this factory have the subdomain set to a
232 """All URLs provided by this factory have the subdomain set to a
244233 specific domain. For example if you want to use the subdomain for
245234 the current language this can be a good setup::
246235
270259
271260
272261 class Submount(RuleFactory):
273 """
274 Like `Subdomain` but prefixes the URL rule with a given string::
262 """Like `Subdomain` but prefixes the URL rule with a given string::
275263
276264 url_map = Map([
277265 Rule('/', endpoint='index'),
296284
297285
298286 class EndpointPrefix(RuleFactory):
299 """
300 Prefixes all endpoints (which must be strings for this factory) with
287 """Prefixes all endpoints (which must be strings for this factory) with
301288 another string. This can be useful for sub applications::
302289
303290 url_map = Map([
321308
322309
323310 class RuleTemplate(object):
324 """
325 Returns copies of the rules wrapped and expands string templates in
311 """Returns copies of the rules wrapped and expands string templates in
326312 the endpoint, rule, defaults or subdomain sections.
327313
328314 Here a small example for such a rule template::
348334
349335
350336 class RuleTemplateFactory(RuleFactory):
351 """
352 A factory that fills in template variables into rules. Used by
337 """A factory that fills in template variables into rules. Used by
353338 `RuleTemplate` internally.
354339
355340 :internal:
386371
387372
388373 class Rule(RuleFactory):
389 """
390 A Rule represents one URL pattern. There are some options for `Rule` that
391 change the way it behaves and are passed to the `Rule` constructor. Note
392 that beside the rule-string all arguments *must* be keyword arguments in
393 order to not break the application on Werkzeug upgrades.
374 """A Rule represents one URL pattern. There are some options for `Rule`
375 that change the way it behaves and are passed to the `Rule` constructor.
376 Note that beside the rule-string all arguments *must* be keyword arguments
377 in order to not break the application on Werkzeug upgrades.
394378
395379 `string`
396380 Rule strings basically are just normal URL paths with placeholders in
453437 Set this to true and the rule will never match but will create a URL
454438 that can be build. This is useful if you have resources on a subdomain
455439 or folder that are not handled by the WSGI application (like static data)
440
441 `redirect_to`
442 If given this must be either a string or callable. In case of a
443 callable it's called with the url adapter that triggered the match and
444 the values of the URL as keyword arguments and has to return the target
445 for the redirect, otherwise it has to be a string with placeholders in
446 rule syntax::
447
448 def foo_with_slug(adapter, id):
449 # ask the database for the slug for the old id. this of
450 # course has nothing to do with werkzeug.
451 return 'foo/' + Foo.get_slug_for_id(id)
452
453 url_map = Map([
454 Rule('/foo/<slug>', endpoint='foo'),
455 Rule('/some/old/url/<slug>', redirect_to='foo/<slug>'),
456 Rule('/other/old/url/<int:id>', redirect_to=foo_with_slug)
457 ])
458
459 When the rule is matched the routing system will raise a
460 `RequestRedirect` exception with the target for the redirect.
461
462 Keep in mind that the URL will be joined against the URL root of the
463 script so don't use a leading slash on the target URL unless you
464 really mean root of that domain.
456465 """
457466
458467 def __init__(self, string, defaults=None, subdomain=None, methods=None,
459 build_only=False, endpoint=None, strict_slashes=None):
468 build_only=False, endpoint=None, strict_slashes=None,
469 redirect_to=None):
460470 if not string.startswith('/'):
461471 raise ValueError('urls must start with a leading slash')
462472 self.rule = string
473483 self.methods = set([x.upper() for x in methods])
474484 self.endpoint = endpoint
475485 self.greediness = 0
486 self.redirect_to = redirect_to
476487
477488 self._trace = []
478489 if defaults is not None:
484495 self._weights = []
485496
486497 def empty(self):
487 """
488 Return an unbound copy of this rule. This can be useful if you want
489 to reuse an already bound URL for another map.
490 """
498 """Return an unbound copy of this rule. This can be useful if you
499 want to reuse an already bound URL for another map."""
491500 return Rule(self.rule, self.defaults, self.subdomain, self.methods,
492 self.build_only, self.endpoint, self.strict_slashes)
501 self.build_only, self.endpoint, self.strict_slashes,
502 self.redirect_to)
493503
494504 def get_rules(self, map):
495505 yield self
496506
497507 def bind(self, map):
498 """
499 Bind the url to a map and create a regular expression based on
508 """Bind the url to a map and create a regular expression based on
500509 the information from the rule itself and the defaults from the map.
501510
502511 :internal:
540549 self._regex = re.compile(regex, re.UNICODE)
541550
542551 def match(self, path):
543 """
544 Check if the rule matches a given path. Path is a string in the
552 """Check if the rule matches a given path. Path is a string in the
545553 form ``"subdomain|/path(method)"`` and is assembled by the map.
546554
547555 If the rule matches a dict with the converted values is returned,
577585 return result
578586
579587 def build(self, values):
580 """
581 Assembles the relative url for that rule and the subdomain.
588 """Assembles the relative url for that rule and the subdomain.
582589 If building doesn't work for some reasons `None` is returned.
583590
584591 :internal:
585592 """
586593 tmp = []
594 add = tmp.append
587595 processed = set(self.arguments)
588596 for is_dynamic, data in self._trace:
589597 if is_dynamic:
590598 try:
591 tmp.append(self._converters[data].to_url(values[data]))
599 add(self._converters[data].to_url(values[data]))
592600 except ValidationError:
593601 return
594602 processed.add(data)
595603 else:
596 tmp.append(data)
604 add(data)
597605 subdomain, url = (u''.join(tmp)).split('|', 1)
598606
599607 query_vars = {}
605613 return subdomain, url
606614
607615 def provides_defaults_for(self, rule):
608 """
609 Check if this rule has defaults for a given rule.
616 """Check if this rule has defaults for a given rule.
610617
611618 :internal:
612619 """
615622 self.arguments == rule.arguments
616623
617624 def suitable_for(self, values, method):
618 """
619 Check if the dict of values has enough data for url generation.
625 """Check if the dict of values has enough data for url generation.
620626
621627 :internal:
622628 """
639645 return True
640646
641647 def match_compare(self, other):
642 """
643 Compare this object with another one for matching.
648 """Compare this object with another one for matching.
644649
645650 :internal:
646651 """
672677 return 1
673678
674679 def build_compare(self, other):
675 """
676 Compare this object with another one for building.
680 """Compare this object with another one for building.
677681
678682 :internal:
679683 """
733737
734738
735739 class BaseConverter(object):
736 """
737 Base class for all converters.
738 """
740 """Base class for all converters."""
739741 regex = '[^/]+'
740742 is_greedy = False
741743 weight = 100
751753
752754
753755 class UnicodeConverter(BaseConverter):
754 """
755 This converter is the default converter and accepts any string but
756 """This converter is the default converter and accepts any string but
756757 only one one path segment. Thus the string can not include a slash.
757758
758759 Supported arguments:
780781
781782
782783 class AnyConverter(BaseConverter):
783 """
784 Matches one of the items provided. Items can either be Python
784 """Matches one of the items provided. Items can either be Python
785785 identifiers or unicode strings::
786786
787787 Rule('/<any(about, help, imprint, u"class"):page_name>')
793793
794794
795795 class PathConverter(BaseConverter):
796 """
797 Matches one of the items provided. Items can either be python
798 identifiers or unicode strings::
799
800 Rule('/<any(about, help, imprint, u"class"):page_name>')
801 """
796 """Like the default string converter, but it also matches slashes."""
802797 regex = '[^/].*?'
803798 is_greedy = True
804799 weight = 50
805800
806801
807802 class NumberConverter(BaseConverter):
808 """
809 Baseclass for `IntegerConverter` and `FloatConverter`.
803 """Baseclass for `IntegerConverter` and `FloatConverter`.
810804
811805 :internal:
812806 """
834828
835829
836830 class IntegerConverter(NumberConverter):
837 """
838 This converter only accepts integer values::
831 """This converter only accepts integer values::
839832
840833 Rule('/page/<int:page>')
841834
853846
854847
855848 class FloatConverter(NumberConverter):
856 """
857 This converter only accepts floating point values::
849 """This converter only accepts floating point values::
858850
859851 Rule('/probability/<float:probability>')
860852
871863
872864
873865 class Map(object):
874 """
875 The map class stores all the URL rules and some configuration
866 """The map class stores all the URL rules and some configuration
876867 parameters. Some of the configuration values are only stored on the
877868 `Map` instance since those affect all rules, others are just defaults
878869 and can be overridden for each rule. Note that you have to specify all
882873 def __init__(self, rules=None, default_subdomain='', charset='utf-8',
883874 strict_slashes=True, redirect_defaults=True,
884875 converters=None):
885 """
886 Initializes the new URL map.
876 """Initializes the new URL map.
887877
888878 :param rules: sequence of url rules for this map.
889879 :param default_subdomain: The default subdomain for rules without a
914904 self.add(rulefactory)
915905
916906 def is_endpoint_expecting(self, endpoint, *arguments):
917 """
918 Iterate over all rules and check if the endpoint expects
907 """Iterate over all rules and check if the endpoint expects
919908 the arguments provided. This is for example useful if you have
920909 some URLs that expect a language code and others that do not and
921910 you want to wrap the builder a bit so that the current language
936925 return iter(self._rules)
937926
938927 def add(self, rulefactory):
939 """
940 Add a new rule or factory to the map and bind it. Requires that the
928 """Add a new rule or factory to the map and bind it. Requires that the
941929 rule is not bound to another map.
942930 """
943931 for rule in rulefactory.get_rules(self):
953941
954942 def bind(self, server_name, script_name=None, subdomain=None,
955943 url_scheme='http', default_method='GET', path_info=None):
956 """
957 Return a new `MapAdapter` with the details specified to the call.
944 """Return a new `MapAdapter` with the details specified to the call.
958945 Note that `script_name` will default to ``'/'`` if not further
959946 specified or `None`. The `server_name` at least is a requirement
960947 because the HTTP RFC requires absolute URLs for redirects and so all
979966
980967 def bind_to_environ(self, environ, server_name=None, subdomain=None,
981968 calculate_subdomain=False):
982 """
983 Like `bind` but you can pass it an WSGI environment and it will fetch
984 the information from that directory. Note that because of limitations
985 in the protocol there is no way to get the current subdomain and real
986 `server_name` from the environment. If you don't provide it, Werkzeug
987 will use `SERVER_NAME` and `SERVER_PORT` (or `HTTP_HOST` if provided)
988 as used `server_name` with disabled subdomain feature.
969 """Like `bind` but you can pass it an WSGI environment and it will
970 fetch the information from that directory. Note that because of
971 limitations in the protocol there is no way to get the current
972 subdomain and real `server_name` from the environment. If you don't
973 provide it, Werkzeug will use `SERVER_NAME` and `SERVER_PORT` (or
974 `HTTP_HOST` if provided) as used `server_name` with disabled subdomain
975 feature.
989976
990977 If `subdomain` is `None` but an environment and a server name is
991978 provided it will calculate the current subdomain automatically.
10241011 environ['REQUEST_METHOD'], environ.get('PATH_INFO'))
10251012
10261013 def update(self):
1027 """
1028 Called before matching and building to keep the compiled rules
1014 """Called before matching and building to keep the compiled rules
10291015 in the correct order after things changed.
10301016 """
10311017 if self._remap:
10361022
10371023
10381024 class MapAdapter(object):
1039 """
1040 Retured by `Map.bind` or `Map.bind_to_environ` and does the
1025 """Retured by `Map.bind` or `Map.bind_to_environ` and does the
10411026 URL matching and building based on runtime information.
10421027 """
10431028
10551040
10561041 def dispatch(self, view_func, path_info=None, method=None,
10571042 catch_http_exceptions=False):
1058 """
1059 Does the complete dispatching process. `view_func` is called with
1043 """Does the complete dispatching process. `view_func` is called with
10601044 the endpoint and a dict with the values for the view. It should
10611045 look up the view function, call it, and return a response object
10621046 or WSGI application. http exceptions are not catched by default
10821066 urls = url_map.bind_to_environ(environ)
10831067 return urls.dispatch(lambda e, v: views[e](request, **v),
10841068 catch_http_exceptions=True)
1069
1070 Keep in mind that this method might return exception objects too, so
1071 use `Response.force_type` to get a response object.
10851072 """
10861073 try:
1087 endpoint, args = self.match(path_info, method)
1088 except RequestRedirect, e:
1089 return e.get_response()
1090 try:
1074 try:
1075 endpoint, args = self.match(path_info, method)
1076 except RequestRedirect, e:
1077 return e
10911078 return view_func(endpoint, args)
10921079 except HTTPException, e:
10931080 if catch_http_exceptions:
1094 return e.get_response()
1081 return e
10951082 raise
10961083
10971084 def match(self, path_info=None, method=None):
1098 """
1099 The usage is simple: you just pass the match method the current path
1100 info as well as the method (which defaults to `GET`). The following
1101 things can then happen:
1085 """The usage is simple: you just pass the match method the current
1086 path info as well as the method (which defaults to `GET`). The
1087 following things can then happen:
11021088
11031089 - you receive a `NotFound` exception that indicates that no URL is
11041090 matching. A `NotFound` exception is also a WSGI application you
11891175 self.script_name[:-1],
11901176 path.lstrip('/')
11911177 )))
1178 if rule.redirect_to is not None:
1179 if isinstance(rule.redirect_to, basestring):
1180 def _handle_match(match):
1181 value = rv[match.group(1)]
1182 return rule._converters[match.group(1)].to_url(value)
1183 redirect_url = _simple_rule_re.sub(_handle_match,
1184 rule.redirect_to)
1185 else:
1186 redirect_url = rule.redirect_to(self, **rv)
1187 raise RequestRedirect(str(urljoin('%s://%s%s%s' % (
1188 self.url_scheme,
1189 self.subdomain and self.subdomain + '.' or '',
1190 self.server_name,
1191 self.script_name
1192 ), redirect_url)))
11921193 return rule.endpoint, rv
11931194 if have_match_for:
11941195 raise MethodNotAllowed(valid_methods=list(have_match_for))
11951196 raise NotFound()
11961197
11971198 def test(self, path_info=None, method=None):
1198 """
1199 Test if a rule would match. Works like `match` but returns `True` if
1200 the URL matches, or `False` if it does not exist.
1199 """Test if a rule would match. Works like `match` but returns `True`
1200 if the URL matches, or `False` if it does not exist.
12011201 """
12021202 try:
12031203 self.match(path_info, method)
12081208 return True
12091209
12101210 def build(self, endpoint, values=None, method=None, force_external=False):
1211 """
1212 Building URLs works pretty much the other way round. Instead of
1211 """Building URLs works pretty much the other way round. Instead of
12131212 `match` you call `build` and pass it the endpoint and a dict of
12141213 arguments for the placeholders.
12151214
12531252 else:
12541253 values = {}
12551254
1256 for rule in self.map._rules_by_endpoint.get(endpoint) or ():
1255 for rule in self.map._rules_by_endpoint.get(endpoint, ()):
12571256 if rule.suitable_for(values, method):
12581257 rv = rule.build(values)
12591258 if rv is not None:
9090
9191
9292 def run(namespace=None, action_prefix='action_', args=None):
93 """
94 Run the script. Participating actions are looked up in the callers
93 """Run the script. Participating actions are looked up in the callers
9594 namespace if no namespace is given, otherwise in the dict provided.
9695 Only items that start with action_prefix are processed as actions. If
9796 you want to use all items in the namespace provided as actions set
98 action_prefix to an empty string.
99 """
97 action_prefix to an empty string."""
10098 if namespace is None:
10199 namespace = sys._getframe(1).f_locals
102100 actions = find_actions(namespace, action_prefix)
234232
235233
236234 def make_shell(init_func=lambda: {}, banner=None, use_ipython=True):
237 """
238 Returns an action callback that spawns a new interactive
239 python shell.
240 """
235 """Returns an action callback that spawns a new interactive
236 python shell."""
241237 if banner is None:
242238 banner = 'Interactive Werkzeug Shell'
243239 def action(ipython=use_ipython):
260256 def make_runserver(app_factory, hostname='localhost', port=5000,
261257 use_reloader=False, use_debugger=False, use_evalex=True,
262258 threaded=False, processes=1):
263 """
264 Returns an action callback that spawns a new wsgiref server.
265 """
259 """Returns an action callback that spawns a new wsgiref server."""
266260 def action(hostname=('h', hostname), port=('p', port),
267261 reloader=use_reloader, debugger=use_debugger,
268262 evalex=use_evalex, threaded=threaded, processes=processes):
269263 """Start a new development server."""
270264 from werkzeug.serving import run_simple
271265 app = app_factory()
272 if debugger:
273 from werkzeug.debug import DebuggedApplication
274 app = DebuggedApplication(app, evalex)
275 run_simple(hostname, port, app, reloader, None, 1, threaded,
276 processes)
266 run_simple(hostname, port, app, reloader, debugger, evalex,
267 None, 1, threaded, processes)
277268 return action
4646 import time
4747 import thread
4848 from itertools import chain
49 from werkzeug.utils import _log
5049 try:
5150 from wsgiref.simple_server import ServerHandler, WSGIRequestHandler, \
5251 WSGIServer
5453 except ImportError:
5554 have_wsgiref = False
5655 from SocketServer import ThreadingMixIn, ForkingMixIn
56 from werkzeug._internal import _log
5757
5858
5959 if have_wsgiref:
102102
103103 def make_server(host, port, app=None, threaded=False, processes=1,
104104 request_handler=None):
105 """
106 Create a new wsgiref server that is either threaded, or forks
105 """Create a new wsgiref server that is either threaded, or forks
107106 or just processes one request after another.
108107 """
109108 if not have_wsgiref:
121120 elif processes > 1:
122121 class request_handler(request_handler):
123122 multiprocess = True
123 class server(ForkingMixIn, WSGIServer):
124124 max_children = processes - 1
125 class server(ForkingMixIn, WSGIServer):
126 pass
127125 else:
128126 server = WSGIServer
129127 srv = server((host, port), request_handler)
172170
173171
174172 def restart_with_reloader():
175 """
176 Spawn a new Python interpreter with the same arguments as this one,
173 """Spawn a new Python interpreter with the same arguments as this one,
177174 but running the reloader thread.
178175 """
179176 while 1:
189186
190187
191188 def run_with_reloader(main_func, extra_files=None, interval=1):
192 """
193 Run the given function in an independent python interpreter.
194 """
189 """Run the given function in an independent python interpreter."""
195190 if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
196191 thread.start_new_thread(main_func, ())
197192 try:
205200
206201
207202 def run_simple(hostname, port, application, use_reloader=False,
203 use_debugger=False, use_evalex=True,
208204 extra_files=None, reloader_interval=1, threaded=False,
209205 processes=1, request_handler=None):
210 """
211 Start an application using wsgiref and with an optional reloader. This
206 """Start an application using wsgiref and with an optional reloader. This
212207 wraps `wsgiref` to fix the wrong default reporting of the multithreaded
213208 WSGI variable and adds optional multithreading and fork support.
214209
217212 :param application: the WSGI application to execute
218213 :param use_reloader: should the server automatically restart the python
219214 process if modules were changed?
215 :param use_debugger: should the werkzeug debugging system be used?
216 :param use_evalex: should the exception evaluation feature be enabled?
220217 :param extra_files: a list of files the reloader should listen for
221218 additionally to the modules. For example configuration
222219 files.
229226 at the `werkzeug.serving` sourcecode for more
230227 details.
231228 """
229 if use_debugger:
230 from werkzeug.debug import DebuggedApplication
231 application = DebuggedApplication(application, use_evalex)
232
232233 def inner():
233234 srv = make_server(hostname, port, application, threaded,
234235 processes, request_handler)
55 This template engine recognizes ASP/PHP like blocks and executes the code
66 in them::
77
8 t = Template('<% for u in users %>${u['username']}\n<% endfor %>')
8 t = Template('<% for u in users %>${u["username"]}\n<% endfor %>')
99 t.render(users=[{'username': 'John'},
1010 {'username': 'Jane'}])
1111
112112 <% if variable is Undefined %>
113113 ...
114114 <% endif %>
115
116
117 Python 2.3 Compatibility
118 ------------------------
119
120 Because of limitations in Python 2.3 it's impossible to achieve the
121 semi-silent variable lookup fallback. If a template relies on undefined
122 variables it won't execute under Python 2.3.
115123
116124
117125 :copyright: 2006-2008 by Armin Ronacher, Ka-Ping Yee.
125133 from compiler.pycodegen import ModuleCodeGenerator
126134 from tokenize import PseudoToken
127135 from werkzeug import utils
136 from werkzeug._internal import _decode_unicode
137
138 # Anything older than Python 2.4
139 if sys.version_info < (2, 4):
140 class AstMangler(object):
141
142 def __getattr__(self, key):
143 class_ = getattr(_ast, key)
144 def wrapper(*args, **kw):
145 lineno = kw.pop('lineno', None)
146 obj = class_(*args, **kw)
147 obj.lineno = lineno
148 return obj
149 return wrapper
150
151 _ast = ast
152 ast = AstMangler()
128153
129154
130155 # Copyright notice: The `parse_data` method uses the string interpolation
133158 # .. _ltipl20.py: http://lfw.org/python/Itpl20.py
134159
135160
136 token_re = re.compile('%s|%s|%s(?i)' % (
137 r'[uU]?[rR]?"""([^"\\]*(?:\\.[^"\\]*)*)"""',
138 r"[uU]?[rR]?'''([^'\\]*(?:\\.[^'\\]*)*)'''",
161 token_re = re.compile('%s|%s(?s)' % (
162 r'[uU]?[rR]?("""|\'\'\')((?<!\\)\\\1|.)*?\1',
139163 PseudoToken
140164 ))
141165 directive_re = re.compile(r'(?<!\\)<%(?:(#)|(py(?:thon)?\b)|'
321345
322346 def write_data(value):
323347 if value:
324 nodes.append(ast.Const(value, lineno))
348 nodes.append(ast.Const(value, lineno=lineno))
325349 return value.count('\n')
326350 return 0
327351
389413
390414 def to_unicode(self, value):
391415 if isinstance(value, str):
392 return value.decode(self.encoding, self.errors)
416 return _decode_unicode(value, self.encoding, self.errors)
393417 return unicode(value)
394418
395419 def get_value(self, as_unicode=True):
426450
427451
428452 class Template(object):
429 """
430 Represents a simple text based template. It's a good idea to load such
453 """Represents a simple text based template. It's a good idea to load such
431454 templates from files on the file system to get better debug output.
432455 """
433456 default_context = {
440463 def __init__(self, source, filename='<template>', encoding='utf-8',
441464 errors='strict', unicode_mode=True):
442465 if isinstance(source, str):
443 source = source.decode(encoding, errors)
466 source = _decode_unicode(source, encoding, errors)
467 if isinstance(filename, unicode):
468 filename = filename.encode('utf-8')
444469 node = Parser(tokenize(u'\n'.join(source.splitlines()),
445470 filename), filename).parse()
446471 self.code = TemplateCodeGenerator(node, filename).getCode()
457482 f = open(file, 'r')
458483 close = True
459484 try:
460 data = f.read().decode(encoding, errors)
485 data = _decode_unicode(f.read(), encoding, errors)
461486 finally:
462487 if close:
463488 f.close()
466491 from_file = classmethod(from_file)
467492
468493 def render(self, *args, **kwargs):
469 """
470 This function accepts either a dict or some keyword arguments which
494 """This function accepts either a dict or some keyword arguments which
471495 will then be the context the template is evaluated in. The return
472496 value will be the rendered template.
473497 """
474498 ns = self.default_context.copy()
475 ns.update(*args, **kwargs)
499 ns.update(dict(*args, **kwargs))
476500 context = Context(ns, self.encoding, self.errors)
477 exec self.code in context.runtime, context
501 if sys.version_info < (2, 4):
502 exec self.code in context.runtime, ns
503 else:
504 exec self.code in context.runtime, context
478505 return context.get_value(self.unicode_mode)
479506
480507 def substitute(self, *args, **kwargs):
508 """For API compatibility with `string.Template`."""
481509 return self.render(*args, **kwargs)
5555
5656
5757 def encode_multipart(values):
58 """
59 Encode a dict of values (can either be strings or file descriptors)
58 """Encode a dict of values (can either be strings or file descriptors)
6059 into a multipart encoded string. The filename is taken from the `.name`
6160 attribute of the file descriptor. Because StringIOs do not provide
6261 this attribute it will generate a random filename in that case.
7170 if isinstance(value, File):
7271 lines.extend((
7372 '--' + boundary,
74 'Content-Dispotion: form-data; name="%s"; filename="%s"' %
73 'Content-Disposition: form-data; name="%s"; filename="%s"' %
7574 (key, value.filename),
7675 'Content-Type: ' + value.mimetype,
7776 '',
8079 else:
8180 lines.extend((
8281 '--' + boundary,
83 'Content-Dispotion: form-data; name="%s"' % key,
82 'Content-Disposition: form-data; name="%s"' % key,
8483 '',
8584 value
8685 ))
8988
9089
9190 class File(object):
92 """
93 Wraps a file descriptor or any other stream so that `encode_multipart`
91 """Wraps a file descriptor or any other stream so that `encode_multipart`
9492 can get the mimetype and filename from it.
9593 """
9694
125123
126124
127125 class Client(object):
128 """
129 This class allows to send requests to a wrapped application.
130 """
126 """This class allows to send requests to a wrapped application."""
131127
132128 def __init__(self, application, response_wrapper=None):
133 """
134 The response wrapper can be a class or factory function that takes
129 """The response wrapper can be a class or factory function that takes
135130 three arguments: app_iter, status and headers. The default response
136131 wrapper just returns a tuple.
137132
152147 content_length=0, errors_stream=None, multithread=False,
153148 multiprocess=False, run_once=False, environ_overrides=None,
154149 as_tuple=False):
155 """
156 Takes the same arguments as the `create_environ` function from the utility
157 module with some additions.
150 """Takes the same arguments as the `create_environ` function from the
151 utility module with some additions.
158152
159153 The first parameter should be the path of the request which defaults to
160154 '/'. The second one can either be a absolute path (in that case the url
1414
1515
1616 class UserAgentParser(object):
17 """
18 A simple user agent parser. Used by the `UserAgent`.
19 """
17 """A simple user agent parser. Used by the `UserAgent`."""
2018
2119 platforms = (
2220 (r'darwin|mac|os\s*x', 'macos'),
8987
9088
9189 class UserAgent(object):
92 """
93 Represents a user agent. Pass it a WSGI environment or an user agent
90 """Represents a user agent. Pass it a WSGI environment or an user agent
9491 string and you can inspect some of the details from the user agent
9592 string via the attributes. The following attribute exist:
9693
1515 import cgi
1616 import urllib
1717 import urlparse
18 import posixpath
1819 from itertools import chain
19 from Cookie import BaseCookie, Morsel, CookieError
2020 from time import asctime, gmtime, time
21 from datetime import datetime, timedelta
22 from cStringIO import StringIO
21 from datetime import timedelta
2322 try:
2423 set = set
2524 except NameError:
2625 from sets import Set as set
2726 def reversed(item):
2827 return item[::-1]
29
30
31 _empty_stream = StringIO('')
32 _logger = None
28 from werkzeug._internal import _patch_wrapper, _decode_unicode, \
29 _empty_stream, _iter_modules, _ExtendedCookie, _ExtendedMorsel, \
30 _StorageHelper, _DictAccessorProperty, _dump_date, \
31 _parse_signature
32 from werkzeug.http import generate_etag, parse_etags
33
3334
3435 _format_re = re.compile(r'\$(%s|\{%s\})' % (('[a-zA-Z_][a-zA-Z0-9_]*',) * 2))
3536 _entity_re = re.compile(r'&([^;]+);')
3637
3738
38 def _log(type, message, *args, **kwargs):
39 """
40 Log into the internal werkzeug logger.
41
42 :internal:
43 """
44 global _logger
45 if _logger is None:
46 import logging
47 handler = logging.StreamHandler()
48 _logger = logging.getLogger('werkzeug')
49 _logger.addHandler(handler)
50 _logger.setLevel(logging.INFO)
51 getattr(_logger, type)(message.rstrip(), *args, **kwargs)
52
53
54 def _patch_wrapper(old, new):
55 """
56 Helper function that forwards all the function details to the
57 decorated function.
58 """
59 try:
60 new.__name__ = old.__name__
61 new.__module__ = old.__module__
62 new.__doc__ = old.__doc__
63 new.__dict__ = old.__dict__
64 except AttributeError:
65 pass
66 return new
67
68
69 class _ExtendedMorsel(Morsel):
70 """
71 Subclass of regular morsels for simpler usage and support of the
72 nonstandard but useful http only header.
73
74 :internal:
75 """
76 _reserved = {'httponly': 'HttpOnly'}
77 _reserved.update(Morsel._reserved)
78
79 def __init__(self, name=None, value=None):
80 Morsel.__init__(self)
81 if name is not None:
82 self.set(name, value, value)
83
84 def OutputString(self, attrs=None):
85 httponly = self.pop('httponly', False)
86 result = Morsel.OutputString(self, attrs).rstrip('\t ;')
87 if httponly:
88 result += '; HttpOnly'
89 return result
90
91
92 class _ExtendedCookie(BaseCookie):
93 """
94 Form of the base cookie that doesn't raise a `CookieError` for
95 malformed keys. This has the advantage that broken cookies submitted
96 by nonstandard browsers don't cause the cookie to be empty.
97
98 :internal:
99 """
100
101 def _BaseCookie__set(self, key, real_value, coded_value):
102 morsel = self.get(key, _ExtendedMorsel())
103 try:
104 morsel.set(key, real_value, coded_value)
105 except CookieError:
106 pass
107 dict.__setitem__(self, key, morsel)
108
109
11039 class MultiDict(dict):
111 """
112 A `MultiDict` is a dictionary subclass customized to deal with multiple
40 """A `MultiDict` is a dictionary subclass customized to deal with multiple
11341 values for the same key which is for example used by the parsing functions
11442 in the wrappers. This is necessary because some HTML form elements pass
11543 multiple values for the same key.
13462
13563 It behaves like a normal dict thus all dict functions will only return the
13664 first value when multiple values for one key are found.
137 """
65
66 From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a
67 subclass of the `BadRequest` HTTP exception and will render a page for a
68 ``400 BAD REQUEST`` if catched in a catch-all for HTTP exceptions.
69 """
70
71 #: the key error this class raises. Because of circular dependencies
72 #: with the http exception module this class is created at the end of
73 #: this module.
74 KeyError = None
13875
13976 def __init__(self, mapping=()):
140 """
141 A `MultiDict` can be constructed from an iterable of ``(key, value)``
142 tuples, a dict, a `MultiDict` or with Werkzeug 0.2 onwards some
143 keyword parameters.
77 """A `MultiDict` can be constructed from an iterable of
78 ``(key, value)`` tuples, a dict, a `MultiDict` or with Werkzeug 0.2
79 onwards some keyword parameters.
14480 """
14581 if isinstance(mapping, MultiDict):
14682 dict.__init__(self, [(k, v[:]) for k, v in mapping.lists()])
16096 dict.__init__(self, tmp)
16197
16298 def __getitem__(self, key):
163 """
164 Return the first data value for this key;
99 """Return the first data value for this key;
165100 raises KeyError if not found.
166101
167102 :raise KeyError: if the key does not exist
168103 """
169 return dict.__getitem__(self, key)[0]
104 if key in self:
105 return dict.__getitem__(self, key)[0]
106 raise self.KeyError(key)
170107
171108 def __setitem__(self, key, value):
172109 """Set an item as list."""
173110 dict.__setitem__(self, key, [value])
174111
175112 def get(self, key, default=None, type=None):
176 """
177 Return the default value if the requested data doesn't exist.
113 """Return the default value if the requested data doesn't exist.
178114 If `type` is provided and is a callable it should convert the value,
179115 return it or raise a `ValueError` if that is not possible. In this
180116 case the function will return the default as if the value was not
197133 return rv
198134
199135 def getlist(self, key, type=None):
200 """
201 Return the list of items for a given key. If that key is not in the
136 """Return the list of items for a given key. If that key is not in the
202137 `MultiDict`, the return value will be an empty list. Just as `get`
203138 `getlist` accepts a `type` parameter. All items will be converted
204139 with the callable defined there.
220155 return result
221156
222157 def setlist(self, key, new_list):
223 """
224 Remove the old values for a key and add new ones. Note that the list
158 """Remove the old values for a key and add new ones. Note that the list
225159 you pass the values in will be shallow-copied before it is inserted in
226160 the dictionary.
227161
250184 return default_list
251185
252186 def items(self):
253 """
254 Return a list of (key, value) pairs, where value is the last item in
255 the list associated with the key.
187 """Return a list of (key, value) pairs, where value is the last item
188 in the list associated with the key.
256189 """
257190 return [(key, self[key]) for key in self.iterkeys()]
258191
281214 return self.__class__(self)
282215
283216 def to_dict(self, flat=True):
284 """
285 Return the contents as regular dict. If `flat` is `True` the returned
286 dict will only have the first item present, if `flat` is `False` all
287 values will be returned as lists.
217 """Return the contents as regular dict. If `flat` is `True` the
218 returned dict will only have the first item present, if `flat` is
219 `False` all values will be returned as lists.
288220
289221 :return: dict
290222 """
325257
326258
327259 class CombinedMultiDict(MultiDict):
328 """
329 A read only `MultiDict` decorator that you can pass multiple `MultiDict`
260 """A read only `MultiDict` decorator that you can pass multiple `MultiDict`
330261 instances as sequence and it will combine the return values of all wrapped
331262 dicts:
332263
341272
342273 This works for all read operations and will raise a `TypeError` for
343274 methods that usually change data which isn't possible.
275
276 From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a
277 subclass of the `BadRequest` HTTP exception and will render a page for a
278 ``400 BAD REQUEST`` if catched in a catch-all for HTTP exceptions.
344279 """
345280
346281 def __init__(self, dicts=None):
355290 for d in self.dicts:
356291 if key in d:
357292 return d[key]
358 raise KeyError(key)
293 raise self.KeyError(key)
359294
360295 def get(self, key, default=None, type=None):
361296 for d in self.dicts:
426361 return self.__class__(self.dicts[:])
427362
428363 def to_dict(self, flat=True):
429 """
430 Returns the contents as simple dict. If `flat` is `True` the
364 """Returns the contents as simple dict. If `flat` is `True` the
431365 resulting dict will only have the first item present, if `flat`
432366 is `False` all values will be lists.
433367 """
460394
461395
462396 class FileStorage(object):
463 """
464 The `FileStorage` object is a thin wrapper over incoming files. It is
397 """The `FileStorage` object is a thin wrapper over incoming files. It is
465398 used by the request object to represent uploaded files. All the
466399 attributes of the wrapper stream are proxied by the file storage so
467400 it's possible to do ``storage.read()`` instead of the long form
470403
471404 def __init__(self, stream=None, filename=None, name=None,
472405 content_type='application/octet-stream', content_length=-1):
473 """
474 Creates a new `FileStorage` object. The constructor looked different
475 for Werkzeug 0.1 but there the object was only used internally.
406 """Creates a new `FileStorage` object.
476407
477408 :param stream: the input stream for uploaded file. Usually this
478409 points to a temporary file.
488419 self.content_length = content_length
489420
490421 def save(self, dst, buffer_size=16384):
491 """
492 Save the file to a destination path or file object. If the
422 """Save the file to a destination path or file object. If the
493423 destination is a file object you have to close it yourself after the
494424 call. The buffer size is the number of bytes held in the memory
495425 during the copy process. It defaults to 16KB.
496426 """
497427 from shutil import copyfileobj
428 close_dst = False
498429 if isinstance(dst, basestring):
499430 dst = file(dst, 'wb')
500431 close_dst = True
501 else:
502 close_dst = False
503
504432 try:
505433 copyfileobj(self.stream, dst, buffer_size)
506434 finally:
528456
529457
530458 class Headers(object):
531 """
532 An object that stores some headers. It has a dict like interface
459 """An object that stores some headers. It has a dict like interface
533460 but is ordered and can store keys multiple times.
534461
535462 This data structure is useful if you want a nicer way to handle WSGI
536463 headers which are stored as tuples in a list.
537 """
464
465 From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a
466 subclass of the `BadRequest` HTTP exception and will render a page for a
467 ``400 BAD REQUEST`` if catched in a catch-all for HTTP exceptions.
468 """
469
470 #: the key error this class raises. Because of circular dependencies
471 #: with the http exception module this class is created at the end of
472 #: this module.
473 KeyError = None
538474
539475 def __init__(self, defaults=None, _list=None):
540 """
541 Create a new `Headers` object based on a list or dict of headers which
542 are used as default values. This does not reuse the list passed to
543 the constructor for internal usage. To create a `Headers` object that
544 uses as internal storage the list or list-like object provided it's
545 possible to use the `linked` classmethod.
476 """Create a new `Headers` object based on a list or dict of headers
477 which are used as default values. This does not reuse the list passed
478 to the constructor for internal usage. To create a `Headers` object
479 that uses as internal storage the list or list-like object provided
480 it's possible to use the `linked` classmethod.
546481 """
547482 if _list is None:
548483 _list = []
558493 self._list[:] = defaults
559494
560495 def linked(cls, headerlist):
561 """
562 Create a new `Headers` object that uses the list of headers passed as
563 internal storage:
496 """Create a new `Headers` object that uses the list of headers passed
497 as internal storage:
564498
565499 >>> headerlist = [('Content-Length', '40')]
566500 >>> headers = Headers.linked(headerlist)
578512 for k, v in self._list:
579513 if k.lower() == ikey:
580514 return v
581 raise KeyError(key)
515 raise self.KeyError(key)
582516
583517 def __eq__(self, other):
584518 return other.__class__ is self.__class__ and \
588522 return not self.__eq__(other)
589523
590524 def get(self, key, default=None, type=None):
591 """
592 Return the default value if the requested data doesn't exist.
525 """Return the default value if the requested data doesn't exist.
593526 If `type` is provided and is a callable it should convert the value,
594527 return it or raise a `ValueError` if that is not possible. In this
595528 case the function will return the default as if the value was not
616549 return default
617550
618551 def getlist(self, key, type=None):
619 """
620 Return the list of items for a given key. If that key is not in the
552 """Return the list of items for a given key. If that key is not in the
621553 `MultiDict`, the return value will be an empty list. Just as `get`
622554 `getlist` accepts a `type` parameter. All items will be converted
623555 with the callable defined there.
660592 return list(self.iteritems(lower))
661593
662594 def extend(self, iterable):
663 """
664 Extend the headers with a dict or an iterable yielding keys and
595 """Extend the headers with a dict or an iterable yielding keys and
665596 values.
666597 """
667598 if isinstance(iterable, dict):
705636 """remove all header tuples for key and add
706637 a new one
707638 """
708 self.remove(key)
639 lc_key = key.lower()
640 for idx, (old_key, old_value) in enumerate(self._list):
641 if old_key.lower() == lc_key:
642 self._list[idx] = (key, value)
643 return
709644 self.add(key, value)
710645
711646 __setitem__ = set
712647
713648 def to_list(self, charset='utf-8'):
714 """
715 Convert the headers into a list and converts the unicode header items
716 to the specified charset.
649 """Convert the headers into a list and converts the unicode header
650 items to the specified charset.
717651
718652 :return: list
719653 """
740674
741675
742676 class EnvironHeaders(Headers):
743 """
744 Read only version of the headers from a WSGI environment. This
677 """Read only version of the headers from a WSGI environment. This
745678 provides the same interface as `Headers` and is constructed from
746679 a WSGI environment.
680
681 From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a
682 subclass of the `BadRequest` HTTP exception and will render a page for a
683 ``400 BAD REQUEST`` if catched in a catch-all for HTTP exceptions.
747684 """
748685
749686 def __init__(self, environ):
758695 return self is other
759696
760697 def __getitem__(self, key):
761 return self.environ['HTTP_' + key.upper().replace('-', '_')]
698 key = key.upper().replace('-', '_')
699 if key in ('CONTENT_TYPE', 'CONTENT_LENGTH'):
700 return self.environ[key]
701 return self.environ['HTTP_' + key]
762702
763703 def __iter__(self):
764704 for key, value in self.environ.iteritems():
765705 if key.startswith('HTTP_'):
766706 yield key[5:].replace('_', '-').title(), value
707 elif key in ('CONTENT_TYPE', 'CONTENT_LENGTH'):
708 yield key.replace('_', '-').title(), value
767709
768710 def copy(self):
769711 raise TypeError('cannot create %r copies' % self.__class__.__name__)
776718
777719
778720 class SharedDataMiddleware(object):
779 """
780 A WSGI middleware that provides static content for development
721 """A WSGI middleware that provides static content for development
781722 environments or simple server setups. Usage is quite simple::
782723
783724 import os
805746 python package.
806747 """
807748
808 def __init__(self, app, exports, disallow=None):
749 def __init__(self, app, exports, disallow=None, cache=True):
809750 self.app = app
810751 self.exports = {}
752 self.cache = cache
811753 for key, value in exports.iteritems():
812754 if isinstance(value, tuple):
813755 loader = self.get_package_loader(*value)
827769 return True
828770
829771 def get_file_loader(self, filename):
830 return lambda x: os.path.basename(filename), \
831 lambda: open(filename, 'rb')
772 return lambda x: (os.path.basename(filename), \
773 lambda: open(filename, 'rb'))
832774
833775 def get_package_loader(self, package, package_path):
834776 from pkg_resources import resource_exists, resource_stream
859801 stream_maker = None
860802 for search_path, loader in self.exports.iteritems():
861803 if search_path == path:
862 real_filename, stream_maker = loader.load(None)
804 real_filename, stream_maker = loader(None)
863805 if stream_maker is not None:
864806 break
865807 if not search_path.endswith('/'):
874816 guessed_type = guess_type(real_filename)
875817 mime_type = guessed_type[0] or 'text/plain'
876818 expiry = asctime(gmtime(time() + 3600))
877 start_response('200 OK', [('Content-Type', mime_type),
878 ('Cache-Control', 'public'),
879 ('Expires', expiry)])
880819 stream = stream_maker()
881820 try:
882 return [stream.read()]
821 data = stream.read()
883822 finally:
884823 stream.close()
824 headers = [('Content-Type', mime_type), ('Cache-Control', 'public')]
825 if self.cache:
826 etag = generate_etag(data)
827 headers += [('Expires', expiry), ('ETag', etag)]
828 if parse_etags(environ.get('HTTP_IF_NONE_MATCH')).contains(etag):
829 start_response('304 Not Modified', headers)
830 return []
831
832 start_response('200 OK', headers)
833 return [data]
885834
886835
887836 class DispatcherMiddleware(object):
888 """
889 Allows one to mount middlewares or application in a WSGI application.
837 """Allows one to mount middlewares or application in a WSGI application.
890838 This is useful if you want to combine multiple WSGI applications::
891839
892840 app = DispatcherMiddleware(app, {
918866
919867
920868 class ClosingIterator(object):
921 """
922 The WSGI specification requires that all middlewares and gateways respect the
923 `close` callback of an iterator. Because it is useful to add another close
924 action to a returned iterator and adding a custom iterator is a boring task
925 this class can be used for that::
869 """The WSGI specification requires that all middlewares and gateways
870 respect the `close` callback of an iterator. Because it is useful to add
871 another close action to a returned iterator and adding a custom iterator
872 is a boring task this class can be used for that::
926873
927874 return ClosingIterator(app(environ, start_response), [cleanup_session,
928875 cleanup_locals])
965912
966913
967914 class Href(object):
968 """
969 Implements a callable that constructs URLs with the given base. The
915 """Implements a callable that constructs URLs with the given base. The
970916 function can be called with any number of positional and keyword
971917 arguments which than are used to assemble the URL. Works with URLs
972918 and posix paths.
1031977
1032978
1033979 class cached_property(object):
1034 """
1035 A decorator that converts a function into a lazy property. The
980 """A decorator that converts a function into a lazy property. The
1036981 function wrapped is called the first time to retrieve the result
1037982 and than that calculated result is used the next time you access
1038983 the value::
10581003 return value
10591004
10601005
1061 def lazy_property(func, name=None, doc=None):
1062 """Backwards compatibility interface."""
1063 from warnings import warn
1064 warn(DeprecationWarning('lazy_property is now called cached_property '
1065 'because it reflects the purpose better. With '
1066 'Werkzeug 0.3 the old name will be unavailable'))
1067 return cached_property(func, name, doc)
1068
1069
1070 class _DictAccessorProperty(object):
1071 """
1072 Baseclass for `environ_property` and `header_property`.
1073
1074 :internal:
1075 """
1076
1077 def __init__(self, name, default=None, load_func=None, dump_func=None,
1078 read_only=False, doc=None):
1079 self.name = name
1080 self.default = default
1081 self.load_func = load_func
1082 self.dump_func = dump_func
1083 self.read_only = read_only
1084 self.__doc__ = doc
1085
1086 def lookup(self):
1087 raise NotImplementedError()
1088
1089 def __get__(self, obj, type=None):
1090 if obj is None:
1091 return self
1092 storage = self.lookup(obj)
1093 if self.name not in storage:
1094 return self.default
1095 rv = storage[self.name]
1096 if self.load_func is not None:
1097 try:
1098 rv = self.load_func(rv)
1099 except (ValueError, TypeError):
1100 rv = self.default
1101 return rv
1102
1103 def __set__(self, obj, value):
1104 if self.read_only:
1105 raise AttributeError('read only property')
1106 if self.dump_func is not None:
1107 value = self.dump_func(value)
1108 self.lookup(obj)[self.name] = value
1109
1110 def __delete__(self, obj):
1111 if self.read_only:
1112 raise AttributeError('read only property')
1113 self.lookup(obj).pop(self.name, None)
1114
1115 def __repr__(self):
1116 return '<%s %s>' % (
1117 self.__class__.__name__,
1118 self.name
1119 )
1120
1121
11221006 class environ_property(_DictAccessorProperty):
1123 """
1124 Maps request attributes to environment variables. This works not only
1007 """Maps request attributes to environment variables. This works not only
11251008 for the Werzeug request object, but also any other class with an
11261009 environ attribute:
11271010
11371020 it. If it raises `ValueError` or `TypeError` the default value is used.
11381021 If no default value is provided `None` is used.
11391022
1140 Per default the property works in two directions, but if you set
1141 `read_only` to False it will block set/delete.
1142 """
1023 Per default the property is read only. You have to explicitly enable it
1024 by passing ``read_only=False`` to the constructor.
1025 """
1026
1027 read_only = True
11431028
11441029 def lookup(self, obj):
11451030 return obj.environ
11461031
11471032
11481033 class header_property(_DictAccessorProperty):
1149 """
1150 Like `environ_property` but for headers.
1151 """
1034 """Like `environ_property` but for headers."""
11521035
11531036 def lookup(self, obj):
11541037 return obj.headers
11551038
11561039
11571040 class HTMLBuilder(object):
1158 """
1159 Helper object for HTML generation.
1041 """Helper object for HTML generation.
11601042
11611043 Per default there are two instances of that class. The `html` one, and
11621044 the `xhtml` one for those two dialects. The class uses keyword parameters
12451127 xhtml = HTMLBuilder('xhtml')
12461128
12471129
1130 def parse_form_data(environ, stream_factory=None, charset='utf-8',
1131 errors='ignore'):
1132 """Parse the form data in the environ and return it as tuple in the form
1133 ``(stream, form, files)``. You should only call this method if the
1134 transport method is `POST` or `PUT`.
1135
1136 If the mimetype of the data transmitted is `multipart/form-data` the
1137 files multidict will be filled with `FileStorage` objects. If the
1138 mimetype is unknow the input stream is wrapped and returned as first
1139 argument, else the stream is empty.
1140 """
1141 stream = _empty_stream
1142 form = []
1143 files = []
1144 storage = _StorageHelper(environ, stream_factory)
1145 if storage.file:
1146 stream = storage.file
1147 if storage.list is not None:
1148 for key in storage.keys():
1149 values = storage[key]
1150 if not isinstance(values, list):
1151 values = [values]
1152 for item in values:
1153 if getattr(item, 'filename', None) is not None:
1154 fn = _decode_unicode(item.filename, charset, errors)
1155 # fix stupid IE bug (IE6 sends the whole path)
1156 if fn[1:3] == ':\\' or fn[:2] == '\\\\':
1157 fn = fn.split('\\')[-1]
1158 files.append((key, FileStorage(item.file, fn, key,
1159 item.type, item.length)))
1160 else:
1161 form.append((key, _decode_unicode(item.value,
1162 charset, errors)))
1163 return stream, MultiDict(form), MultiDict(files)
1164
1165
12481166 def get_content_type(mimetype, charset):
1249 """
1250 Return the full content type string with charset for a mimetype.
1167 """Return the full content type string with charset for a mimetype.
12511168
12521169 If the mimetype represents text the charset will be appended as charset
12531170 parameter, otherwise the mimetype is returned unchanged.
12611178
12621179
12631180 def format_string(string, context):
1264 """
1265 String-template format a string::
1181 """String-template format a string::
12661182
12671183 >>> format_string('$foo and ${foo}s', dict(foo=42))
12681184 '42 and 42s'
12781194 return _format_re.sub(lookup_arg, string)
12791195
12801196
1281 def url_decode(s, charset='utf-8', decode_keys=False, include_empty=True):
1282 """
1283 Parse a querystring and return it as `MultiDict`. Per default only values
1284 are decoded into unicode strings. If `decode_keys` is set to ``True`` the
1285 same will happen for keys.
1197 def url_decode(s, charset='utf-8', decode_keys=False, include_empty=True,
1198 errors='ignore'):
1199 """Parse a querystring and return it as `MultiDict`. Per default only
1200 values are decoded into unicode strings. If `decode_keys` is set to
1201 ``True`` the same will happen for keys.
12861202
12871203 Per default a missing value for a key will default to an empty key. If
12881204 you don't want that behavior you can set `include_empty` to `False`.
1205
1206 Per default encoding errors are ignore. If you want a different behavior
1207 you can set `errors` to ``'replace'`` or ``'strict'``. In strict mode a
1208 `HTTPUnicodeError` is raised.
12891209 """
12901210 tmp = []
12911211 for key, values in cgi.parse_qs(str(s), include_empty).iteritems():
12921212 for value in values:
12931213 if decode_keys:
1294 key = key.decode(charset, 'ignore')
1295 tmp.append((key, value.decode(charset, 'ignore')))
1214 key = _decode_unicode(key, charset, errors)
1215 tmp.append((key, _decode_unicode(value, charset, errors)))
12961216 return MultiDict(tmp)
12971217
12981218
12991219 def url_encode(obj, charset='utf-8', encode_keys=False):
1300 """
1301 URL encode a dict/`MultiDict`. If a value is `None` it will not appear in
1302 the result string. Per default only values are encoded into the target
1220 """URL encode a dict/`MultiDict`. If a value is `None` it will not appear
1221 in the result string. Per default only values are encoded into the target
13031222 charset strings. If `encode_keys` is set to ``True`` unicode keys are
13041223 supported too.
13051224 """
1306 if obj is None:
1307 items = []
1308 elif isinstance(obj, MultiDict):
1225 if isinstance(obj, MultiDict):
13091226 items = obj.lists()
13101227 elif isinstance(obj, dict):
1311 items = [(key, [value]) for key, value in obj.iteritems()]
1228 items = []
1229 for key, value in obj.iteritems():
1230 if not isinstance(value, (tuple, list)):
1231 value = [value]
1232 items.append((key, value))
13121233 else:
1313 items = obj
1234 items = obj or ()
13141235 tmp = []
13151236 for key, values in items:
13161237 if encode_keys and isinstance(key, unicode):
13301251
13311252
13321253 def url_quote(s, charset='utf-8', safe='/:'):
1333 """
1334 URL encode a single string with a given encoding.
1335 """
1254 """URL encode a single string with a given encoding."""
13361255 if isinstance(s, unicode):
13371256 s = s.encode(charset)
13381257 elif not isinstance(s, str):
13411260
13421261
13431262 def url_quote_plus(s, charset='utf-8', safe=''):
1344 """
1345 URL encode a single string with the given encoding and convert
1263 """URL encode a single string with the given encoding and convert
13461264 whitespace to "+".
13471265 """
13481266 if isinstance(s, unicode):
13521270 return urllib.quote_plus(s, safe=safe)
13531271
13541272
1355 def url_unquote(s, charset='utf-8'):
1356 """
1357 URL decode a single string with a given decoding.
1358 """
1359 return urllib.unquote(s).decode(charset, 'ignore')
1360
1361
1362 def url_unquote_plus(s, charset='utf-8'):
1363 """
1364 URL decode a single string with the given decoding and decode
1273 def url_unquote(s, charset='utf-8', errors='ignore'):
1274 """URL decode a single string with a given decoding.
1275
1276 Per default encoding errors are ignore. If you want a different behavior
1277 you can set `errors` to ``'replace'`` or ``'strict'``. In strict mode a
1278 `HTTPUnicodeError` is raised.
1279 """
1280 return _decode_unicode(urllib.unquote(s), charset, errors)
1281
1282
1283 def url_unquote_plus(s, charset='utf-8', errors='ignore'):
1284 """URL decode a single string with the given decoding and decode
13651285 a "+" to whitespace.
1366 """
1367 return urllib.unquote_plus(s).decode(charset, 'ignore')
1286
1287 Per default encoding errors are ignore. If you want a different behavior
1288 you can set `errors` to ``'replace'`` or ``'strict'``. In strict mode a
1289 `HTTPUnicodeError` is raised.
1290 """
1291 return _decode_unicode(urllib.unquote_plus(s), charset, errors)
13681292
13691293
13701294 def url_fix(s, charset='utf-8'):
1371 """
1372 Sometimes you get an URL by a user that just isn't a real URL because
1295 """Sometimes you get an URL by a user that just isn't a real URL because
13731296 it contains unsafe characters like ' ' and so on. This function can fix
13741297 some of the problems in a similar way browsers handle data entered by the
13751298 user:
13881311 return urlparse.urlunsplit((scheme, netloc, path, qs, anchor))
13891312
13901313
1391 def escape(s, quote=None):
1392 """
1393 Replace special characters "&", "<" and ">" to HTML-safe sequences. If the
1394 optional flag `quote` is `True`, the quotation mark character (") is also
1395 translated.
1314 def escape(s, quote=False):
1315 """Replace special characters "&", "<" and ">" to HTML-safe sequences. If
1316 the optional flag `quote` is `True`, the quotation mark character (") is
1317 also translated.
13961318
13971319 There is a special handling for `None` which escapes to an empty string.
13981320 """
13991321 if s is None:
14001322 return ''
1323 elif hasattr(s, '__html__'):
1324 return s.__html__()
14011325 elif not isinstance(s, basestring):
14021326 s = unicode(s)
14031327 s = s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
14071331
14081332
14091333 def unescape(s):
1410 """
1411 The reverse function of `escape`. This unescapes all the HTML entities,
1412 not only the XML entities inserted by `escape`.
1334 """The reverse function of `escape`. This unescapes all the HTML
1335 entities, not only the XML entities inserted by `escape`.
14131336 """
14141337 def handle_match(m):
14151338 name = m.group(1)
14161339 if name in HTMLBuilder._entities:
14171340 return unichr(HTMLBuilder._entities[name])
1418 if name[:2] in ('#x', '#X'):
1419 try:
1341 try:
1342 if name[:2] in ('#x', '#X'):
14201343 return unichr(int(name[2:], 16))
1421 except ValueError:
1422 return u''
1423 elif name.startswith('#'):
1424 try:
1344 elif name.startswith('#'):
14251345 return unichr(int(name[1:]))
1426 except ValueError:
1427 return u''
1346 except ValueError:
1347 pass
14281348 return u''
14291349 return _entity_re.sub(handle_match, s)
14301350
14311351
14321352 def get_host(environ):
1433 """
1434 Return the real host for the given WSGI enviornment. This takes care
1353 """Return the real host for the given WSGI enviornment. This takes care
14351354 of the `X-Forwarded-Host` header.
14361355 """
14371356 if 'HTTP_X_FORWARDED_HOST' in environ:
14471366
14481367 def get_current_url(environ, root_only=False, strip_querystring=False,
14491368 host_only=False):
1450 """
1451 A handy helper function that recreates the full URL for the current
1369 """A handy helper function that recreates the full URL for the current
14521370 request or parts of it. Here an example:
14531371
14541372 >>> env = create_environ("/?param=foo", "http://localhost/script")
14611379 >>> get_current_url(env, strip_querystring=True)
14621380 'http://localhost/script/'
14631381 """
1464 tmp = [environ['wsgi.url_scheme'], '://']
1382 tmp = [environ['wsgi.url_scheme'], '://', get_host(environ)]
14651383 cat = tmp.append
1466 cat(get_host(environ))
1467
14681384 if host_only:
14691385 return ''.join(tmp) + '/'
1470
14711386 cat(urllib.quote(environ.get('SCRIPT_NAME', '').rstrip('/')))
14721387 if root_only:
14731388 cat('/')
14741389 else:
1475 cat(urllib.quote('/' + environ.get('PATH_INFO', '') \
1476 .lstrip('/')))
1477
1390 cat(urllib.quote('/' + environ.get('PATH_INFO', '').lstrip('/')))
14781391 if not strip_querystring:
14791392 qs = environ.get('QUERY_STRING')
14801393 if qs:
14811394 cat('?' + qs)
1482
14831395 return ''.join(tmp)
14841396
14851397
1486 def cookie_date(expires=None, _date_delim='-'):
1487 """
1488 Formats the time to ensure compatibility with Netscape's cookie standard.
1398 def cookie_date(expires=None):
1399 """Formats the time to ensure compatibility with Netscape's cookie
1400 standard.
14891401
14901402 Accepts a floating point number expressed in seconds since the epoc in, a
14911403 datetime object or a timetuple. All times in UTC. The `parse_date`
14931405
14941406 Outputs a string in the format ``Wdy, DD-Mon-YYYY HH:MM:SS GMT``.
14951407 """
1496 if expires is None:
1497 expires = gmtime()
1498 elif isinstance(expires, datetime):
1499 expires = expires.utctimetuple()
1500 elif isinstance(expires, (int, long, float)):
1501 expires = gmtime(expires)
1502
1503 return '%s, %02d%s%s%s%s %02d:%02d:%02d GMT' % (
1504 ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')[expires.tm_wday],
1505 expires.tm_mday,
1506 _date_delim,
1507 ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
1508 'Oct', 'Nov', 'Dec')[expires.tm_mon - 1],
1509 _date_delim,
1510 str(expires.tm_year),
1511 expires.tm_hour,
1512 expires.tm_min,
1513 expires.tm_sec
1514 )
1515
1516
1517 def parse_cookie(header, charset='utf-8'):
1518 """Parse a cookie. Either from a string or WSGI environ."""
1408 return _dump_date(expires, '-')
1409
1410
1411 def parse_cookie(header, charset='utf-8', errors='ignore'):
1412 """Parse a cookie. Either from a string or WSGI environ.
1413
1414 Per default encoding errors are ignore. If you want a different behavior
1415 you can set `errors` to ``'replace'`` or ``'strict'``. In strict mode a
1416 `HTTPUnicodeError` is raised.
1417 """
15191418 if isinstance(header, dict):
15201419 header = header.get('HTTP_COOKIE', '')
15211420 cookie = _ExtendedCookie()
15271426 # `None` items which we have to skip here.
15281427 for key, value in cookie.iteritems():
15291428 if value.value is not None:
1530 result[key] = value.value.decode(charset, 'ignore')
1429 result[key] = _decode_unicode(value.value, charset, errors)
15311430
15321431 return result
15331432
15351434 def dump_cookie(key, value='', max_age=None, expires=None, path='/',
15361435 domain=None, secure=None, httponly=False, charset='utf-8',
15371436 sync_expires=True):
1538 """
1539 Creates a new Set-Cookie header without the ``Set-Cookie`` prefix
1437 """Creates a new Set-Cookie header without the ``Set-Cookie`` prefix
15401438 The parameters are the same as in the cookie Morsel object in the
15411439 Python standard library but it accepts unicode data too.
15421440
15831481
15841482
15851483 def http_date(timestamp=None):
1586 """
1587 Formats the time to match the RFC1123 date format.
1484 """Formats the time to match the RFC1123 date format.
15881485
15891486 Accepts a floating point number expressed in seconds since the epoc in, a
15901487 datetime object or a timetuple. All times in UTC. The `parse_date`
15921489
15931490 Outputs a string in the format ``Wdy, DD Mon YYYY HH:MM:SS GMT``.
15941491 """
1595 return cookie_date(timestamp, ' ')
1492 return _dump_date(timestamp, ' ')
15961493
15971494
15981495 def redirect(location, code=302):
1599 """
1600 Return a response object (a WSGI application) that, if called, redirects
1601 the client to the target location. Supported codes are 301, 302, 303,
1602 305, and 307. 300 is not supported because it's not a real redirect and
1603 304 because it's the answer for a request with a request with defined
1604 If-Modified-Since headers.
1496 """Return a response object (a WSGI application) that, if called,
1497 redirects the client to the target location. Supported codes are 301,
1498 302, 303, 305, and 307. 300 is not supported because it's not a real
1499 redirect and 304 because it's the answer for a request with a request
1500 with defined If-Modified-Since headers.
16051501 """
16061502 assert code in (301, 302, 303, 305, 307)
16071503 from werkzeug.wrappers import BaseResponse
16171513
16181514
16191515 def append_slash_redirect(environ, code=301):
1620 """
1621 Redirect to the same URL but with a slash appended. The behavior
1516 """Redirect to the same URL but with a slash appended. The behavior
16221517 of this function is undefined if the path ends with a slash already.
16231518 """
16241519 new_path = environ['PATH_INFO'].strip('/') + '/'
16311526
16321527
16331528 def responder(f):
1634 """
1635 Marks a function as responder. Decorate a function with it and it
1529 """Marks a function as responder. Decorate a function with it and it
16361530 will automatically call the return value as WSGI application.
16371531
16381532 Example::
16451539
16461540
16471541 def import_string(import_name, silent=False):
1648 """
1649 Imports an object based on a string. This use useful if you want to use
1650 import paths as endpoints or something similar. An import path can
1542 """Imports an object based on a string. This use useful if you want to
1543 use import paths as endpoints or something similar. An import path can
16511544 be specified either in dotted notation (``xml.sax.saxutils.escape``)
16521545 or with a colon as object delimiter (``xml.sax.saxutils:escape``).
16531546
16711564 raise
16721565
16731566
1674 def _iter_modules(path):
1675 import pkgutil
1676 if hasattr(pkgutil, 'iter_modules'):
1677 for importer, modname, ispkg in pkgutil.iter_modules(path):
1678 yield modname, ispkg
1679 return
1680 from inspect import getmodulename
1681 from pydoc import ispackage
1682 found = set()
1683 for path in path:
1684 for filename in os.listdir(path):
1685 p = os.path.join(path, filename)
1686 modname = getmodulename(filename)
1687 if modname and modname != '__init__':
1688 if modname not in found:
1689 found.add(modname)
1690 yield modname, ispackage(modname)
1691
1692
16931567 def find_modules(import_path, include_packages=False, recursive=False):
1694 """
1695 Find all the modules below a package. This can be useful to automatically
1696 import all views / controllers so that their metaclasses / function
1697 decorators have a chance to register themselves on the application.
1568 """Find all the modules below a package. This can be useful to
1569 automatically import all views / controllers so that their metaclasses /
1570 function decorators have a chance to register themselves on the
1571 application.
16981572
16991573 Packages are not returned unless `include_packages` is `True`. This can
17001574 also recursively list modules but in that case it will import all the
17231597 input_stream=None, content_type=None, content_length=0,
17241598 errors_stream=None, multithread=False,
17251599 multiprocess=False, run_once=False):
1726 """
1727 Create a new WSGI environ dict based on the values passed. The first
1728 parameter should be the path of the request which defaults to '/'.
1729 The second one can either be a absolute path (in that case the URL
1730 host is localhost:80) or a full path to the request with scheme,
1731 netloc port and the path to the script.
1600 """Create a new WSGI environ dict based on the values passed. The first
1601 parameter should be the path of the request which defaults to '/'. The
1602 second one can either be a absolute path (in that case the host is
1603 localhost:80) or a full path to the request with scheme, netloc port and
1604 the path to the script.
17321605
17331606 If the `path` contains a query string it will be used, even if the
17341607 `query_string` parameter was given. If it does not contain one
18091682
18101683
18111684 def run_wsgi_app(app, environ, buffered=False):
1812 """
1813 Return a tuple in the form (app_iter, status, headers) of the application
1814 output. This works best if you pass it an application that returns a
1815 iterator all the time.
1685 """Return a tuple in the form (app_iter, status, headers) of the
1686 application output. This works best if you pass it an application that
1687 returns a iterator all the time.
18161688
18171689 Sometimes applications may use the `write()` callable returned
18181690 by the `start_response` function. This tries to resolve such edge
18571729 app_iter = ClosingIterator(app_iter, close_func)
18581730
18591731 return app_iter, response[0], response[1]
1732
1733
1734 def validate_arguments(func, args, kwargs, drop_extra=True):
1735 """Check if the function accepts the arguments and keyword arguments.
1736 Returns a new ``(args, kwargs)`` tuple that can savely be passed to
1737 the function without causing a `TypeError` because the function signature
1738 is incompatible. If `drop_extra` is set to `True` (which is the default)
1739 any extra positional or keyword arguments are dropped automatically.
1740
1741 The exception raised provides three attributes:
1742
1743 `missing`
1744 A set of argument names that the function expected but where
1745 missing.
1746
1747 `extra`
1748 A dict of keyword arguments that the function can not handle but
1749 where provided.
1750
1751 `extra_positional`
1752 A list of values that where given by positional argument but the
1753 function cannot accept.
1754
1755 This can be useful for decorators that forward user submitted data to
1756 a view function::
1757
1758 from werkzeug import ArgumentValidationError, validate_arguments
1759
1760 def sanitize(f):
1761 def proxy(request):
1762 data = request.values.to_dict()
1763 try:
1764 args, kwargs = validate_arguments(f, (request,), data)
1765 except ArgumentValidationError:
1766 raise BadRequest('The browser failed to transmit all '
1767 'the data expected.')
1768 return f(*args, **kwargs)
1769 return proxy
1770 """
1771 parser = _parse_signature(func)
1772 args, kwargs, missing, extra, extra_positional = parser(args, kwargs)[:5]
1773 if missing:
1774 raise ArgumentValidationError(tuple(missing))
1775 elif (extra or extra_positional) and not drop_extra:
1776 raise ArgumentValidationError(None, extra, extra_positional)
1777 return tuple(args), kwargs
1778
1779
1780 def bind_arguments(func, args, kwargs):
1781 """Bind the arguments provided into a dict. When passed a function,
1782 a tuple of arguments and a dict of keyword arguments `bind_arguments`
1783 returns a dict of names as the function would see it. This can be useful
1784 to implement a cache decorator that uses the function arguments to build
1785 the cache key based on the values of the arguments.
1786 """
1787 args, kwargs, missing, extra, extra_positional, \
1788 arg_spec, vararg_var, kwarg_var = _parse_signature(func)(args, kwargs)
1789 values = {}
1790 for (name, has_default, default), value in zip(arg_spec, args):
1791 values[name] = value
1792 if vararg_var is not None:
1793 values[vararg_var] = tuple(extra_positional)
1794 elif extra_positional:
1795 raise TypeError('too many positional arguments')
1796 if kwarg_var is not None:
1797 multikw = set(extra) & set([x[0] for x in arg_spec])
1798 if multikw:
1799 raise TypeError('got multiple values for keyword argument ' +
1800 repr(iter(multikw).next()))
1801 values[kwarg_var] = extra
1802 elif extra:
1803 raise TypeError('got unexpected keyword argument ' +
1804 repr(iter(extra).next()))
1805 return values
1806
1807
1808 class ArgumentValidationError(ValueError):
1809 """Raised if `validate_arguments` fails to validate"""
1810
1811 def __init__(self, missing=None, extra=None, extra_positional=None):
1812 self.missing = set(missing or ())
1813 self.extra = extra or {}
1814 self.extra_positional = extra_positional or []
1815 ValueError.__init__(self, 'function arguments invalid. ('
1816 '%d missing, %d additional)' % (
1817 len(self.missing),
1818 len(self.extra) + len(self.extra_positional)
1819 ))
1820
1821
1822 # create all the special key errors now that the classes are defined.
1823 from werkzeug.exceptions import BadRequest
1824 for _cls in MultiDict, CombinedMultiDict, Headers, EnvironHeaders:
1825 _cls.KeyError = BadRequest.wrap(KeyError, _cls.__name__ + '.KeyError')
1826 del BadRequest, _cls
1919 :copyright: 2007-2008 by Armin Ronacher, Georg Brandl.
2020 :license: BSD, see LICENSE for more details.
2121 """
22 import cgi
2322 import tempfile
2423 import urlparse
2524 from datetime import datetime, timedelta
2625 from werkzeug.http import HTTP_STATUS_CODES, Accept, CacheControl, \
2726 parse_accept_header, parse_cache_control_header, parse_etags, \
2827 parse_date, generate_etag, is_resource_modified, unquote_etag, \
29 quote_etag, parse_set_header
28 quote_etag, parse_set_header, parse_authorization_header, \
29 parse_www_authenticate_header
3030 from werkzeug.utils import MultiDict, CombinedMultiDict, FileStorage, \
3131 Headers, EnvironHeaders, cached_property, environ_property, \
3232 get_current_url, create_environ, url_encode, run_wsgi_app, get_host, \
3333 cookie_date, parse_cookie, dump_cookie, http_date, escape, \
34 header_property, get_content_type, _empty_stream
35
36
37 class _StorageHelper(cgi.FieldStorage):
38 """
39 Helper class used by `BaseRequest` to parse submitted file and
40 form data. Don't use this class directly.
41 """
42
43 FieldStorageClass = cgi.FieldStorage
44
45 def __init__(self, environ, get_stream):
46 self.get_stream = get_stream
47 cgi.FieldStorage.__init__(self,
48 fp=environ['wsgi.input'],
49 environ={
50 'REQUEST_METHOD': environ['REQUEST_METHOD'],
51 'CONTENT_TYPE': environ['CONTENT_TYPE'],
52 'CONTENT_LENGTH': environ['CONTENT_LENGTH']
53 },
54 keep_blank_values=True
55 )
56
57 def make_file(self, binary=None):
58 return self.get_stream()
59
60 def __repr__(self):
61 """
62 A repr that doesn't read the file. In theory that code is never
63 triggered, but if we debug werkzeug itself it could be that
64 werkzeug fetches the debug info for a _StorageHelper. The default
65 repr reads the whole file which causes problems in the debug view.
66 """
67 return '<%s %r>' % (
68 self.__class__.__name__,
69 self.name
70 )
34 header_property, parse_form_data, get_content_type, url_decode
35 from werkzeug._internal import _empty_stream, _decode_unicode, \
36 _patch_wrapper
7137
7238
7339 class BaseRequest(object):
74 """
75 Very basic request object. This does not implement advanced stuff like
40 """Very basic request object. This does not implement advanced stuff like
7641 entity tag parsing or cache controls. The request object is created with
7742 the WSGI environment as first argument and will add itself to the WSGI
7843 environment as ``'werkzeug.request'`` unless it's created with
9459 Request objects should be considered *read only*. Even though the object
9560 doesn't enforce read only access everywhere you should never modify any
9661 data on the object itself unless you know exactly what you are doing.
62
63 Per default the request object will assume all the text data is `utf-8`
64 encoded. Please refer to `the unicode chapter <unicode.txt>`_ for more
65 details about customizing the behavior.
9766 """
9867 charset = 'utf-8'
68 encoding_errors = 'ignore'
9969 is_behind_proxy = False
10070
101 def __init__(self, environ, populate_request=True):
102 """
103 Per default the request object will be added to the WSGI enviornment
104 as `werkzeug.request` to support the debugging system. If you don't
105 want that, set `populate_request` to `False`.
71 def __init__(self, environ, populate_request=True, shallow=False):
72 """Per default the request object will be added to the WSGI
73 enviornment as `werkzeug.request` to support the debugging system.
74 If you don't want that, set `populate_request` to `False`.
75
76 If `shallow` is `True` the environment is initialized as shallow
77 object around the environ. Every operation that would modify the
78 environ in any way (such as consuming form data) raises an exception
79 unless the `shallow` attribute is explicitly set to `False`. This
80 is useful for middlewares where you don't want to consume the form
81 data by accident. A shallow request is not populated to the WSGI
82 environment.
10683 """
10784 self.environ = environ
108 if populate_request:
85 if populate_request and not shallow:
10986 self.environ['werkzeug.request'] = self
87 self.shallow = shallow
11088 self._data_stream = None
11189
11290 def from_values(cls, path='/', base_url=None, query_string=None, **options):
113 """
114 Create a new request object based on the values provided. If environ
115 is given missing values are filled from there. This method is useful
116 for small scripts when you need to simulate a request from an URL. Do
117 not use this method for unittesting, there is a full featured client
91 """Create a new request object based on the values provided. If
92 environ is given missing values are filled from there. This method is
93 useful for small scripts when you need to simulate a request from an URL.
94 Do not use this method for unittesting, there is a full featured client
11895 object in `werkzeug.test` that allows to create multipart requests
11996 etc.
12097
139116 return cls(result)
140117 from_values = classmethod(from_values)
141118
119 def application(cls, f):
120 """Decorate a function as responder that accepts the request as
121 first argument. This works like the `responder` decorator but
122 the function is passed the request object as first argument::
123
124 @Request.application
125 def my_wsgi_app(request):
126 return Response('Hello World!')
127 """
128 return _patch_wrapper(f, lambda *a: f(cls(a[-2]))(*a[-2:]))
129 application = classmethod(application)
130
142131 def _get_file_stream(self):
143 """
144 Called to get a stream for the file upload.
132 """Called to get a stream for the file upload.
145133
146134 This must provide a file-like class with `read()`, `readline()`
147135 and `seek()` methods that is both writeable and readable.
151139 return tempfile.TemporaryFile('w+b')
152140
153141 def _load_form_data(self):
154 """
155 Method used internally to retrieve submitted data. After calling
142 """Method used internally to retrieve submitted data. After calling
156143 this sets `_form` and `_files` on the request object to multi dicts
157144 filled with the incoming form data. As a matter of fact the input
158145 stream will be empty afterwards.
159146
160147 :internal:
161148 """
162 self._data_stream = _empty_stream
163 form = []
164 files = []
149 if self.shallow:
150 raise RuntimeError('A shallow request tried to consume '
151 'form data. If you really want to do that, '
152 'set `shallow` to False.')
165153 if self.environ['REQUEST_METHOD'] in ('POST', 'PUT'):
166 storage = _StorageHelper(self.environ, self._get_file_stream)
167 if storage.file:
168 self._data_stream = storage.file
169 if storage.list is not None:
170 for key in storage.keys():
171 values = storage[key]
172 if not isinstance(values, list):
173 values = [values]
174 for item in values:
175 if getattr(item, 'filename', None) is not None:
176 fn = item.filename.decode(self.charset, 'ignore')
177 # fix stupid IE bug (IE6 sends the whole path)
178 if fn[1:3] == ':\\' or fn[:2] == '\\\\':
179 fn = fn.split('\\')[-1]
180 files.append((key, FileStorage(item.file, fn,
181 key, item.type, item.length)))
182 else:
183 form.append((key, item.value.decode(self.charset,
184 'ignore')))
185 self._form = MultiDict(form)
186 self._files = MultiDict(files)
154 data = parse_form_data(self.environ, self._get_file_stream,
155 self.charset, self.encoding_errors)
156 else:
157 data = (_empty_stream, MultiDict(), MultiDict())
158 self._data_stream, self._form, self._files = data
187159
188160 def stream(self):
189 """
190 The parsed stream if the submitted data was not multipart or
161 """The parsed stream if the submitted data was not multipart or
191162 urlencoded form data. This stream is the stream left by the CGI
192163 module after parsing. This is *not* the WSGI input stream.
193164 """
194165 if self._data_stream is None:
195166 self._load_form_data()
196167 return self._data_stream
197 stream = property(stream, doc=stream)
168 stream = property(stream, doc=stream.__doc__)
198169 input_stream = environ_property('wsgi.input', 'The WSGI input stream.')
199170
200171 def args(self):
201172 """The parsed URL parameters as `MultiDict`."""
202 items = []
203 qs = self.environ.get('QUERY_STRING', '')
204 for key, values in cgi.parse_qs(qs, True).iteritems():
205 for value in values:
206 value = value.decode(self.charset, 'ignore')
207 items.append((key, value))
208 return MultiDict(items)
173 return url_decode(self.environ.get('QUERY_STRING', ''), self.charset,
174 errors=self.encoding_errors)
209175 args = cached_property(args)
210176
211177 def data(self):
212 """
213 This reads the buffered incoming data from the client into the string.
214 Usually it's a bad idea to access `data` because a client could send
215 dozens of megabytes or more to cause memory problems on the server.
178 """This reads the buffered incoming data from the client into the
179 string. Usually it's a bad idea to access `data` because a client
180 could send dozens of megabytes or more to cause memory problems on the
181 server.
216182 """
217183 return self.stream.read()
218184 data = cached_property(data)
219185
220186 def form(self):
221 """
222 Form parameters. Currently it's not guaranteed that the MultiDict
187 """Form parameters. Currently it's not guaranteed that the MultiDict
223188 returned by this function is ordered in the same way as the submitted
224189 form data. The reason for this is that the underlaying cgi library
225190 uses a dict internally and loses the ordering.
230195 form = property(form, doc=form.__doc__)
231196
232197 def values(self):
233 """Combined multi dict for `args` and `form`"""
198 """Combined multi dict for `args` and `form`."""
234199 return CombinedMultiDict([self.args, self.form])
235200 values = cached_property(values)
236201
237202 def files(self):
238 """
239 A `MultiDict` containing all uploaded files. Each key in
203 """`MultiDict` object containing all uploaded files. Each key in
240204 `files` is the name from the ``<input type="file" name="" />``. Each
241 value in `files` is a Werkzeug `FileStorage` object.
205 value in `files` is a Werkzeug `FileStorage` object with the following
206 members:
207
208 - `filename` - The name of the uploaded file, as a Python string.
209 - `type` - The content type of the uploaded file.
210 - `data` - The raw content of the uploaded file.
211 - `read()` - Read from the stream.
212
213 Note that `files` will only contain data if the request method was POST
214 and the ``<form>`` that posted to the request had
215 ``enctype="multipart/form-data"``. It will be empty otherwise.
216
217 See the `MultiDict` / `FileStorage` documentation for more details about
218 the used data structure.
242219 """
243220 if not hasattr(self, '_files'):
244221 self._load_form_data()
256233 headers = cached_property(headers)
257234
258235 def path(self):
259 """
260 Requested path as unicode. This works a bit like the regular path
236 """Requested path as unicode. This works a bit like the regular path
261237 info in the WSGI environment but will always include a leading slash,
262238 even if the URL root is accessed.
263239 """
264240 path = '/' + (self.environ.get('PATH_INFO') or '').lstrip('/')
265 return path.decode(self.charset, 'ignore')
241 return _decode_unicode(path, self.charset, self.encoding_errors)
266242 path = cached_property(path)
267243
268244 def script_root(self):
269245 """The root path of the script without the trailing slash."""
270246 path = (self.environ.get('SCRIPT_NAME') or '').rstrip('/')
271 return path.decode(self.charset, 'ignore')
247 return _decode_unicode(path, self.charset, self.encoding_errors)
272248 script_root = cached_property(script_root)
273249
274250 def url(self):
302278 '''The transmission method. (For example ``'GET'`` or ``'POST'``).''')
303279
304280 def access_route(self):
305 """
306 If an forwarded header exists this is a list of all ip addresses
281 """If an forwarded header exists this is a list of all ip addresses
307282 from the client ip to the last proxy server.
308283 """
309284 if 'HTTP_X_FORWARDED_FOR' in self.environ:
321296 return self.environ.get('REMOTE_ADDR')
322297 remote_addr = property(remote_addr)
323298
324 is_xhr = property(lambda x: x.environ.get('X_REQUESTED_WITH') ==
325 'XmlHttpRequest', doc='''
299 remote_user = environ_property('REMOTE_ADDR', doc='''
300 If the server supports user authentication, and the script is
301 protected, this attribute contains the username the user has
302 authenticated as.''')
303
304 is_xhr = property(lambda x: x.environ.get('HTTP_X_REQUESTED_WITH', '')
305 .lower() == 'xmlhttprequest', doc='''
326306 True if the request was triggered via an JavaScript XMLHttpRequest.
327307 This only works with libraries that support the X-Requested-With
328308 header and set it to "XMLHttpRequest". Libraries that do that are
342322
343323
344324 class BaseResponse(object):
345 """
346 Base response class. The most important fact about a response object is
347 that it's a regular WSGI application. It's initialized with a couple of
348 response parameters (headers, body, status code etc.) and will start a
325 """Base response class. The most important fact about a response object
326 is that it's a regular WSGI application. It's initialized with a couple
327 of response parameters (headers, body, status code etc.) and will start a
349328 valid WSGI response when called with the environ and start response
350329 callable.
351330
379358 `force_type` method. This is useful if you're working with different
380359 subclasses of response objects and you want to post process them with a
381360 know interface.
361
362 Per default the request object will assume all the text data is `utf-8`
363 encoded. Please refer to `the unicode chapter <unicode.txt>`_ for more
364 details about customizing the behavior.
382365 """
383366 charset = 'utf-8'
384367 default_status = 200
386369
387370 def __init__(self, response=None, status=None, headers=None,
388371 mimetype=None, content_type=None):
389 """
390 Response can be any kind of iterable or string. If it's a string it's
391 considered being an iterable with one item which is the string passed.
392 headers can be a list of tuples or a `Headers` object.
372 """Response can be any kind of iterable or string. If it's a string
373 it's considered being an iterable with one item which is the string
374 passed. Headers can be a list of tuples or a `Headers` object.
393375
394376 Special note for `mimetype` and `content_type`. For most mime types
395377 `mimetype` and `content_type` work the same, the difference affects
426408 self.status = status
427409
428410 def force_type(cls, response, environ=None):
429 """
430 Enforce that the WSGI response is a response object of the current
411 """Enforce that the WSGI response is a response object of the current
431412 type. Werkzeug will use the `BaseResponse` internally in many
432413 situations like the exceptions. If you call `get_response` on an
433414 exception you will get back a regular `BaseResponse` object, even if
441422 # MyResponseClass subclass.
442423 response = MyResponseClass.force_type(response)
443424
444 # convert any WSGI application into a request object
425 # convert any WSGI application into a response object
445426 response = MyResponseClass.force_type(response, environ)
446427
447428 This is especially useful if you want to post-process responses in
460441 force_type = classmethod(force_type)
461442
462443 def from_app(cls, app, environ, buffered=False):
463 """
464 Create a new response object from an application output. This works
465 best if you pass it an application that returns a generator all the
466 time. Sometimes applications may use the `write()` callable returned
467 by the `start_response` function. This tries to resolve such edge
468 cases automatically. But if you don't get the expected output you
469 should set `buffered` to `True` which enforces buffering.
444 """Create a new response object from an application output. This
445 works best if you pass it an application that returns a generator all
446 the time. Sometimes applications may use the `write()` callable
447 returned by the `start_response` function. This tries to resolve such
448 edge cases automatically. But if you don't get the expected output
449 you should set `buffered` to `True` which enforces buffering.
470450 """
471451 return cls(*run_wsgi_app(app, environ, buffered))
472452 from_app = classmethod(from_app)
485465 'The HTTP Status code as number')
486466 del _get_status_code, _set_status_code
487467
488 def write(self, data):
489 """
490 **deprecated**
491
492 Use `response.stream` now, if you are using a `BaseResponse` subclass
493 and mix the `ResponseStreamMixin` in.
494 """
495 from warnings import warn
496 warn(DeprecationWarning('response.write() will go away in Werkzeug '
497 '0.3. Use the new response.stream available '
498 'on `Response`.'))
499 if not isinstance(self.response, list):
500 raise RuntimeError('cannot write to a streamed response.')
501 self.response.append(data)
502
503 def writelines(self, lines):
504 """
505 **deprecated**
506
507 :see: `write`
508 """
509 self.write(''.join(lines))
510
511468 def _get_data(self):
512 """
513 The string representation of the request body. Whenever you access
469 """The string representation of the request body. Whenever you access
514470 this property the request iterable is encoded and flattened. This
515471 can lead to unwanted behavior if you stream big data.
516472 """
520476 def _set_data(self, value):
521477 self.response = [value]
522478 data = property(_get_data, _set_data, doc=_get_data.__doc__)
523
524 def _deprecate_data(f):
525 def proxy(*args, **kwargs):
526 from warnings import warn
527 warn(DeprecationWarning('response_body is now called data'))
528 return f(*args, **kwargs)
529 return proxy
530 response_body = property(_deprecate_data(_get_data),
531 _deprecate_data(_set_data),
532 doc='**deprecated**\ncalled `data` now. '
533 'Will go away in Werkzeug 0.3')
534 del _get_data, _set_data, _deprecate_data
479 del _get_data, _set_data
535480
536481 def iter_encoded(self, charset=None):
537 """
538 Iter the response encoded with the encoding specified. If no
482 """Iter the response encoded with the encoding specified. If no
539483 encoding is given the encoding from the class is used. Note that
540484 this does not encode data that is already a bytestring.
541485 """
548492
549493 def set_cookie(self, key, value='', max_age=None, expires=None,
550494 path='/', domain=None, secure=None, httponly=False):
551 """
552 Sets a cookie. The parameters are the same as in the cookie `Morsel`
495 """Sets a cookie. The parameters are the same as in the cookie `Morsel`
553496 object in the Python standard library but it accepts unicode data too:
554497
555498 - `max_age` should be a number of seconds, or `None` (default) if the
571514 self.set_cookie(key, expires=0, max_age=0, path=path, domain=domain)
572515
573516 def header_list(self):
574 """
575 This returns the headers in the target charset as list. It's used in
576 __call__ to get the headers for the response.
517 """This returns the headers in the target charset as list. It's used
518 in __call__ to get the headers for the response.
577519 """
578520 return self.headers.to_list(self.charset)
579521 header_list = property(header_list, doc=header_list.__doc__)
580522
581523 def is_streamed(self):
582 """
583 If the response is streamed (the response is not a sequence) this
524 """If the response is streamed (the response is not a sequence) this
584525 property is `True`. In this case streamed means that there is no
585526 information about the number of iterations. This is usully `True`
586527 if a generator is passed to the response object.
528
529 This is useful for checking before applying some sort of post
530 filtering that should not take place for streamed responses.
587531 """
588532 try:
589533 len(self.response)
593537 is_streamed = property(is_streamed, doc=is_streamed.__doc__)
594538
595539 def fix_headers(self, environ):
596 """
597 This is automatically called right before the response is started
540 """This is automatically called right before the response is started
598541 and should fix common mistakes in headers. For example location
599542 headers are joined with the root URL here.
600543 """
610553 self.response.close()
611554
612555 def freeze(self):
613 """
614 Call this method if you want to make your response object ready for
615 pickeling. This buffers the generator if there is one.
616 """
556 """Call this method if you want to make your response object ready for
557 pickeling. This buffers the generator if there is one."""
617558 BaseResponse.data.__get__(self)
618559
619560 def __call__(self, environ, start_response):
631572
632573
633574 class AcceptMixin(object):
634 """
635 A mixin for classes with an `environ` attribute to get and all the HTTP
575 """A mixin for classes with an `environ` attribute to get and all the HTTP
636576 accept headers as `Accept` objects. This can be mixed in request objects
637577 or any other object that has a WSGI environ available as `environ`.
638578 """
648588 accept_charsets = cached_property(accept_charsets)
649589
650590 def accept_encodings(self):
651 """
652 List of encodings this client accepts. Encodings in a HTTP term are
653 compression encodings such as gzip. For charsets have a look at
654 `accept_charset`.
655 """
591 """List of encodings this client accepts. Encodings in a HTTP term
592 are compression encodings such as gzip. For charsets have a look at
593 `accept_charset`."""
656594 return parse_accept_header(self.environ.get('HTTP_ACCEPT_ENCODING'))
657595 accept_encodings = cached_property(accept_encodings)
658596
663601
664602
665603 class ETagRequestMixin(object):
666 """
667 Add entity tag and cache descriptors to a request object or object with
604 """Add entity tag and cache descriptors to a request object or object with
668605 an WSGI environment available as `environ`. This not only provides
669606 access to etags but also to the cache control header.
670607 """
697634
698635
699636 class UserAgentMixin(object):
700 """
701 Adds a `user_agent` attribute to the request object which contains the
637 """Adds a `user_agent` attribute to the request object which contains the
702638 parsed user agent of the browser that triggered the request as `UserAgent`
703639 object.
704640 """
715651 user_agent = cached_property(user_agent)
716652
717653
654 class AuthorizationMixin(object):
655 """Adds an `authorization` property that represents the parsed value of
656 the `Authorization` header as `Authorization` object.
657 """
658
659 def authorization(self):
660 """The `Authorization` object in parsed form."""
661 header = self.environ.get('HTTP_AUTHORIZATION')
662 return parse_authorization_header(header)
663 authorization = cached_property(authorization)
664
665
718666 class ETagResponseMixin(object):
719 """
720 Adds extra functionality to a response object for etag and cache
667 """Adds extra functionality to a response object for etag and cache
721668 handling. This mixin requires an object with at least a `headers`
722669 object that implements a dict like interface similar to `Headers`.
723670 """
724671
725672 def cache_control(self):
726 """
727 The Cache-Control general-header field is used to specify directives
728 that MUST be obeyed by all caching mechanisms along the
673 """The Cache-Control general-header field is used to specify
674 directives that MUST be obeyed by all caching mechanisms along the
729675 request/response chain.
730676 """
731677 def on_update(cache_control):
738684 cache_control = property(cache_control, doc=cache_control.__doc__)
739685
740686 def make_conditional(self, request_or_environ):
741 """
742 Make the response conditional to the request. This method works best
743 if an etag was defined for the response already. The `add_etag`
687 """Make the response conditional to the request. This method works
688 best if an etag was defined for the response already. The `add_etag`
744689 method can be used to do that. If called without etag just the date
745690 header is set.
746691
774719 self.headers['ETag'] = quote_etag(etag, weak)
775720
776721 def get_etag(self):
777 """
778 Return a tuple in the form ``(etag, is_weak)``. If there is no
722 """Return a tuple in the form ``(etag, is_weak)``. If there is no
779723 ETag the return value is ``(None, None)``.
780724 """
781725 return unquote_etag(self.headers.get('ETag'))
782726
783727 def freeze(self, no_etag=False):
784 """
785 Call this method if you want to make your response object ready for
728 """Call this method if you want to make your response object ready for
786729 pickeling. This buffers the generator if there is one. This also
787730 sets the etag unless `no_etag` is set to `True`.
788731 """
792735
793736
794737 class ResponseStream(object):
795 """
796 A file descriptor like object used by the `ResponseStreamMixin` to
738 """A file descriptor like object used by the `ResponseStreamMixin` to
797739 represent the body of the stream. It directly pushes into the response
798740 iterable of the response object.
799741 """
834776
835777
836778 class ResponseStreamMixin(object):
837 """
838 Mixin for `BaseRequest` subclasses. Classes that inherit from this mixin
839 will automatically get a `stream` property that provides a write-only
840 interface to the response iterable.
779 """Mixin for `BaseRequest` subclasses. Classes that inherit from this
780 mixin will automatically get a `stream` property that provides a
781 write-only interface to the response iterable.
841782 """
842783
843784 def stream(self):
847788
848789
849790 class CommonResponseDescriptorsMixin(object):
850 """
851 A mixin for `BaseResponse` subclasses. Response objects that mix this
791 """A mixin for `BaseResponse` subclasses. Response objects that mix this
852792 class in will automatically get descriptors for a couple of HTTP headers
853793 with automatic type conversion.
854794 """
972912 _set_retry_after
973913
974914
915 class WWWAuthenticateMixin(object):
916 """Adds a `www_authenticate` property to a response object."""
917
918 def www_authenticate(self):
919 """The ``WWW-Authenticate`` header in a parsed form."""
920 def on_update(www_auth):
921 if not www_auth and 'www-authenticate' in self.headers:
922 del self.headers['www-authenticate']
923 elif www_auth:
924 self.headers['WWW-Authenticate'] = www_auth.to_header()
925 header = self.headers.get('www-authenticate')
926 return parse_www_authenticate_header(header, on_update)
927 www_authenticate = property(www_authenticate)
928
929
975930 class Request(BaseRequest, AcceptMixin, ETagRequestMixin,
976 UserAgentMixin):
977 """
978 Full featured request object implementing the following mixins:
931 UserAgentMixin, AuthorizationMixin):
932 """Full featured request object implementing the following mixins:
979933
980934 - `AcceptMixin` for accept header parsing
981935 - `ETagRequestMixin` for etag and cache control handling
982936 - `UserAgentMixin` for user agent introspection
937 - `AuthorizationMixin` for http auth handling
983938 """
984939
985940
986941 class Response(BaseResponse, ETagResponseMixin, ResponseStreamMixin,
987 CommonResponseDescriptorsMixin):
988 """
989 Full featured response object implementing the following mixins:
942 CommonResponseDescriptorsMixin,
943 WWWAuthenticateMixin):
944 """Full featured response object implementing the following mixins:
990945
991946 - `ETagResponseMixin` for etag and cache control handling
992947 - `ResponseStreamMixin` to add support for the `stream` property
993948 - `CommonResponseDescriptorsMixin` for various HTTP descriptors
994 """
995
996
997 # XXX: backwards compatibility interface. goes away with werkzeug 0.3
998 try:
999 from werkzeug.contrib.reporterstream import BaseReporterStream
1000 except ImportError:
1001 class BaseReporterStream(object):
1002 def __new__(*args, **kw):
1003 raise RuntimeError('base reporter stream is now part of the '
1004 'contrib package. In order to use it install '
1005 'werkzeug with the contrib package enabled '
1006 'and import it from '
1007 'werkzeug.contrib.reporterstream')
1008 else:
1009 class BaseReporterStream(BaseReporterStream):
1010 def __init__(self, environ, threshold):
1011 from warnings import warn
1012 warn(DeprecationWarning('BaseReporterStream is now part of '
1013 'the werkzeug contrib module. Import '
1014 'it from werkzeug.contrib.reporterstream'
1015 '. As of werkzeug 0.3 this will be'
1016 'required.'))
1017 super(BaseReporterStream, self).__init__(environ, threshold)
949 - `WWWAuthenticateMixin` for HTTP authentication support
950 """