Codebase list python-werkzeug / d9a4624
Imported Upstream version 0.2 SVN-Git Migration 8 years ago
48 changed file(s) with 6273 addition(s) and 1362 deletion(s). Raw diff Collapse all Expand all
55 - Georg Brandl
66 - Leif K-Brooks <eurleif@gmail.com>
77 - Marek Kubica
8 - Thomas Johansson
9 - Marian Sigler
11 ==================
22
33
4 Version 0.2
5 -----------
6 (codename Faustkeil, estimated release date Feb 14th 2008)
7
8 - Added `AnyConverter` to the routing system.
9 - Added `werkzeug.contrib.securecookie`
10 - Exceptions have a ``get_response()`` method that return a resposne object
11 - fixed the path ordering bug (#293), thanks Thomas Johansson
12 - `BaseReporterStream` is now part of the werkzeug contrib module. With
13 Werkzeug 0.3 onwards you will have to import it from there.
14 - added `DispatcherMiddleware`.
15 - `RequestRedirect` is now a subclass of `HTTPException` and uses a
16 301 status code instead of 302.
17 - `url_encode` and `url_decode` can optionally treat keys as unicode strings
18 now too.
19 - `werkzeug.script` has a different caller format for boolean arguments now.
20 - renamed `lazy_property` to `cached_property`.
21 - added `import_string`.
22 - added is_* properties to request objects.
23 - added `empty()` method to routing rules.
24 - added `werkzeug.contrib.profiler`.
25 - added `extends` to `Headers`.
26 - added `dump_cookie` and `parse_cookie`.
27 - added `as_tuple` to the `Client`.
28 - added `werkzeug.contrib.testtools`.
29 - added `werkzeug.unescape`
30 - added `BaseResponse.freeze`
31 - added `werkzeug.contrib.atom`
32 - the HTTPExceptions accept an argument `description` now which overrides the
33 default description.
34 - the `MapAdapter` has a default for path info now. If you use
35 `bind_to_environ` you don't have to pass the path later.
36 - the wsgiref subclass werkzeug uses for the dev server does not use direct
37 sys.stderr logging any more but a logger called "werkzeug".
38 - implemented `Href`.
39 - implemented `find_modules`
40 - refactored request and response objects into base objects, mixins and
41 full featured subclasses that implement all mixins.
42 - added simple user agent parser
43 - werkzeug's routing raises `MethodNotAllowed` now if it matches a
44 rule but for a different method.
45 - many fixes and small improvements
46
47
448 Version 0.1
549 -----------
6 (codename Wictorinoxger, release Dec 9th 2007)
50 (codename Wictorinoxger, released Dec 9th 2007)
751
852 - Initial release
00 include Makefile CHANGES LICENSE AUTHORS TODO ez_setup.py
1 recursive-include werkzeug.debug shared/*
1 recursive-include werkzeug debug/shared/*
22 recursive-include tests *
88 #
99
1010 documentation:
11 @(cd docs; ./generate.py)
12
13 webpage:
14 @(cd ../www; ./generate.py)
11 @(cd docs; python ./generate.py)
1512
1613 test:
1714 @(cd tests; py.test $(TESTS))
00 Metadata-Version: 1.0
11 Name: Werkzeug
2 Version: 0.1
2 Version: 0.2
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://trac.pocoo.org/repos/werkzeug/trunk
9 Description: werkzeug
10 ~~~~~~~~
8 Download-URL: http://werkzeug.pocoo.org/download
9 Description:
10 Werkzeug
11 ========
1112
12 Werkzeug is the Swiss Army(r) knife of Python web development.
13 Werkzeug started as simple collection of various utilities for WSGI
14 applications and has become one of the most advanced WSGI utility
15 modules. It includes a powerful debugger, full featured request and
16 response objects, HTTP utilities to handle entity tags, cache control
17 headers, HTTP dates, cookie handling, file uploads, a powerful URL
18 routing system and a bunch of community contributed addon modules.
1319
14 It provides useful classes and functions for any WSGI application take
15 make life much easier. All of the provided classes are independed from
16 each other so you can mix it with any other library.
20 Werkzeug is unicode aware and doesn't enforce a specific template
21 engine, database adapter or anything else. It doesn't even enforce
22 a specific way of handling requests and leaves all that up to the
23 developer. It's most useful for end user applications which should work
24 on as many server environments as possible (such as blogs, wikis,
25 bulletin boards, etc.).
26
27 Details and example applications are available on the
28 `Werkzeug website <http://werkzeug.pocoo.org/>`_.
1729
1830
19 Builtin blades^Wfeatures
20 ~~~~~~~~~~~~~~~~~~~~~~~~
31 Features
32 --------
2133
22 **Request / Response objects**
34 - unicode awareness
2335
24 These objects wrap the WSGI `environ` and `start_response` objects.
25 They handle unicode conversion, form data parsing, cookie management
26 and much more in a django like manner.
36 - request and response objects
2737
28 Just subclass them and hook your own features in.
38 - various utility functions for dealing with HTTP headers such as
39 `Accept` and `Cache-Control` headers.
2940
30 **Reporter stream**
41 - thread local objects with proper cleanup at request end
3142
32 A class that can wrap a `wsgi.input` stream so that it reports it's
33 progress into the active session, a file on the filesystem etc. This
34 is very useful if you want to give your users a visual feedback for
35 file uploads using AJAX.
43 - an interactive debugger
3644
37 **Application debugger middleware**
45 - wrapper around wsgiref that works around some of the limitations
46 and bugs, adds threading and fork support for test environments
47 and adds an automatic reloader.
3848
39 If you want to debug your WSGI application you can hook in the
40 `DebuggedApplication` middleware that allows you to inspect the frames
41 of tracebacks either by looking at the current locals and sourcecode
42 or starting an interactive shell in one of the frames.
49 - a flexible URL routing system with REST support.
4350
44 **Shared data middleware**
45
46 In production environments static data is usually served by a
47 lightweight webserver like lighttpd or nginx. But during development
48 it makes no sense to install another service on the computer so the
49 `SharedDataMiddleware` can serve static files in your WSGI
50 application.
51
52 **Unicode aware data processing**
53
54 The utils package contains functions that work like their counterparts
55 in the builtin `urllib` or `cgi` module but are unicode aware. Per
56 default they expect utf-8 strings like the request/response objects
57 but you can pass an encoding to the too.
58
59 **Mini template engine**
60
61 For small projects you often face the problem that a real template
62 engine means another requirement but the builtin string formattings
63 (or string template) operations are not enough for the application.
64 Werkzeug provides a minimal template engine that looks and behaves
65 like the e-ruby template engine.
66
67 **Context Locals**
68
69 The `Local` object works pretty much like a normal thread local but
70 it has support for py.magic greenlets too. Additionally there is a
71 `LocalManager` that allows you to clean up all the context locals you
72 have instanciated.
73
74 **Test utilities**
75
76 Werkzeug provides a `Client` class that can be used to test
77 applications. Just instanciate it with the app and fire virtual
78 requests.
51 - fully WSGI compatible
7952
8053
81 :copyright: 2007 by Armin Ronacher.
82 :license: BSD, see LICENSE for more details.
54 Development Version
55 -------------------
56
57 The `Werkzeug tip <http://dev.pocoo.org/hg/werkzeug-main/archive/tip.zip#egg=Werkzeug-dev`
58 is installable via `easy_install` with ``easy_install Werkzeug==dev``.
59
8360 Platform: any
84 Classifier: Development Status :: 2 - Pre-Alpha
61 Classifier: Development Status :: 3 - Alpha
8562 Classifier: Environment :: Web Environment
8663 Classifier: Intended Audience :: Developers
8764 Classifier: License :: OSI Approved :: BSD License
00 Metadata-Version: 1.0
11 Name: Werkzeug
2 Version: 0.1
2 Version: 0.2
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://trac.pocoo.org/repos/werkzeug/trunk
9 Description: werkzeug
10 ~~~~~~~~
8 Download-URL: http://werkzeug.pocoo.org/download
9 Description:
10 Werkzeug
11 ========
1112
12 Werkzeug is the Swiss Army(r) knife of Python web development.
13 Werkzeug started as simple collection of various utilities for WSGI
14 applications and has become one of the most advanced WSGI utility
15 modules. It includes a powerful debugger, full featured request and
16 response objects, HTTP utilities to handle entity tags, cache control
17 headers, HTTP dates, cookie handling, file uploads, a powerful URL
18 routing system and a bunch of community contributed addon modules.
1319
14 It provides useful classes and functions for any WSGI application take
15 make life much easier. All of the provided classes are independed from
16 each other so you can mix it with any other library.
20 Werkzeug is unicode aware and doesn't enforce a specific template
21 engine, database adapter or anything else. It doesn't even enforce
22 a specific way of handling requests and leaves all that up to the
23 developer. It's most useful for end user applications which should work
24 on as many server environments as possible (such as blogs, wikis,
25 bulletin boards, etc.).
26
27 Details and example applications are available on the
28 `Werkzeug website <http://werkzeug.pocoo.org/>`_.
1729
1830
19 Builtin blades^Wfeatures
20 ~~~~~~~~~~~~~~~~~~~~~~~~
31 Features
32 --------
2133
22 **Request / Response objects**
34 - unicode awareness
2335
24 These objects wrap the WSGI `environ` and `start_response` objects.
25 They handle unicode conversion, form data parsing, cookie management
26 and much more in a django like manner.
36 - request and response objects
2737
28 Just subclass them and hook your own features in.
38 - various utility functions for dealing with HTTP headers such as
39 `Accept` and `Cache-Control` headers.
2940
30 **Reporter stream**
41 - thread local objects with proper cleanup at request end
3142
32 A class that can wrap a `wsgi.input` stream so that it reports it's
33 progress into the active session, a file on the filesystem etc. This
34 is very useful if you want to give your users a visual feedback for
35 file uploads using AJAX.
43 - an interactive debugger
3644
37 **Application debugger middleware**
45 - wrapper around wsgiref that works around some of the limitations
46 and bugs, adds threading and fork support for test environments
47 and adds an automatic reloader.
3848
39 If you want to debug your WSGI application you can hook in the
40 `DebuggedApplication` middleware that allows you to inspect the frames
41 of tracebacks either by looking at the current locals and sourcecode
42 or starting an interactive shell in one of the frames.
49 - a flexible URL routing system with REST support.
4350
44 **Shared data middleware**
45
46 In production environments static data is usually served by a
47 lightweight webserver like lighttpd or nginx. But during development
48 it makes no sense to install another service on the computer so the
49 `SharedDataMiddleware` can serve static files in your WSGI
50 application.
51
52 **Unicode aware data processing**
53
54 The utils package contains functions that work like their counterparts
55 in the builtin `urllib` or `cgi` module but are unicode aware. Per
56 default they expect utf-8 strings like the request/response objects
57 but you can pass an encoding to the too.
58
59 **Mini template engine**
60
61 For small projects you often face the problem that a real template
62 engine means another requirement but the builtin string formattings
63 (or string template) operations are not enough for the application.
64 Werkzeug provides a minimal template engine that looks and behaves
65 like the e-ruby template engine.
66
67 **Context Locals**
68
69 The `Local` object works pretty much like a normal thread local but
70 it has support for py.magic greenlets too. Additionally there is a
71 `LocalManager` that allows you to clean up all the context locals you
72 have instanciated.
73
74 **Test utilities**
75
76 Werkzeug provides a `Client` class that can be used to test
77 applications. Just instanciate it with the app and fire virtual
78 requests.
51 - fully WSGI compatible
7952
8053
81 :copyright: 2007 by Armin Ronacher.
82 :license: BSD, see LICENSE for more details.
54 Development Version
55 -------------------
56
57 The `Werkzeug tip <http://dev.pocoo.org/hg/werkzeug-main/archive/tip.zip#egg=Werkzeug-dev`
58 is installable via `easy_install` with ``easy_install Werkzeug==dev``.
59
8360 Platform: any
84 Classifier: Development Status :: 2 - Pre-Alpha
61 Classifier: Development Status :: 3 - Alpha
8562 Classifier: Environment :: Web Environment
8663 Classifier: Intended Audience :: Developers
8764 Classifier: License :: OSI Approved :: BSD License
2020 tests/test_utils.pyc
2121 tests/test_wrappers.py
2222 tests/test_wrappers.pyc
23 tests/contrib/test_testtools.py
24 tests/contrib/test_testtools.pyc
2325 tests/res/test.txt
2426 werkzeug/__init__.py
2527 werkzeug/exceptions.py
3133 werkzeug/templates.py
3234 werkzeug/test.py
3335 werkzeug/testapp.py
36 werkzeug/useragents.py
3437 werkzeug/utils.py
3538 werkzeug/wrappers.py
3639 werkzeug/contrib/__init__.py
40 werkzeug/contrib/atom.py
41 werkzeug/contrib/cache.py
3742 werkzeug/contrib/iterio.py
3843 werkzeug/contrib/jsrouting.py
3944 werkzeug/contrib/kickstart.py
4045 werkzeug/contrib/limiter.py
46 werkzeug/contrib/profiler.py
47 werkzeug/contrib/reporterstream.py
48 werkzeug/contrib/securecookie.py
4149 werkzeug/contrib/sessions.py
50 werkzeug/contrib/testtools.py
4251 werkzeug/debug/__init__.py
4352 werkzeug/debug/render.py
4453 werkzeug/debug/util.py
54 werkzeug/debug/shared/body.tmpl
55 werkzeug/debug/shared/codetable.tmpl
56 werkzeug/debug/shared/debugger.js
57 werkzeug/debug/shared/jquery.js
58 werkzeug/debug/shared/style.css
59 werkzeug/debug/shared/vartable.tmpl
00 # -*- coding: utf-8 -*-
1 import werkzeug
1 """
2 Werkzeug
3 ========
4
5 Werkzeug started as simple collection of various utilities for WSGI
6 applications and has become one of the most advanced WSGI utility
7 modules. It includes a powerful debugger, full featured request and
8 response objects, HTTP utilities to handle entity tags, cache control
9 headers, HTTP dates, cookie handling, file uploads, a powerful URL
10 routing system and a bunch of community contributed addon modules.
11
12 Werkzeug is unicode aware and doesn't enforce a specific template
13 engine, database adapter or anything else. It doesn't even enforce
14 a specific way of handling requests and leaves all that up to the
15 developer. It's most useful for end user applications which should work
16 on as many server environments as possible (such as blogs, wikis,
17 bulletin boards, etc.).
18
19 Details and example applications are available on the
20 `Werkzeug website <http://werkzeug.pocoo.org/>`_.
21
22
23 Features
24 --------
25
26 - unicode awareness
27
28 - request and response objects
29
30 - various utility functions for dealing with HTTP headers such as
31 `Accept` and `Cache-Control` headers.
32
33 - thread local objects with proper cleanup at request end
34
35 - an interactive debugger
36
37 - wrapper around wsgiref that works around some of the limitations
38 and bugs, adds threading and fork support for test environments
39 and adds an automatic reloader.
40
41 - a flexible URL routing system with REST support.
42
43 - fully WSGI compatible
44
45
46 Development Version
47 -------------------
48
49 The `Werkzeug tip <http://dev.pocoo.org/hg/werkzeug-main/archive/tip.zip#egg=Werkzeug-dev`
50 is installable via `easy_install` with ``easy_install Werkzeug==dev``.
51 """
252 import os
353 import ez_setup
4 from inspect import getdoc
554 ez_setup.use_setuptools()
655
756 from setuptools import setup, Feature
857
958 setup(
1059 name='Werkzeug',
11 version='0.1',
60 version='0.2',
1261 url='http://werkzeug.pocoo.org/',
13 download_url='http://trac.pocoo.org/repos/werkzeug/trunk',
62 download_url='http://werkzeug.pocoo.org/download',
1463 license='BSD',
1564 author='Armin Ronacher',
1665 author_email='armin.ronacher@active-4.com',
1766 description='The Swiss Army knife of Python web development',
18 long_description=getdoc(werkzeug),
67 long_description=__doc__,
1968 zip_safe=False,
2069 classifiers=[
21 'Development Status :: 2 - Pre-Alpha',
70 'Development Status :: 3 - Alpha',
2271 'Environment :: Web Environment',
2372 'Intended Audience :: Developers',
2473 'License :: OSI Approved :: BSD License',
0 from werkzeug.contrib.testtools import *
1 from werkzeug import Client, BaseRequest, responder
2
3 from py.test import raises
4
5 def response(content, mimetype):
6 return TestResponse(
7 status=200,
8 response=content,
9 mimetype=mimetype,
10 )
11
12 @responder
13 def application(environ, start_response):
14 request = BaseRequest(environ)
15 return response('This is a Test.', 'text/plain')
16
17 def test_json():
18 resp = response('{ "a": 1}', 'application/json')
19 assert resp.json == {'a': 1}
20
21 def test_json_fail():
22 resp = response('{ "a": 1}', 'text/plain')
23 raises(AttributeError, 'resp.json')
24
25 def test_lxml_html():
26 resp = response(
27 '<html><head><title>Test</title></head></html>',
28 'text/html')
29 assert resp.lxml.xpath('//text()') == ['Test']
30
31 def test_lxml_xml():
32 resp = response(
33 '<html><head><title>Test</title></head></html>',
34 'application/xml')
35 assert resp.lxml.xpath('//text()') == ['Test']
36
37 def test_lxml_fail():
38 resp = response(
39 '<html><head><title>Test</title></head></html>',
40 'text/plain')
41 raises(AttributeError, 'resp.lxml')
88 """
99 from py.test import raises
1010 from werkzeug.routing import Map, Rule, NotFound, BuildError, RequestRedirect
11 from werkzeug.utils import create_environ
1112
1213
1314 def test_basic_routing():
2223 assert adapter.match('/bar/') == ('bar', {})
2324 raises(RequestRedirect, lambda: adapter.match('/bar'))
2425 raises(NotFound, lambda: adapter.match('/blub'))
26
27
28 test_environ_defaults = '''
29 >>> from werkzeug.routing import Map, Rule
30 >>> from werkzeug import create_environ
31 >>> environ = create_environ("/foo")
32 >>> environ["PATH_INFO"]
33 '/foo'
34 >>> m = Map([Rule("/foo", endpoint="foo"), Rule("/bar", endpoint="bar")])
35 >>> a = m.bind_to_environ(environ)
36 >>> a.match("/foo")
37 ('foo', {})
38 >>> a.match()
39 ('foo', {})
40 >>> a.match("/bar")
41 ('bar', {})
42 >>> a.match("/bars")
43 Traceback (most recent call last):
44 ...
45 NotFound: 404 Not Found
46 '''
2547
2648
2749 def test_basic_building():
7698 assert adapter.build('foo', {}) == '/foo'
7799 assert adapter.build('bar', {'bar': 'blub'}) == '/blub'
78100 assert adapter.build('bar', {'bar': 'blub', 'blub': 'bar'}) == '/blub/bar'
101
102
103 def test_path():
104 map = Map([
105 Rule('/', defaults={'name': 'FrontPage'}, endpoint='page'),
106 Rule('/Special', endpoint='special'),
107 Rule('/<int:year>', endpoint='year'),
108 Rule('/<path:name>', endpoint='page'),
109 Rule('/<path:name>/edit', endpoint='editpage'),
110 Rule('/<path:name>/silly/<path:name2>', endpoint='sillypage'),
111 Rule('/<path:name>/silly/<path:name2>/edit', endpoint='editsillypage'),
112 Rule('/Talk:<path:name>', endpoint='talk'),
113 Rule('/User:<username>', endpoint='user'),
114 Rule('/User:<username>/<path:name>', endpoint='userpage'),
115 Rule('/Files/<path:file>', endpoint='files'),
116 ])
117 adapter = map.bind('example.org', '/')
118
119 assert adapter.match('/') == ('page', {'name':'FrontPage'})
120 raises(RequestRedirect, lambda: adapter.match('/FrontPage'))
121 assert adapter.match('/Special') == ('special', {})
122 assert adapter.match('/2007') == ('year', {'year':2007})
123 assert adapter.match('/Some/Page') == ('page', {'name':'Some/Page'})
124 assert adapter.match('/Some/Page/edit') == ('editpage', {'name':'Some/Page'})
125 assert adapter.match('/Foo/silly/bar') == ('sillypage', {'name':'Foo', 'name2':'bar'})
126 assert adapter.match('/Foo/silly/bar/edit') == ('editsillypage', {'name':'Foo', 'name2':'bar'})
127 assert adapter.match('/Talk:Foo/Bar') == ('talk', {'name':'Foo/Bar'})
128 assert adapter.match('/User:thomas') == ('user', {'username':'thomas'})
129 assert adapter.match('/User:thomas/projects/werkzeug') == ('userpage', {'username':'thomas', 'name':'projects/werkzeug'})
130 assert adapter.match('/Files/downloads/werkzeug/0.2.zip') == ('files', {'file':'downloads/werkzeug/0.2.zip'})
Binary diff not shown
Binary diff not shown
66 :license: BSD license.
77 """
88 import sys
9 from datetime import datetime
910 from os import path
1011 from py.test import raises
1112 from werkzeug.utils import *
13 from werkzeug.wrappers import BaseResponse
14 from werkzeug.http import parse_date
1215 from werkzeug.test import Client
16
17
18 def test_import_patch():
19 import werkzeug
20 from werkzeug import __all__ as public_methods
21 for name in public_methods:
22 getattr(werkzeug, name)
1323
1424
1525 def test_multidict():
105115 popped = md.popitemlist()
106116 assert popped in [('b', [2]), ('c', [3])]
107117
118 # type conversion
119 md = MultiDict({'a': '4', 'b': ['2', '3']})
120 assert md.get('a', type=int) == 4
121 assert md.getlist('b', type=int) == [2, 3]
122
108123
109124 def test_combined_multidict():
110 d1 = MultiDict([('foo', 1)])
111 d2 = MultiDict([('bar', 2)])
125 d1 = MultiDict([('foo', '1')])
126 d2 = MultiDict([('bar', '2'), ('bar', '3')])
112127 d = CombinedMultiDict([d1, d2])
113128
114129 # lookup
115 assert d['foo'] == 1
116 assert d['bar'] == 2
130 assert d['foo'] == '1'
131 assert d['bar'] == '2'
132 assert d.getlist('bar') == ['2', '3']
133
134 # type lookup
135 assert d.get('foo', type=int) == 1
136 assert d.getlist('bar', type=int) == [2, 3]
117137
118138 # get key errors for missing stuff
119139 raises(KeyError, 'd["missing"]')
150170 assert headers.get('x-Bar') == '1'
151171 assert headers.get('Content-Type') == 'text/plain'
152172
173 # type conversion
174 assert headers.get('x-bar', type=int) == 1
175 assert headers.getlist('x-bar', type=int) == [1, 2]
176
153177 # copying
154178 a = Headers([('foo', 'bar')])
155179 b = a.copy()
158182 assert b.getlist('foo') == ['bar']
159183
160184
161 def test_lazy_property():
185 def test_cached_property():
162186 foo = []
163187 class A(object):
164188 def prop(self):
165189 foo.append(42)
166190 return 42
167 prop = lazy_property(prop)
191 prop = cached_property(prop)
168192
169193 a = A()
170194 p = a.prop
174198
175199 foo = []
176200 class A(object):
177 def prop(self):
201 def _prop(self):
178202 foo.append(42)
179203 return 42
180 prop = lazy_property(prop, name='propval')
204 prop = cached_property(_prop, name='prop')
205 del _prop
181206
182207 a = A()
183208 p = a.prop
184209 q = a.prop
185 r = a.propval
186 assert p == q == r == 42
187 assert foo == [42, 42]
210 assert p == q == 42
211 assert foo == [42]
188212
189213
190214 def test_environ_property():
194218 string = environ_property('string')
195219 missing = environ_property('missing', 'spam')
196220 read_only = environ_property('number', read_only=True)
197 number = environ_property('number', convert=int)
198 broken_number = environ_property('broken_number', convert=int)
221 number = environ_property('number', load_func=int)
222 broken_number = environ_property('broken_number', load_func=int)
223 date = environ_property('date', None, parse_date, http_date)
199224
200225 a = A()
201226 assert a.string == 'abc'
203228 raises(AttributeError, 'a.read_only = "something"')
204229 assert a.number == 42
205230 assert a.broken_number == None
231 assert a.date is None
232 a.date = datetime(2008, 1, 22, 10, 0, 0, 0)
233 assert a.environ['date'] == 'Tue, 22 Jan 2008 10:00:00 GMT'
206234
207235
208236 def test_quoting():
211239 assert url_quote_plus('foo bar') == 'foo+bar'
212240 assert url_unquote_plus('foo+bar') == 'foo bar'
213241 assert url_encode({'a': None, 'b': 'foo bar'}) == 'b=foo+bar'
242 assert url_fix(u'http://de.wikipedia.org/wiki/Elf (Begriffsklärung)') == \
243 'http://de.wikipedia.org/wiki/Elf%20%28Begriffskl%C3%A4rung%29'
244
245
246 test_href_tool = '>>> from werkzeug import Href\n\n' + Href.__doc__
214247
215248
216249 def test_escape():
250 assert escape(None) == ''
251 assert escape(42) == '42'
217252 assert escape('<>') == '&lt;&gt;'
218253 assert escape('"foo"') == '"foo"'
219254 assert escape('"foo"', True) == '&quot;foo&quot;'
255
256
257 def test_unescape():
258 assert unescape('&lt;&auml;&gt;') == u'<ä>'
220259
221260
222261 def test_create_environ():
263302 assert ''.join(app_iter).strip() == 'NOT FOUND'
264303
265304
305 def test_run_wsgi_app():
306 def foo(environ, start_response):
307 start_response('200 OK', [('Content-Type', 'text/plain')])
308 yield '1'
309 yield '2'
310 yield '3'
311
312 app_iter, status, headers = run_wsgi_app(foo, {})
313 assert status == '200 OK'
314 assert headers == [('Content-Type', 'text/plain')]
315 assert app_iter.next() == '1'
316 assert app_iter.next() == '2'
317 assert app_iter.next() == '3'
318 raises(StopIteration, app_iter.next)
319
320 got_close = []
321 class CloseIter(object):
322 def __init__(self):
323 self.iterated = False
324 def __iter__(self):
325 return self
326 def close(self):
327 got_close.append(None)
328 def next(self):
329 if self.iterated:
330 raise StopIteration()
331 self.iterated = True
332 return 'bar'
333
334 def bar(environ, start_response):
335 start_response('200 OK', [('Content-Type', 'text/plain')])
336 return CloseIter()
337
338 app_iter, status, headers = run_wsgi_app(bar, {})
339 assert status == '200 OK'
340 assert headers == [('Content-Type', 'text/plain')]
341 assert app_iter.next() == 'bar'
342 raises(StopIteration, app_iter.next)
343 app_iter.close()
344
345 assert run_wsgi_app(bar, {}, True)[0] == ['bar']
346
347 assert len(got_close) == 2
348
349
266350 def test_date_funcs():
267351 assert http_date(0) == 'Thu, 01 Jan 1970 00:00:00 GMT'
268352 assert cookie_date(0) == 'Thu, 01-Jan-1970 00:00:00 GMT'
288372 >>> x(env, strip_querystring=True)
289373 'http://example.org/blub/foo'
290374 '''
375
376
377 def test_dates():
378 assert cookie_date(0) == 'Thu, 01-Jan-1970 00:00:00 GMT'
379 assert cookie_date(datetime(1970, 1, 1)) == 'Thu, 01-Jan-1970 00:00:00 GMT'
380 assert http_date(0) == 'Thu, 01 Jan 1970 00:00:00 GMT'
381 assert http_date(datetime(1970, 1, 1)) == 'Thu, 01 Jan 1970 00:00:00 GMT'
382
383
384 def test_cookies():
385 assert parse_cookie('dismiss-top=6; CP=null*; PHPSESSID=0a539d42abc001cd'
386 'c762809248d4beed; a=42') == {
387 'CP': u'null*',
388 'PHPSESSID': u'0a539d42abc001cdc762809248d4beed',
389 'a': u'42',
390 'dismiss-top': u'6'
391 }
392 assert set(dump_cookie('foo', 'bar baz blub', 360, httponly=True,
393 sync_expires=False).split('; ')) == \
394 set(['HttpOnly', 'Max-Age=360', 'Path=/', 'foo=bar baz blub'])
395 assert parse_cookie('fo234{=bar blub=Blah') == {'blub': 'Blah'}
396
397
398 def test_responder():
399 def foo(environ, start_response):
400 return BaseResponse('Test')
401 client = Client(responder(foo), BaseResponse)
402 response = client.get('/')
403 assert response.status_code == 200
404 assert response.data == 'Test'
405
406
407 def test_import_string():
408 import cgi
409 assert import_string('cgi.escape') is cgi.escape
410 assert import_string('cgi:escape') is cgi.escape
411 assert import_string('XXXXXXXXXXXX', True) is None
412 assert import_string('cgi.XXXXXXXXXXXX', True) is None
413 raises(ImportError, "import_string('XXXXXXXXXXXXXXXX')")
414 raises(AttributeError, "import_string('cgi.XXXXXXXXXX')")
415
416
417 def test_find_modules():
418 assert list(find_modules('werkzeug.debug')) == ['werkzeug.debug.render',
419 'werkzeug.debug.util']
420
421
422 def test_html_builder():
423 assert html.p('Hello World') == '<p>Hello World</p>'
424 assert html.a('Test', href='#') == '<a href="#">Test</a>'
425 assert html.br() == '<br>'
426 assert xhtml.br() == '<br />'
427 assert html.img(src='foo') == '<img src="foo">'
428 assert xhtml.img(src='foo') == '<img src="foo" />'
429 assert html.html(
430 html.head(
431 html.title('foo'),
432 html.script(type='text/javascript')
433 )
434 ) == '<html><head><title>foo</title><script type="text/javascript">' \
435 '</script></head></html>'
Binary diff not shown
77 :license: BSD license.
88 """
99 import pickle
10 from werkzeug.wrappers import BaseResponse, BaseRequest
10 from py.test import raises
11 from datetime import datetime, timedelta
12 from werkzeug.wrappers import *
1113 from werkzeug.utils import MultiDict
1214 from werkzeug.test import Client
1315
2123
2224 def __init__(self, response, status, headers):
2325 BaseResponse.__init__(self, response, status, headers)
24 self.body_data = pickle.loads(self.response_body)
26 self.body_data = pickle.loads(self.data)
2527
2628 def __getitem__(self, key):
2729 return self.body_data[key]
6264 assert environ['wsgi.url_scheme'] == 'http'
6365
6466
65 def test_request():
67 def test_base_request():
6668 client = Client(request_test_app, RequestTestResponse)
6769
6870 # get requests
9496 assert response['form'] == MultiDict()
9597
9698
97 def test_response():
99 def test_base_response():
98100 # unicode
99101 response = BaseResponse(u'öäü')
100 assert response.response_body == 'öäü'
102 assert response.data == 'öäü'
101103
102104 # writing
103105 response = BaseResponse('foo')
104106 response.write('bar')
105 assert response.response_body == 'foobar'
107 assert response.data == 'foobar'
106108
107109 # set cookie
108110 response = BaseResponse()
112114 ('Set-Cookie', 'foo=bar; Domain=example.org; expires=Thu, '
113115 '01-Jan-1970 00:00:00 GMT; Max-Age=60; Path=/blub')
114116 ]
117
118
119 def test_type_forcing():
120 def wsgi_application(environ, start_response):
121 start_response('200 OK', [('Content-Type', 'text/html')])
122 return ['Hello World!']
123 base_response = BaseResponse('Hello World!', content_type='text/html')
124
125 class SpecialResponse(Response):
126 def foo(self):
127 return 42
128
129 # good enough for this simple application, but don't ever use that in
130 # real world examples!
131 fake_env = {}
132
133 for orig_resp in wsgi_application, base_response:
134 response = SpecialResponse.force_type(orig_resp, fake_env)
135 assert response.__class__ is SpecialResponse
136 assert response.foo() == 42
137 assert response.data == 'Hello World!'
138 assert response.content_type == 'text/html'
139
140 # without env, no arbitrary conversion
141 raises(TypeError, "SpecialResponse.force_type(wsgi_application)")
142
143
144 def test_accept_mixin():
145 request = Request({
146 'HTTP_ACCEPT': 'text/xml,application/xml,application/xhtml+xml,'
147 'text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
148 'HTTP_ACCEPT_CHARSET': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
149 'HTTP_ACCEPT_ENCODING': 'gzip,deflate',
150 'HTTP_ACCEPT_LANGUAGE': 'en-us,en;q=0.5'
151 })
152 assert request.accept_mimetypes == Accept([
153 ('text/xml', 1), ('image/png', 1), ('application/xml', 1),
154 ('application/xhtml+xml', 1), ('text/html', 0.9),
155 ('text/plain', 0.8), ('*/*', 0.5)
156 ])
157 assert request.accept_charsets == Accept([
158 ('ISO-8859-1', 1), ('utf-8', 0.7), ('*', 0.7)
159 ])
160 assert request.accept_encodings == Accept([('gzip', 1), ('deflate', 1)])
161 assert request.accept_languages == Accept([('en-us', 1), ('en', 0.5)])
162
163
164 def test_etag_request_mixin():
165 request = Request({
166 'HTTP_CACHE_CONTROL': 'private, no-cache',
167 'HTTP_IF_MATCH': 'w/"foo", bar, "baz"',
168 'HTTP_IF_NONE_MATCH': 'w/"foo", bar, "baz"',
169 'HTTP_IF_MODIFIED_SINCE': 'Tue, 22 Jan 2008 11:18:44 GMT',
170 'HTTP_IF_UNMODIFIED_SINCE': 'Tue, 22 Jan 2008 11:18:44 GMT'
171 })
172 assert request.cache_control.private
173 assert request.cache_control.no_cache
174
175 for etags in request.if_match, request.if_none_match:
176 assert etags('bar')
177 assert etags.contains_raw('w/"foo"')
178 assert etags.contains_weak('foo')
179 assert not etags.contains('foo')
180
181 assert request.if_modified_since == datetime(2008, 1, 22, 11, 18, 44)
182 assert request.if_unmodified_since == datetime(2008, 1, 22, 11, 18, 44)
183
184
185 def test_user_agent_mixin():
186 user_agents = [
187 ('Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.11) '
188 'Gecko/20071127 Firefox/2.0.0.11', 'firefox', 'macos', '2.0.0.11',
189 'en-US'),
190 ('Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; de-DE) Opera 8.54',
191 'opera', 'windows', '8.54', 'de-DE'),
192 ('Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420 '
193 '(KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3',
194 'safari', 'iphone', '419.3', 'en'),
195 ('Bot Googlebot/2.1 ( http://www.googlebot.com/bot.html)',
196 'google', None, '2.1', None)
197 ]
198 for ua, browser, platform, version, lang in user_agents:
199 request = Request({'HTTP_USER_AGENT': ua})
200 assert request.user_agent.browser == browser
201 assert request.user_agent.platform == platform
202 assert request.user_agent.version == version
203 assert request.user_agent.language == lang
204
205
206 def test_etag_response_mixin():
207 response = Response('Hello World')
208 assert response.get_etag() == (None, None)
209 response.add_etag()
210 assert response.get_etag() == ('b10a8db164e0754105b7a99be72e3fe5', False)
211 assert not response.cache_control
212 response.cache_control.must_revalidate = True
213 response.cache_control.max_age = 60
214 assert response.headers['Cache-Control'] == 'must-revalidate, max-age=60'
215
216 response.make_conditional({
217 'REQUEST_METHOD': 'GET',
218 'HTTP_IF_NONE_MATCH': response.get_etag()[0]
219 })
220 assert response.status_code == 304
221
222
223 def test_response_stream_mixin():
224 response = Response()
225 response.stream.write('Hello ')
226 response.stream.write('World!')
227 assert response.response == ['Hello ', 'World!']
228 assert response.data == 'Hello World!'
229
230
231 def test_common_response_descriptors_mixin():
232 response = Response()
233 response.mimetype = 'text/html'
234 assert response.mimetype == 'text/html'
235 assert response.content_type == 'text/html; charset=utf-8'
236
237 now = datetime.utcnow().replace(microsecond=0)
238
239 assert response.content_length is None
240 response.content_length = '42'
241 assert response.content_length == 42
242
243 for attr in 'date', 'age', 'expires':
244 assert getattr(response, attr) is None
245 setattr(response, attr, now)
246 assert getattr(response, attr) == now
247
248 assert response.retry_after is None
249 response.retry_after = now
250 assert response.retry_after == now
251
252 assert not response.vary
253 response.vary.add('Cookie')
254 response.vary.add('Content-Language')
255 assert 'cookie' in response.vary
256 assert response.vary.to_header() == 'Cookie, Content-Language'
257 response.headers['Vary'] = 'Content-Encoding'
258 assert response.vary.as_set() == set(['content-encoding'])
259
260 response.allow.update(['GET', 'POST'])
261 assert response.headers['Allow'] == 'GET, POST'
262
263 response.content_language.add('en-US')
264 response.content_language.add('fr')
265 assert response.headers['Content-Language'] == 'en-US, fr'
Binary diff not shown
22 werkzeug
33 ~~~~~~~~
44
5 Werkzeug is the Swiss Army(r) knife of Python web development.
5 Werkzeug is the Swiss Army knife of Python web development.
66
7 It provides useful classes and functions for any WSGI application take
8 make life much easier. All of the provided classes are independed from
9 each other so you can mix it with any other library.
7 It provides useful classes and functions for any WSGI application to make
8 the life of a python web developer much easier. All of the provided
9 classes are independed from each other so you can mix it with any other
10 library.
1011
1112
12 Builtin blades^Wfeatures
13 ~~~~~~~~~~~~~~~~~~~~~~~~
14
15 **Request / Response objects**
16
17 These objects wrap the WSGI `environ` and `start_response` objects.
18 They handle unicode conversion, form data parsing, cookie management
19 and much more in a django like manner.
20
21 Just subclass them and hook your own features in.
22
23 **Reporter stream**
24
25 A class that can wrap a `wsgi.input` stream so that it reports it's
26 progress into the active session, a file on the filesystem etc. This
27 is very useful if you want to give your users a visual feedback for
28 file uploads using AJAX.
29
30 **Application debugger middleware**
31
32 If you want to debug your WSGI application you can hook in the
33 `DebuggedApplication` middleware that allows you to inspect the frames
34 of tracebacks either by looking at the current locals and sourcecode
35 or starting an interactive shell in one of the frames.
36
37 **Shared data middleware**
38
39 In production environments static data is usually served by a
40 lightweight webserver like lighttpd or nginx. But during development
41 it makes no sense to install another service on the computer so the
42 `SharedDataMiddleware` can serve static files in your WSGI
43 application.
44
45 **Unicode aware data processing**
46
47 The utils package contains functions that work like their counterparts
48 in the builtin `urllib` or `cgi` module but are unicode aware. Per
49 default they expect utf-8 strings like the request/response objects
50 but you can pass an encoding to the too.
51
52 **Mini template engine**
53
54 For small projects you often face the problem that a real template
55 engine means another requirement but the builtin string formattings
56 (or string template) operations are not enough for the application.
57 Werkzeug provides a minimal template engine that looks and behaves
58 like the e-ruby template engine.
59
60 **Context Locals**
61
62 The `Local` object works pretty much like a normal thread local but
63 it has support for py.magic greenlets too. Additionally there is a
64 `LocalManager` that allows you to clean up all the context locals you
65 have instanciated.
66
67 **Test utilities**
68
69 Werkzeug provides a `Client` class that can be used to test
70 applications. Just instanciate it with the app and fire virtual
71 requests.
72
73
74 :copyright: 2007 by Armin Ronacher.
13 :copyright: 2007-2008 by Armin Ronacher.
7514 :license: BSD, see LICENSE for more details.
7615 """
7716 from types import ModuleType
7918
8019
8120 all_by_module = {
82 'werkzeug': ['exceptions', 'routing', 'script'],
8321 'werkzeug.debug': ['DebuggedApplication'],
8422 'werkzeug.local': ['Local', 'LocalManager', 'LocalProxy'],
8523 'werkzeug.templates': ['Template'],
8624 'werkzeug.serving': ['run_simple'],
8725 'werkzeug.test': ['Client'],
8826 'werkzeug.testapp': ['test_app'],
27 'werkzeug.exceptions': ['abort', 'Aborter'],
8928 'werkzeug.utils': ['escape', 'create_environ', 'url_quote',
9029 'environ_property', 'cookie_date', 'http_date',
9130 'url_encode', 'url_quote_plus', 'Headers',
92 'EnvironHeaders', 'CombinedMultiDict',
93 'run_wsgi_app', 'get_host',
31 'EnvironHeaders', 'CombinedMultiDict', 'url_fix',
32 'run_wsgi_app', 'get_host', 'responder',
9433 'SharedDataMiddleware', 'ClosingIterator',
95 'FileStorage', 'url_unquote_plus',
34 'FileStorage', 'url_unquote_plus', 'url_decode',
9635 'url_unquote', 'get_current_url', 'redirect',
97 'lazy_property', 'MultiDict', 'url_decode'],
98 'werkzeug.http': ['Accept', 'CacheControl', 'parse_accept_header',
99 'parse_cache_control_header',
100 'HTTP_STATUS_CODES'],
101 'werkzeug.wrappers': ['BaseResponse', 'BaseRequest',
102 'BaseReporterStream']
36 'append_slash_redirect', 'lazy_property',
37 'cached_property', 'MultiDict', 'import_string',
38 'dump_cookie', 'parse_cookie', 'unescape',
39 'format_string', 'Href', 'DispatcherMiddleware',
40 'find_modules', 'header_property', 'html',
41 'xhtml'],
42 'werkzeug.useragents': ['UserAgent'],
43 'werkzeug.http': ['Accept', 'CacheControl', 'ETags', 'parse_etags',
44 'parse_date', 'parse_cache_control_header',
45 'is_resource_modified', 'parse_accept_header',
46 'parse_set_header', 'quote_etag', 'unquote_etag',
47 'HeaderSet', 'HTTP_STATUS_CODES'],
48 'werkzeug.wrappers': ['BaseResponse', 'BaseRequest', 'Request',
49 'Response', 'AcceptMixin', 'ETagRequestMixin',
50 'ETagResponseMixin', 'ResponseStreamMixin',
51 'CommonResponseDescriptorsMixin',
52 'UserAgentMixin', 'BaseReporterStream']
10353 }
54
55 attribute_modules = ['exceptions', 'routing', 'script']
10456
10557
10658 object_origins = {}
10961 object_origins[item] = module
11062
11163
112 class _AutoModule(ModuleType):
64 class module(ModuleType):
11365 """Automatically import objects from the modules."""
11466
11567 def __getattr__(self, name):
11870 for extra_name in all_by_module[module.__name__]:
11971 setattr(self, extra_name, getattr(module, extra_name))
12072 return getattr(module, name)
73 elif name in attribute_modules:
74 __import__('werkzeug.' + name)
12175 return ModuleType.__getattribute__(self, name)
12276
12377
78 # keep a reference to this module so that it's not garbage collected
12479 old_module = sys.modules['werkzeug']
125 new_module = sys.modules['werkzeug'] = _AutoModule('werkzeug')
126 new_module.__dict__.update(
127 __file__=__file__,
128 __path__=__path__,
129 __doc__=__doc__,
130 __all__=tuple(object_origins)
131 )
80
81 # setup the new module and patch it into the dict of loaded modules
82 new_module = sys.modules['werkzeug'] = module('werkzeug')
83 new_module.__dict__.update({
84 '__file__': __file__,
85 '__path__': __path__,
86 '__doc__': __doc__,
87 '__all__': tuple(object_origins)
88 })
1212 tell the Python interpreter that `contrib` is a package.
1313
1414
15 :copyright: 2007 by Marek Kubica.
15 :copyright: 2007-2008 by Marek Kubica, Marian Sigler, Armin Ronacher,
16 Leif K-Brooks, Ronny Pfannschmid, Thomas Johansson.
1617 :license: BSD, see LICENSE for more details.
1718 """
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.contrib.atom
3 ~~~~~~~~~~~~~~~~~~~~~
4
5 This module provides a class called `AtomFeed` which can be used
6 to generate Atom feeds.
7
8 :copyright: Copyright 2007 by Armin Ronacher, Marian Sigler.
9 :license: GNU GPL.
10 """
11 from datetime import datetime
12 from werkzeug.utils import escape
13 from werkzeug.wrappers import BaseResponse
14
15
16 XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'
17
18
19 def _make_text_block(name, content, content_type=None):
20 """Helper function for the builder that creates an XML text block."""
21 if content_type == 'xhtml':
22 return u'<%s type="xhtml"><div xmlns="%s">%s</div></%s>\n' % \
23 (name, XHTML_NAMESPACE, content, name)
24 if not content_type:
25 return u'<%s>%s</%s>\n' % (name, escape(content), name)
26 return u'<%s type="%s">%s</%s>\n' % (name, content_type,
27 escape(content), name)
28
29
30 def format_iso8601(obj):
31 """Format a datetime object for iso8601"""
32 return obj.strftime('%Y-%d-%mT%H:%M:%SZ')
33
34
35 class AtomFeed(object):
36 """
37 A helper class that creates ATOM feeds.
38 """
39 default_generator = ('Werkzeug', None, None)
40
41 def __init__(self, title=None, entries=None, **kwargs):
42 """
43 Create an atom feed.
44
45 :Parameters:
46 title
47 the title of the feed. Required.
48 title_type
49 the type attribute for the title element. One of html, text,
50 xhtml. Default is text.
51 url
52 the url for the feed (not the url *of* the feed)
53 id
54 a globally unique id for the feed. Must be an URI. If not present
55 the `feed_url` is used, but one of both is required.
56 updated
57 the time the feed was modified the last time. Must be a `datetime`
58 object. If not present the latest entry's `updated` is used.
59 feed_url
60 the url to the feed. Should be the URL that was requested.
61 author
62 the author of the feed. Must be either a string (the name) or a
63 dict with name (required) and uri or email (both optional). Can be
64 a list of (may be mixed, too) strings and dicts, too, if there are
65 multiple authors. Required if not every entry has an author
66 element.
67 icon
68 an icon for the feed.
69 logo
70 a logo for the feed.
71 rights
72 copyright information for the feed.
73 rights_type
74 the type attribute for the rights element. One of html, text,
75 xhtml. Default is text.
76 subtitle
77 a short description of the feed.
78 subtitle_type
79 the type attribute for the subtitle element. One of html, text,
80 xhtml. Default is text.
81 links
82 additional links. Must be a list of dictionaries with href
83 (required) and rel, type, hreflang, title, length (all optional)
84 generator
85 the software that generated this feed. This must be a tuple in
86 the form ``(name, url, version)``. If you don't want to specify
87 one of them, set the item to None.
88 entries
89 a list with the entries for the feed. Entries can also be added
90 later with add().
91
92 For more information on the elements see
93 http://www.atomenabled.org/developers/syndication/
94
95 Everywhere where a list is demanded, any iterable can be used.
96 """
97 self.title = title
98 self.title_type = kwargs.get('title_type')
99 self.url = kwargs.get('url')
100 self.feed_url = kwargs.get('feed_url', self.url)
101 self.id = kwargs.get('id', self.feed_url)
102 self.updated = kwargs.get('updated')
103 self.author = kwargs.get('author', ())
104 self.icon = kwargs.get('icon')
105 self.logo = kwargs.get('logo')
106 self.rights = kwargs.get('rights')
107 self.rights_type = kwargs.get('rights_type')
108 self.subtitle = kwargs.get('subtitle')
109 self.subtitle_type = kwargs.get('subtitle_type')
110 self.generator = kwargs.get('generator')
111 if self.generator is None:
112 self.generator = self.default_generator
113 self.links = kwargs.get('links', [])
114 self.entries = entries and list(entries) or []
115
116 if not hasattr(self.author, '__iter__') \
117 or isinstance(self.author, (basestring, dict)):
118 self.author = [self.author]
119 for i, author in enumerate(self.author):
120 if not isinstance(author, dict):
121 self.author[i] = {'name': author}
122
123 if not self.title:
124 raise ValueError('title is required')
125 if not self.id:
126 raise ValueError('id is required')
127 for author in self.author:
128 if 'name' not in author:
129 raise TypeError('author must contain at least a name')
130
131 def add(self, *args, **kwargs):
132 """add a new entry to the feed"""
133 if len(args) == 1 and not kwargs and isinstance(args[0], FeedEntry):
134 self.entries.append(args[0])
135 else:
136 self.entries.append(FeedEntry(*args, **kwargs))
137
138 def __repr__(self):
139 return '<%s %r (%d entries)>' % (
140 self.__class__.__name__,
141 self.title,
142 len(self.entries)
143 )
144
145 def generate(self):
146 """Return a generator that yields pieces of XML."""
147 # atom demands either an author element in every entry or a global one
148 if not self.author:
149 if False in map(lambda e: bool(e.author), self.entries):
150 self.author = ({'name': u'unbekannter Autor'},)
151
152 if not self.updated:
153 dates = sorted(entry.updated for entry in self.entries)
154 self.updated = dates and dates[-1] or datetime.utcnow()
155
156 yield u'<?xml version="1.0" encoding="utf-8"?>\n'
157 yield u'<feed xmlns="http://www.w3.org/2005/Atom">\n'
158 yield ' ' + _make_text_block('title', self.title, self.title_type)
159 yield u' <id>%s</id>\n' % escape(self.id)
160 yield u' <updated>%s</updated>\n' % format_iso8601(self.updated)
161 if self.url:
162 yield u' <link href="%s" />\n' % escape(self.url, True)
163 if self.feed_url:
164 yield u' <link href="%s" rel="self" />\n' % \
165 escape(self.feed_url, True)
166 for link in self.links:
167 yield u' <link %s/>\n' % ''.join('%s="%s" ' % \
168 (k, escape(link[k], True)) for k in link)
169 for author in self.author:
170 yield u' <author>\n'
171 yield u' <name>%s</name>\n' % escape(author['name'])
172 if 'uri' in author:
173 yield u' <uri>%s</uri>\n' % escape(author['uri'])
174 if 'email' in author:
175 yield ' <email>%s</email>\n' % escape(author['email'])
176 yield ' </author>\n'
177 if self.subtitle:
178 yield ' ' + _make_text_block('subtitle', self.subtitle,
179 self.subtitle_type)
180 if self.icon:
181 yield u' <icon>%s</icon>\n' % escape(self.icon)
182 if self.logo:
183 yield u' <logo>%s</logo>\n' % escape(self.logo)
184 if self.rights:
185 yield ' ' + _make_text_block('rights', self.rights,
186 self.rights_type)
187 generator_name, generator_url, generator_version = self.generator
188 if generator_name or generator_url or generator_version:
189 tmp = [u' <generator']
190 if generator_url:
191 tmp.append(u' uri="%s"' % escape(generator_url, True))
192 if generator_version:
193 tmp.append(u' version="%s"' % escape(generator_version, True))
194 tmp.append(u'>%s</generator>\n' % escape(generator_name))
195 yield u''.join(tmp)
196 for entry in self.entries:
197 for line in entry.generate():
198 yield u' ' + line
199 yield u'</feed>\n'
200
201 def to_string(self):
202 """Convert the feed into a string."""
203 return u''.join(self.generate())
204
205 def get_response(self):
206 """Return a response object for the feed."""
207 return BaseResponse(self.to_string(), mimetype='application/atom+xml')
208
209 def __call__(self, environ, start_response):
210 """Use the class as WSGI response object."""
211 return self.get_response(environ, start_response)
212
213 def __unicode__(self):
214 return self.to_string()
215
216 def __str__(self):
217 return self.to_string().encode('utf-8')
218
219
220 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.
228
229 :Parameters:
230 title
231 the title of the entry. Required.
232 title_type
233 the type attribute for the title element. One of html, text,
234 xhtml. Default is text.
235 content
236 the content of the entry.
237 content_type
238 the type attribute for the content element. One of html, text,
239 xhtml. Default is text.
240 summary
241 a summary of the entry's content.
242 summary_type
243 a type attribute for the summary element. One of html, text,
244 xhtml. Default is text.
245 url
246 the url for the entry.
247 id
248 a globally unique id for the entry. Must be an URI. If not present
249 the URL is used, but one of both is required.
250 updated
251 the time the entry was modified the last time. Must be a
252 `datetime` object. Required.
253 author
254 the author of the entry. Must be either a string (the name) or a
255 dict with name (required) and uri or email (both optional). Can
256 be a list of (may be mixed, too) strings and dicts, too, if there
257 are multiple authors. Required if there is no author for the
258 feed.
259 published
260 the time the entry was initially published. Must be a `datetime`
261 object.
262 rights
263 copyright information for the entry.
264 rights_type
265 the type attribute for the rights element. One of html, text,
266 xhtml. Default is text.
267 links
268 additional links. Must be a list of dictionaries with href
269 (required) and rel, type, hreflang, title, length (all optional)
270 xml_base
271 The xml base (url) for this feed item. If not provided it will
272 default to the item url.
273
274 For more information on the elements see
275 http://www.atomenabled.org/developers/syndication/
276
277 Everywhere where a list is demanded, any iterable can be used.
278 """
279 self.title = title
280 self.title_type = kwargs.get('title_type', 'html')
281 self.content = content
282 self.content_type = kwargs.get('content_type', 'html')
283 self.url = kwargs.get('url')
284 self.id = kwargs.get('id', self.url)
285 self.updated = kwargs.get('updated')
286 self.summary = kwargs.get('summary')
287 self.summary_type = kwargs.get('summary_type', 'html')
288 self.author = kwargs.get('author')
289 self.published = kwargs.get('published')
290 self.rights = kwargs.get('rights')
291 self.links = kwargs.get('links', [])
292 self.xml_base = kwargs.get('xml_base', self.url)
293
294 if not hasattr(self.author, '__iter__') \
295 or isinstance(self.author, (basestring, dict)):
296 self.author = [self.author]
297 for i, author in enumerate(self.author):
298 if not isinstance(author, dict):
299 self.author[i] = {'name': author}
300
301 if not self.title:
302 raise ValueError('title is required')
303 if not self.id:
304 raise ValueError('id is required')
305 if not self.updated:
306 raise ValueError('updated is required')
307
308 def __repr__(self):
309 return '<%s %r>' % (
310 self.__class__.__name__,
311 self.title
312 )
313
314 def generate(self):
315 """Yields pieces of ATOM XML."""
316 base = ''
317 if self.xml_base:
318 base = ' xml:base="%s"' % escape(self.xml_base, True)
319 yield u'<entry%s>\n' % base
320 yield u' ' + _make_text_block('title', self.title, self.title_type)
321 yield u' <id>%s</id>\n' % escape(self.id)
322 yield u' <updated>%s</updated>\n' % format_iso8601(self.updated)
323 if self.published:
324 yield u' <published>%s</published>\n' % \
325 format_iso8601(self.published)
326 if self.url:
327 yield u' <link href="%s" />\n' % escape(self.url)
328 for author in self.author:
329 yield u' <author>\n'
330 yield u' <name>%s</name>\n' % escape(author['name'])
331 if 'uri' in author:
332 yield u' <uri>%s</uri>\n' % escape(author['uri'])
333 if 'email' in author:
334 yield u' <email>%s</email>\n' % escape(author['email'])
335 yield u' </author>\n'
336 for link in self.links:
337 yield u' <link %s/>\n' % ''.join('%s="%s" ' % \
338 (k, escape(link[k], True)) for k in link)
339 if self.summary:
340 yield u' ' + _make_text_block('summary', self.summary,
341 self.summary_type)
342 if self.content:
343 yield u' ' + _make_text_block('content', self.content,
344 self.content_type)
345 yield u'</entry>\n'
346
347 def to_string(self):
348 """Convert the feed item into a unicode object."""
349 return u''.join(self.generate())
350
351 def __unicode__(self):
352 return self.to_string()
353
354 def __str__(self):
355 return self.to_string().encode('utf-8')
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.contrib.cache
3 ~~~~~~~~~~~~~~~~~~~~~~
4
5 Small helper module that provides a simple interface to memcached, a
6 simple django-inspired in-process cache and a file system based cache.
7
8 The idea is that it's possible to switch caching systems without changing
9 much code in the application.
10
11
12 :copyright: 2007-2008 by Armin Ronacher.
13 :license: BSD, see LICENSE for more details.
14 """
15 try:
16 from hashlib import md5
17 except ImportError:
18 from md5 import new as md5
19 from itertools import izip
20 from time import time
21 from cPickle import loads, dumps, load, dump, HIGHEST_PROTOCOL
22
23 have_memcache = True
24 try:
25 from cmemcache import memcache
26 except ImportError:
27 try:
28 import memcache
29 except ImportError:
30 have_memcache = False
31
32
33 class BaseCache(object):
34 """Baseclass for the cache systems."""
35
36 def __init__(self, default_timeout=300):
37 self.default_timeout = default_timeout
38
39 def get(self, key):
40 return None
41 delete = get
42
43 def get_many(self, *keys):
44 return [self.get(key) for key in keys]
45
46 def get_dict(self, *keys):
47 return dict(izip(keys, self.get_many(keys)))
48
49 def set(self, key, value, timeout=None):
50 pass
51 add = set
52
53 def set_many(self, mapping, timeout=None):
54 for key, value in mapping.iteritems():
55 self.set(key, value, timeout)
56
57 def delete_many(self, *keys):
58 for key in keys:
59 self.delete(key)
60
61 def clear(self):
62 pass
63
64
65 class NullCache(BaseCache):
66 """A cache that doesn't cache."""
67
68
69 class SimpleCache(BaseCache):
70 """
71 Simple memory cache for single process environments. This class exists
72 mainly for the development server and is not 100% thread safe. It tries
73 to use as many atomic operations as possible and no locks for simplicity
74 but it could happen under heavy load that keys are added multiple times.
75 """
76
77 def __init__(self, threshold=500, default_timeout=300):
78 BaseCache.__init__(self, default_timeout)
79 self._cache = {}
80 self.clear = self._cache.clear
81 self._threshold = threshold
82
83 def _prune(self):
84 if len(self._cache) > self._threshold:
85 now = time()
86 for idx, (key, (expires, _)) in enumerate(self._cache.items()):
87 if expires <= now or idx % 3 == 0:
88 self._cache.pop(key, None)
89
90 def get(self, key):
91 now = time()
92 expires, value = self._cache.get(key, (0, None))
93 if expires > time():
94 return loads(value)
95
96 def set(self, key, value, timeout=None):
97 if timeout is None:
98 timeout = self.default_timeout
99 self._prune()
100 self._cache[key] = (time() + timeout, dumps(value, HIGHEST_PROTOCOL))
101
102 def add(self, key, value, timeout=None):
103 if timeout is None:
104 timeout = self.default_timeout
105 if len(self._cache) > self._threshold:
106 self._prune()
107 item = (time() + timeout, dumps(value, HIGHEST_PROTOCOL))
108 self._cache.setdefault(key, item)
109
110 def delete(self, key):
111 self._cache.pop(key, None)
112
113
114 class MemcachedCache(BaseCache):
115 """A cache that uses memcached as backend."""
116
117 def __init__(self, servers, default_timeout=300):
118 BaseClient.__init__(self, default_timeout)
119 self._client = memcache.Client(servers)
120
121 def get(self, key):
122 return self._client.get(key)
123
124 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)
136
137 def set_many(self, mapping, timeout=None):
138 if timeout is None:
139 timeout = self.default_timeout
140 self._client.set_multi(mapping, timeout)
141
142 def delete(self, key):
143 self._client.delete(key)
144
145 def delete_many(self, *keys):
146 self._client.delete_multi(keys)
147
148 def clear(self):
149 self._client.flush_all()
150
151
152 class FileSystemCache(BaseCache):
153 """A cache that stores the items on the file system."""
154
155 def __init__(self, cache_dir, threshold=500, default_timeout=300):
156 BaseCache.__init__(self, default_timeout)
157 self._path = cache_dir
158 self._threshold = threshold
159 if not os.path.exists(self._path):
160 os.makedirs(self._path)
161
162 def _prune(self):
163 entries = os.listdir(self._path)
164 if len(entries) > self._threshold:
165 now = time()
166 for idx, key in enumerate(entries):
167 try:
168 f = file(self._get_filename(key))
169 if pickle.load(f) > now and idx % 3 != 0:
170 f.close()
171 continue
172 except:
173 f.close()
174 self.delete(key)
175
176 def _get_filename(self, key):
177 hash = md5(key).hexdigest()
178 return os.path.join(self._path, hash)
179
180 def get(self, key):
181 filename = self._get_filename(key)
182 try:
183 f = file(filename, 'rb')
184 try:
185 if load(f) >= time():
186 return load(f)
187 finally:
188 f.close()
189 os.remove(filename)
190 except:
191 return None
192
193 def add(self, key, value, timeout=None):
194 filename = self._get_filename(key)
195 if not os.path.exists(filename):
196 self.set(key, value, timeout)
197
198 def set(self, key, value, timeout=None):
199 if timeout is None:
200 timeout = self.default_timeout
201 filename = self._get_filename(key)
202 self._prune()
203 try:
204 f = file(filename, 'wb')
205 try:
206 dump(int(time() + timeout), f, 1)
207 dump(value, f, HIGHEST_PROTOCOL)
208 finally:
209 f.close()
210 except (IOError, OSError):
211 pass
212
213 def delete(self, key):
214 try:
215 os.remove(self._get_filename(key))
216 except (IOError, OSError):
217 pass
218
219 def clear(self):
220 for key in os.listdir(self._path):
221 self.delete(key)
33 ~~~~~~~~~~~~~~~~~~~~~~~
44
55 This module implements a `IterIO` that converts an iterator into a stream
6 object and the other way round. Converting streams into interators
6 object and the other way round. Converting streams into iterators
77 requires the `greenlet`_ module.
88
99
2424 constructor on the other hand is not an stream object but an iterator::
2525
2626 def foo(stream):
27 stream.write("something")
27 stream.write("some")
28 stream.write("thing")
29 stream.flush()
2830 stream.write("otherthing")
2931 iterator = IterIO(foo)
3032 print iterator.next() # prints something
124126
125127 def __new__(cls, func):
126128 if greenlet is None:
127 raise RuntimeError('IterI requires greenlets')
129 raise RuntimeError('IterI requires greenlet support')
128130 stream = object.__new__(cls)
129131 stream.__init__(greenlet.getcurrent())
130132
131 g = greenlet(lambda: func(stream), stream._parent)
133 def run():
134 func(stream)
135 stream.flush()
136
137 g = greenlet(run, stream._parent)
132138 while 1:
133139 rv = g.switch()
134140 if not rv:
137143
138144 def __init__(self, parent):
139145 self._parent = parent
146 self._buffer = []
140147 self.closed = False
141148 self.pos = 0
142149
143150 def close(self):
144151 if not self.closed:
145152 self.closed = True
146 self._parent.throw(ExecutionStop)
147153
148154 def write(self, s):
149155 if self.closed:
150156 raise ValueError('I/O operation on closed file')
151157 self.pos += len(s)
152 self._parent.switch((s,))
158 self._buffer.append(s)
153159
154160 def writelines(slf, list):
155161 self.write(''.join(list))
157163 def flush(self):
158164 if self.closed:
159165 raise ValueError('I/O operation on closed file')
166 data = ''.join(self._buffer)
167 self._buffer = []
168 self._parent.switch((data,))
160169
161170
162171 class IterO(IterIO):
55 This module provides some simple shortcuts to make using Werkzeug
66 simpler for small scripts.
77
8 These improvements include predefied Request and Response objects as well
9 as a pre-defined Application object which all can be customized in child
10 classes of course. The Request and Reponse objects handle URL generation
11 as well as sessions via the werkzeug.contrib.sessions and is purely
12 optional.
13
14 There is also some integration of template engines. The template loaders
15 are of course not neccessary to use the template engines in Werkzeug, but
16 they provide a common interface. Currently supported template engines
17 include Werkzeug's minitmpl an Genshi. Support for other engines can be
18 added in a trivial way. These loaders provide a template interface similar
19 to the one that Django uses.
820
921 :copyright: 2007 by Marek Kubica, Armin Ronacher.
1022 :license: BSD, see LICENSE for more details.
1224 from os import path
1325 from werkzeug.wrappers import BaseRequest, BaseResponse
1426 from werkzeug.templates import Template
15
16
17 __all__ = ['Request', 'Response', 'TemplateNotFound', 'TemplateLoader']
27 from werkzeug.exceptions import HTTPException
28 from werkzeug.routing import RequestRedirect
29 from werkzeug.contrib.sessions import FilesystemSessionStore
30
31 __all__ = ['Request', 'Response', 'TemplateNotFound', 'TemplateLoader',
32 'GenshiTemplateLoader', 'Application']
1833
1934
2035 class Request(BaseRequest):
2136 """
2237 A handy subclass of the base request that adds a URL builder.
23 """
24
25 def __init__(self, environ, url_map):
38 It when supplied a session store, it is also able to handle sessions.
39 """
40
41 def __init__(self, environ, url_map,
42 session_store=None, cookie_name=None):
43 # call the parent for initialization
2644 BaseRequest.__init__(self, environ)
45 # create an adapter
2746 self.url_adapter = url_map.bind_to_environ(environ)
47 # create all stuff for sessions
48 self.session_store = session_store
49 self.cookie_name = cookie_name
50
51 if session_store is not None and cookie_name is not None:
52 if cookie_name in self.cookies:
53 # get the session out of the storage
54 self.session = session_store.get(self.cookies[cookie_name])
55 else:
56 # create a new session
57 self.session = session_store.new()
2858
2959 def url_for(self, callback, **values):
3060 return self.url_adapter.build(callback, values)
3262
3363 class Response(BaseResponse):
3464 """
35 A subclass of base response which sets the default mimetype to text/html
65 A subclass of base response which sets the default mimetype to text/html.
66 It the `Request` that came in is using Werkzeug sessions, this class
67 takes care of saving that session.
3668 """
3769 default_mimetype = 'text/html'
70
71 def __call__(self, environ, start_response):
72 # get the request object
73 request = environ['werkzeug.request']
74
75 if request.session_store is not None:
76 # save the session if neccessary
77 request.session_store.save_if_modified(request.session)
78
79 # set the cookie for the browser if it is not there:
80 if request.cookie_name not in request.cookies:
81 self.set_cookie(request.cookie_name, request.session.sid)
82
83 # go on with normal response business
84 return BaseResponse.__call__(self, environ, start_response)
85
86
87 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.
92
93 The code of this processor is derived from the example in the Werkzeug
94 trac, called `Request and Response Processor
95 <http://dev.pocoo.org/projects/werkzeug/wiki/RequestResponseProcessor>`_
96 """
97
98 def process_request(self, request):
99 return request
100
101 def process_response(self, request, response):
102 return response
103
104 def process_view(self, request, view_func, view_args, view_kwargs):
105 """
106 process_view() is called just before the Application calls the
107 function specified by view_func.
108
109 If this returns None, the Application processes the next Processor,
110 and if it returns something else (like a Response instance), that
111 will be returned without any further processing.
112 """
113 return None
114
115 def process_exception(self, request, exception):
116 return None
117
118
119 class Application(object):
120 """
121 A generic WSGI application which can be used to start with Werkzeug in an
122 easy, straightforward way.
123 """
124
125 def __init__(self, name, url_map, session=False, processors=None):
126 # save the name and the URL-map, as it'll be needed later on
127 self.name = name
128 self.url_map = url_map
129 # save the list of processors if supplied
130 self.processors = processors or []
131 # create an instance of the storage
132 if session:
133 self.store = session
134 else:
135 self.store = None
136
137 def __call__(self, environ, start_response):
138 # create a request - with or without session support
139 if self.store is not None:
140 request = Request(environ, self.url_map,
141 session_store=self.store, cookie_name='%s_sid' % self.name)
142 else:
143 request = Request(environ, self.url_map)
144
145 # apply the request processors
146 for processor in self.processors:
147 request = processor.process_request(request)
148
149 try:
150 # find the callback to which the URL is mapped
151 callback, args = request.url_adapter.match(request.path)
152 except (HTTPException, RequestRedirect), e:
153 response = e
154 else:
155 # check all view processors
156 for processor in self.processors:
157 action = processor.process_view(request, callback, (), args)
158 if action is not None:
159 # it is overriding the default behaviour, this is
160 # short-circuiting the processing, so it returns here
161 return action(environ, start_response)
162
163 try:
164 response = callback(request, **args)
165 except Exception, exception:
166 # the callback raised some exception, need to process that
167 for processor in reversed(self.processors):
168 # filter it through the exception processor
169 action = processor.process_exception(request, exception)
170 if action is not None:
171 # the exception processor returned some action
172 return action(environ, start_response)
173 # still not handled by a exception processor, so re-raise
174 raise
175
176 # apply the response processors
177 for processor in reversed(self.processors):
178 response = processor.process_response(request, response)
179
180 # return the completely processed response
181 return response(environ, start_response)
182
183
184 def config_session(self, store, expiration='session'):
185 """
186 Configures the setting for cookies. You can also disable cookies by
187 setting store to None.
188 """
189 self.store = store
190 # expiration=session is the default anyway
191 # TODO: add settings to define the expiration date, the domain, the
192 # path any maybe the secure parameter.
38193
39194
40195 class TemplateNotFound(IOError, LookupError):
70225 return Response(self.render_to_string(*args, **kwargs))
71226
72227 def render_to_string(self, *args, **kwargs):
73 """Load and render a tempalte into a unicode string."""
228 """Load and render a template into a unicode string."""
74229 try:
75230 template_name, args = args[0], args[1:]
76231 except IndexError:
77232 raise TypeError('name of template required')
78233 return self.get_template(template_name).render(*args, **kwargs)
234
235
236 class GenshiTemplateLoader(TemplateLoader):
237 """
238 A unified interface for loading Genshi templates. Actually a quite thin
239 wrapper for Genshi's TemplateLoader.
240
241 It sets some defaults that differ from the Genshi loader, most notably
242 auto_reload is active. All imporant options can be passed through to
243 Genshi.
244 The default output type is 'html', but can be adjusted easily by changing
245 the `output_type` attribute.
246 """
247 def __init__(self, search_path, encoding='utf-8', **kwargs):
248 TemplateLoader.__init__(self, search_path, encoding)
249 # import Genshi here, because we don't want a general Genshi
250 # dependency, only a local one
251 from genshi.template import TemplateLoader as GenshiLoader
252 from genshi.template.loader import TemplateNotFound
253
254 self.not_found_exception = TemplateNotFound
255 # set auto_reload to True per default
256 reload_template = kwargs.pop('auto_reload', True)
257 # get rid of default_encoding as this template loaders overwrites it
258 # with the value of encoding
259 kwargs.pop('default_encoding', None)
260
261 # now, all arguments are clean, pass them on
262 self.loader = GenshiLoader(search_path, default_encoding=encoding,
263 auto_reload=reload_template, **kwargs)
264
265 # the default output is HTML but can be overridden easily
266 self.output_type = 'html'
267 self.encoding = encoding
268
269 def get_template(self, template_name):
270 """Get the template which is at the given name"""
271 try:
272 return self.loader.load(template_name, encoding=self.encoding)
273 except self.not_found_exception, e:
274 # catch the exception raised by Genshi, convert it into a werkzeug
275 # exception (for the sake of consistency)
276 raise TemplateNotFound(template_name)
277
278 def render_to_string(self, template_name, context=None):
279 """Load and render a template into an unicode string"""
280 # create an empty context if no context was specified
281 context = context or {}
282 tmpl = self.get_template(template_name)
283 # render the template into a unicode string (None means unicode)
284 return tmpl.\
285 generate(**context).\
286 render(self.output_type, encoding=None)
0 # -*- coding: utf-8 -*-
1 """
2 inyoka.middlewares.profiler
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Provides a WSGI Profiler middleware for finding bottlenecks.
6
7 :copyright: 2006-2007 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import sys
11 try:
12 try:
13 from cProfile import Profile
14 except ImportError:
15 from profile import Profile
16 from pstats import Stats
17 available = True
18 except ImportError:
19 available = False
20
21
22 class MergeStream(object):
23 """
24 A object that redirects `write` calls to multiple streams.
25 Use this to log to both `sys.stdout` and a file::
26
27 f = file('profiler.log')
28 stream = MergeStream(sys.stdout, f)
29 profiler = ProfilerMiddleware(app, stream)
30 """
31
32 def __init__(self, *streams):
33 if not streams:
34 raise TypeError('at least one stream must be given')
35 self.streams = streams
36
37 def write(self, data):
38 for stream in self.streams:
39 stream.write(data)
40
41
42 class ProfilerMiddleware(object):
43 """
44 Simple profiler middleware
45 """
46
47 def __init__(self, app, stream=None,
48 sort_by=('time', 'calls'), restrictions=()):
49 if not available:
50 raise RuntimeError('the profiler is not available because '
51 'profile or pstat is not installed.')
52 self._app = app
53 self._stream = stream or sys.stdout
54 self._sort_by = sort_by
55 self._restrictions = restrictions
56
57 def __call__(self, environ, start_response):
58 response_body = []
59
60 def catching_start_response(status, headers, exc_info=None):
61 start_response(status, headers, exc_info)
62 return response_body.append
63
64 def runapp():
65 appiter = self._app(environ, catching_start_response)
66 response_body.extend(appiter)
67 if hasattr(appiter, 'close'):
68 appiter.close()
69
70 p = Profile()
71 p.runcall(runapp)
72 body = ''.join(response_body)
73 stats = Stats(p)
74 stats.sort_stats(*self._sort_by)
75
76 self._stream.write('-' * 80)
77 self._stream.write('\nPATH: %r\n' % environ.get('PATH_INFO'))
78 stats.print_stats(*self._restrictions)
79 self._stream.write('-' * 80 + '\n\n')
80
81 return [body]
82
83
84 def make_action(app_factory, hostname='localhost', port=5000,
85 threaded=False, processes=1, stream=None,
86 sort_by=('time', 'calls'), restrictions=()):
87 """
88 Return a new callback for werkzeug scripts that starts a local server
89 for profiling.
90 """
91 def action(hostname=('h', hostname), port=('p', port),
92 threaded=threaded, processes=processes):
93 """Start a new development server."""
94 from werkzeug.serving import run_simple
95 app = ProfilerMiddleware(app_factory(), stream, sort_by, restrictions)
96 run_simple(hostname, port, app, False, None, threaded, processes)
97 return action
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.contrib.reporterstream
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 This module implements a class that can wrap `wsgi.input` in order to
6 be informed about changes of the stream. This is useful if you want
7 to display a progress bar for the upload.
8
9 :copyright: 2007 by Armin Ronacher.
10 :license: BSD, see LICENSE for more details.
11 """
12
13
14 class BaseReporterStream(object):
15 """
16 This class can be used to wrap `wsgi.input` in order to be informed about
17 changes of the stream.
18
19 Usage::
20
21 from random import randrange
22
23 class ReporterStream(BaseReporterStream):
24
25 def __init__(self, environ):
26 super(ReporterStream, self).__init__(environ, 1024 * 16)
27 self.transport_id = randrange(0, 100000)
28
29 def processed(self):
30 s = self.environ['my.session.service']
31 s.store['upload/%s' % self.transport_id] = (self.pos, self.length)
32 s.flush()
33
34
35 And before accessing `request.form` or similar attributes add the stream:
36
37 stream = ReporterStream(environ)
38 environ['wsgi.input'] = stream
39 """
40
41 def __init__(self, environ, threshold):
42 self.threshold = threshold
43 self.length = int(environ.get('CONTENT_LENGTH') or 0)
44 self.pos = 0
45 self.environ = environ
46 self._stream = environ['wsgi.input']
47
48 def processed(self):
49 """Called after pos has changed for threshold or a line was read."""
50
51 def read(self, size=None):
52 length = self.length
53 threshold = self.threshold
54 buffer = []
55
56 if size is None:
57 while self.pos < length:
58 step = min(threshold, length - self.pos)
59 data = self._stream.read(step)
60 self.pos += step
61 self.processed()
62 buffer.append(data)
63 else:
64 read = 0
65 while read < size:
66 step = min(threshold, length - self.pos)
67 step = min(step, size)
68 data = self._stream.read(step)
69 self.pos += step
70 read += step
71 self.processed()
72 buffer.append(data)
73
74 return ''.join(buffer)
75
76 def readline(self, *args):
77 line = self._stream.readline(*args)
78 self.pos += len(line)
79 self.processed()
80 return line
81
82 def readlines(self, hint=None):
83 result = []
84 while self.pos < self.length:
85 result.append(self.readline())
86 return result
0 # -*- coding: utf-8 -*-
1 r"""
2 werkzeug.contrib.securecookie
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 This module implements a cookie that is not alterable from the client
6 because it adds a checksum the server checks for. You can use it as
7 session replacement if all you have is a user id or something to mark
8 a logged in user.
9
10 Keep in mind that the data is still readable from the client as a
11 normal cookie is. However you don't have to store and flush the
12 sessions you have at the server.
13
14 Example usage:
15
16 >>> from werkzeug.contrib.securecookie import SecureCookie
17 >>> x = SecureCookie({"foo": 42, "baz": (1, 2, 3)}, "deadbeef")
18
19 Dumping into a string so that one can store it in a cookie:
20
21 >>> value = x.serialize()
22
23 Loading from that string again:
24
25 >>> x = SecureCookie.unserialize(value, "deadbeef")
26 >>> x["baz"]
27 (1, 2, 3)
28
29 If someone modifies the cookie and the checksum is wrong the unserialize
30 method will fail silently and return a new empty `SecureCookie` object.
31
32 Keep in mind that the values will be visible in the cookie so do not
33 store data in a cookie you don't want the user to see.
34
35 Application Integration
36 =======================
37
38 If you are using the werkzeug request objects you could integrate the
39 secure cookie into your application like this::
40
41 from werkzeug import BaseRequest, cached_property
42 from werkzeug.contrib.securecookie import SecureCookie
43
44 # don' use this key but a different one. you could just use
45 # os.unrandom(20) to get something random
46 SECRET_KEY = '\xfa\xdd\xb8z\xae\xe0}4\x8b\xea'
47
48 class Request(BaseRequest):
49
50 @cached_property
51 def client_session(self):
52 data = self.cookies.get('session_data')
53 if not data:
54 return SecureCookie(secret_key=SECRET_KEY)
55 return SecureCookie.unserialize(data, SECRET_KEY)
56
57 def application(environ, start_response):
58 request = Request(environ, start_response)
59
60 # get a response object here
61 response = ...
62
63 if request.client_session.should_save:
64 session_data = request.client_session.serialize()
65 response.set_cookie('session_data', session_data,
66 httponly=True)
67 return response(environ, start_response)
68
69
70 :copyright: 2007 by Armin Ronacher, Thomas Johansson.
71 :license: BSD, see LICENSE for more details.
72 """
73 try:
74 from hashlib import sha1
75 except ImportError:
76 from sha import new as sha1
77 from binascii import Error as BinASCIIError
78 from datetime import datetime
79 from time import time, mktime, gmtime
80 from random import Random
81 from cPickle import loads, dumps, HIGHEST_PROTOCOL
82 from werkzeug import url_quote_plus, url_unquote_plus
83 from werkzeug.contrib.sessions import ModificationTrackingDict, generate_key
84
85
86 class UnquoteError(Exception):
87 pass
88
89
90 def pickle_quote(value):
91 """Pickle and url encode a value."""
92 result = None
93 for protocol in xrange(HIGHEST_PROTOCOL + 1):
94 data = ''.join(dumps(value, protocol).encode('base64').splitlines()).strip()
95 if result is None or len(result) > len(data):
96 result = data
97 return result
98
99
100 def pickle_unquote(string):
101 """URL decode a string and load it into pickle"""
102 try:
103 return loads(string.decode('base64'))
104 # unfortunately pickle can cause pretty every error here.
105 # if we get one we catch it and convert it into an UnquoteError
106 except Exception, e:
107 raise UnquoteError(str(e))
108
109
110 class SecureCookie(ModificationTrackingDict):
111 """
112 Represents a secure cookie. You can subclass this class and provide
113 an alternative hash method. The import thing is that the hash method
114 is a function with a similar interface to the hashlib. Required
115 methods are update() and digest().
116 """
117 __slots__ = ModificationTrackingDict.__slots__ + ('secret_key', 'new')
118
119 hash_method = sha1
120
121 def __init__(self, data=None, secret_key=None, new=True):
122 ModificationTrackingDict.__init__(self, data or ())
123 self.secret_key = secret_key
124 self.new = new
125
126 def __repr__(self):
127 return '<%s %s%s>' % (
128 self.__class__.__name__,
129 dict.__repr__(self),
130 self.should_save and '*' or ''
131 )
132
133 def should_save(self):
134 """True if the session should be saved."""
135 return self.modified
136 should_save = property(should_save)
137
138 def new_salt(self, secret_key):
139 """Return a new salt Return value must be 4 bytes long."""
140 return generate_key()[:4]
141
142 def serialize(self, expires=None):
143 """
144 Serialize the secure cookie into a string.
145
146 If expires is provided, the session will be automatically invalidated
147 after expiration when you unseralize it. This provides better
148 protection against session cookie theft.
149 """
150 if self.secret_key is None:
151 raise RuntimeError('no secret key defined')
152 if expires:
153 if isinstance(expires, datetime):
154 expires = expires.utctimetuple()
155 elif isinstance(expires, (int, long, float)):
156 expires = gmtime(expires)
157 self['_expires'] = int(mktime(expires))
158 result = []
159 salt = self.new_salt(self.secret_key)
160 hash = self.hash_method(self.secret_key + salt)
161 for key, value in self.iteritems():
162 result.append('%s=%s' % (
163 url_quote_plus(key),
164 pickle_quote(value)
165 ))
166 hash.update('|' + result[-1])
167 return '%s%s?%s' % (
168 salt,
169 hash.digest().encode('base64').strip(),
170 '&'.join(result)
171 )
172
173 def unserialize(cls, string, secret_key):
174 """Load the secure cookie from a serialized string."""
175 if isinstance(string, unicode):
176 string = string.encode('utf-8', 'ignore')
177 salt = string[:4]
178 try:
179 base64_hash, data = string[4:].split('?', 1)
180 except (ValueError, IndexError):
181 items = ()
182 else:
183 items = {}
184 hash = sha1(secret_key + salt)
185 for item in data.split('&'):
186 hash.update('|' + item)
187 if not '=' in item:
188 items = None
189 break
190 key, value = item.split('=', 1)
191 items[url_unquote_plus(key)] = value
192
193 # no parsing error and the hash looks okay, we can now
194 # sercurely unpickle our cookie.
195 try:
196 client_hash = base64_hash.decode('base64')
197 except Exception:
198 items = client_hash = None
199 if items is not None and client_hash == hash.digest():
200 try:
201 for key, value in items.iteritems():
202 items[key] = pickle_unquote(value)
203 except UnquoteError:
204 items = ()
205 else:
206 if '_expires' in items:
207 if time() > items['_expires']:
208 items = ()
209 else:
210 del items['_expires']
211 else:
212 items = ()
213 return cls(items, secret_key, False)
214 unserialize = classmethod(unserialize)
215
216 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
219 set, a new SecureCookie instanced is returned.
220 """
221 data = request.cookies.get(key)
222 if not data:
223 return SecureCookie(secret_key=secret_key)
224 return SecureCookie.unserialize(data, secret_key)
225 load_cookie = classmethod(load_cookie)
226
227 def save_cookie(self, response, key='session', expires=None,
228 session_expires=None, max_age=None, path='/', domain=None,
229 secure=None, httponly=False, force=False):
230 """Saves the SecureCookie in a cookie on response."""
231 if force or self.should_save:
232 data = self.serialize(session_expires or expires)
233 response.set_cookie(key, data, expires=expires, max_age=max_age,
234 path=path, domain=domain, secure=secure,
235 httponly=httponly)
2323 time of the files. It sessions are stored in the database the new()
2424 method should add an expiration timestamp for the session.
2525
26 For better flexibility it's recommended to not use the middleware but the
27 store and session object directly in the application dispatching::
28
29 session_store = FilesystemSessionStore()
30
31 def application(environ, start_response):
32 request = Request(environ)
33 sid = request.cookie.get('cookie_name')
34 if sid is None:
35 request.session = session_store.new()
36 else:
37 request.session = session_store.get(sid)
38 response = get_the_response_object(request)
39 if request.session.should_save:
40 session_store.save(request.session)
41 response.set_cookie('cookie_name', request.session.sid)
42 return response(environ, start_response)
43
2644
2745 :copyright: 2007 by Armin Ronacher.
2846 :license: BSD, see LICENSE for more details.
2947 """
3048 import re
49 import os
3150 from os import path, unlink
3251 from time import time
33 from random import Random
52 from random import Random, random
3453 try:
3554 from hashlib import sha1
3655 except ImportError:
3756 from sha import new as sha1
3857 from cPickle import dump, load, HIGHEST_PROTOCOL
39 from Cookie import SimpleCookie, Morsel
40 from werkzeug.utils import ClosingIterator
58 from werkzeug.utils import ClosingIterator, dump_cookie, parse_cookie
4159
4260
4361 _sha1_re = re.compile(r'^[a-fA-F0-9]{40}$')
4462
4563
46 class Session(dict):
47 """
48 Subclass of a dict that keeps track of direct object changes. Changes
49 in mutable structures are not tracked, for those you have to set
50 `modified` to `True` by hand.
51 """
52
53 __slots__ = ('modified', 'sid', 'new')
54
55 def __init__(self, data, sid, new=False):
56 dict.__init__(self, data)
64 def _urandom():
65 if hasattr(os, 'urandom'):
66 return os.urandom(30)
67 return random()
68
69
70 def generate_key(salt=None):
71 return sha1('%s%s%s' % (salt, time(), _urandom())).hexdigest()
72
73
74 class ModificationTrackingDict(dict):
75 __slots__ = ('modified',)
76
77 def __init__(self, *args, **kwargs):
78 dict.__init__(self, *args, **kwargs)
5779 self.modified = False
58 self.sid = sid
59 self.new = new
6080
6181 def __repr__(self):
6282 return '<%s %s%s>' % (
6383 self.__class__.__name__,
64 dict.__repr__(self),
65 self.should_save and '*' or ''
84 dict.__repr__(self)
6685 )
6786
68 def should_save(self):
69 """True if the session should be saved."""
70 return self.modified or self.new
71 should_save = property(should_save)
72
7387 def copy(self):
74 """Create a flat copy of the session."""
75 result = self.__class__(dict(self), self.sid, self.new)
76 result.modified = self.modified
88 """Create a flat copy of the dict."""
89 missing = object()
90 result = object.__new__(self.__class__)
91 for name in self.__slots__:
92 val = getattr(self, name, missing)
93 if val is not missing:
94 setattr(result, name, val)
7795 return result
96
97 def __copy__(self):
98 return self.copy()
7899
79100 def call_with_modification(f):
80101 def oncall(self, *args, **kw):
100121 del call_with_modification
101122
102123
124 class Session(ModificationTrackingDict):
125 """
126 Subclass of a dict that keeps track of direct object changes. Changes
127 in mutable structures are not tracked, for those you have to set
128 `modified` to `True` by hand.
129 """
130 __slots__ = ModificationTrackingDict.__slots__ + ('sid', 'new')
131
132 def __init__(self, data, sid, new=False):
133 ModificationTrackingDict.__init__(self, data)
134 self.sid = sid
135 self.new = new
136
137 def __repr__(self):
138 return '<%s %s%s>' % (
139 self.__class__.__name__,
140 dict.__repr__(self),
141 self.should_save and '*' or ''
142 )
143
144 def should_save(self):
145 """True if the session should be saved."""
146 return self.modified or self.new
147 should_save = property(should_save)
148
149
103150 class SessionStore(object):
104151 """
105152 Baseclass for all session stores. The Werkzeug contrib module does not
118165
119166 def generate_key(self, salt=None):
120167 """Simple function that generates a new session key."""
121 return sha1('%s|%s|%s' % (Random(salt).random(), time(),
122 salt)).hexdigest()
168 return generate_key(salt)
123169
124170 def new(self):
125171 """Generate a new session."""
196242 the WSGI environ. It automatically sets cookies and restores sessions.
197243
198244 However a middleware is not the preferred solution because it won't be as
199 fast as sessions managed by the application itself.
245 fast as sessions managed by the application itself and will put a key into
246 the WSGI environment only relevant for the application which is against
247 the concept of WSGI.
200248 """
201249
202250 def __init__(self, app, store, cookie_name='session_id',
203 cookie_age=None, cookie_path=None, cookie_domain=None,
204 cookie_secure=None, environ_key='werkzeug.session'):
251 cookie_age=None, cookie_expires=None, cookie_path='/',
252 cookie_domain=None, cookie_secure=None,
253 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.
258 """
205259 self.app = app
206260 self.store = store
207261 self.cookie_name = cookie_name
208262 self.cookie_age = cookie_age
263 self.cookie_expires = cookie_expires
209264 self.cookie_path = cookie_path
210265 self.cookie_domain = cookie_domain
211266 self.cookie_secure = cookie_secure
267 self.cookie_httponly = cookie_httponly
212268 self.environ_key = environ_key
213269
214270 def __call__(self, environ, start_response):
215 cookie = SimpleCookie(environ.get('HTTP_COOKIE', ''))
216 morsel = cookie.get(self.cookie_name, None)
217 if morsel is None:
271 cookie = parse_cookie(environ.get('HTTP_COOKIE', ''))
272 sid = cookie.get(self.cookie_name, None)
273 if sid is None:
218274 session = self.store.new()
219275 else:
220 session = self.store.get(morsel.value)
276 session = self.store.get(sid)
221277 environ[self.environ_key] = session
222278
223279 def injecting_start_response(status, headers, exc_info=None):
224280 if session.should_save:
225 morsel = Morsel()
226 morsel.key = self.cookie_name
227 morsel.coded_value = session.sid
228 if self.cookie_age is not None:
229 morsel['max-age'] = self.cookie_age
230 morsel['expires'] = cookie_date(time() + self.cookie_age)
231 if self.cookie_domain is not None:
232 morsel['domain'] = self.cookie_domain
233 if self.cookie_path is not None:
234 morsel['path'] = self.cookie_path
235 if self.cookie_secure is not None:
236 morsel['secure'] = self.cookie_secure
237 headers.append(tuple(str(morsel).split(':', 1)))
281 self.store.save(session)
282 headers.append(('Set-Cookie', dump_cookie(self.cookie_name,
283 session.sid, self.cookie_age,
284 self.cookie_expires, self.cookie_path,
285 self.cookie_domain, self.cookie_secure,
286 self.cookie_httponly)))
238287 return start_response(status, headers, exc_info)
239288 return ClosingIterator(self.app(environ, injecting_start_response),
240289 lambda: self.store.save_if_modified(session))
0 """
1 werkzeug.contrib.testtools
2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3
4 This Module implements a extended wrappers for simplified Testing
5
6 `TestResponse`
7 a response wrapper wich adds various cached attributes for
8 simplified assertions on various contenttypes
9
10 :copyright: 2007 by Ronny Pfannschmidt.
11 :license: BSD, see LICENSE for more details.
12 """
13 from werkzeug import Response, cached_property, import_string
14
15
16 class ContentAccessors(object):
17
18 def xml(self):
19 """Get an etree if possible."""
20 if 'xml' not in self.mimetype:
21 raise AttributeError(
22 'Not a XML response (Content-Type: %s)'
23 % self.mimetype)
24 for module in ['xml.etree.ElementTree', 'ElementTree',
25 'elementtree.ElementTree']:
26 etree = import_string(module, silent=True)
27 if etree is not None:
28 return etree.XML(self.body)
29 raise RuntimeError('You must have ElementTree installed '
30 'to use TestResponse.xml')
31 xml = cached_property(xml)
32
33 def lxml(self):
34 """Get an lxml etree if possible."""
35 if ('html' not in self.mimetype and 'xml' not in self.mimetype):
36 raise AttributeError('Not an HTML/XML response')
37 from lxml import etree
38 try:
39 from lxml.html import fromstring
40 except ImportError:
41 fromstring = etree.HTML
42 if self.mimetype=='text/html':
43 return fromstring(self.data)
44 return etree.XML(self.data)
45 lxml = cached_property(lxml)
46
47 def json(self):
48 """Get the result of simplejson.loads if possible."""
49 if 'json' not in self.mimetype:
50 raise AttributeError('Not a JSON response')
51 from simplejson import loads
52 return loads(self.data)
53 json = cached_property(json)
54
55
56 class TestResponse(Response, ContentAccessors):
57 """
58 Pass this to `werkzeug.test.Client` for easier unittesting.
59 """
1919
2020
2121 try:
22 system_exceptions = (GeneratorExist,)
22 system_exceptions = (GeneratorExit,)
2323 except NameError:
2424 system_exceptions = ()
2525 system_exceptions += (SystemExit, KeyboardInterrupt)
114114
115115 def format_exception(self, exc_info):
116116 """Format a text/plain traceback."""
117 return self.create_debug_context({}, exc_info, True).plaintb
117 return self.create_debug_context({
118 'wsgi.run_once': True
119 }, exc_info, True).plaintb + '\n'
118120
119121 def create_debug_context(self, environ, exc_info, simple=False):
120122 exception_type, exception_value, tb = exc_info
224226 self.middleware = middleware
225227 self.globals = frame.f_globals
226228 code.InteractiveInterpreter.__init__(self, frame.f_locals)
227 self.prompt = '>>> '
229 self.more = False
228230 self.buffer = []
229231
230232 def runsource(self, source):
233 if isinstance(source, unicode):
234 source = source.encode('utf-8')
235 source = source.rstrip() + '\n'
231236 ThreadedStream.push()
232 prompt = self.prompt
237 prompt = self.more and '... ' or '>>> '
233238 try:
234239 source_to_eval = ''.join(self.buffer + [source])
235240 if code.InteractiveInterpreter.runsource(self,
236241 source_to_eval, '<debugger>', 'single'):
237 self.prompt = '... '
242 self.more = True
238243 self.buffer.append(source)
239244 else:
240 self.prompt = '>>> '
245 self.more = False
241246 del self.buffer[:]
242247 finally:
243 source = source.encode('utf-8')
244248 return prompt + source + ThreadedStream.fetch()
245249
246250 def runcode(self, code):
247251 try:
248252 exec code in self.globals, self.locals
249253 except:
250 self.showtraceback()
251
252 def showtraceback(self):
253 self.write(self.middleware.format_exception(sys.exc_info()))
254 self.write(self.middleware.format_exception(sys.exc_info()))
254255
255256 def write(self, data):
256257 sys.stdout.write(data)
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(exception_type) in $escape(last_frame['basename']) (Werkzeug Debugger)</title>
5 <link rel="stylesheet" href="__traceback__?resource=style.css&amp;mimetype=text/css" type="text/css">
6 <script type="text/javascript" src="__traceback__?resource=jquery.js&amp;mimetype=text/javascript"></script>
7 <script type="text/javascript" src="__traceback__?resource=debugger.js&amp;mimetype=text/javascript"></script>
8 </head>
9 <body>
10 <div class="traceback_wrapper">
11 <h1>$escape(exception_type)</h1>
12 <p class="errormsg">$escape(exception_value)</p>
13
14 <p class="errorline">
15 $escape(last_frame['filename']) in
16 $escape(last_frame['function']),
17 line $last_frame['lineno']
18 </p>
19
20 <h2 onclick="changeTB()" class="tb">Traceback <span>(toggle raw view)</span></h2>
21 <div id="interactive">
22 <p class="text">A problem occurred in your Python WSGI application.
23 Here is the sequence of function calls leading up to the error, in the order
24 they occurred. Activate a code line to toggle context lines.</p>
25
26 <% for num, frame in enumerate(frames) %>
27 <div class="frame" id="frame-$num">
28 <h3 class="fn"><em>$escape(frame['function'])</em> in <tt>$escape(frame['filename'])</tt></h3>
29 <a class="locals" href="javascript:toggleFrameVars($num)">[inspect]</a>
30 <% if evalex %><a class="eval" href="javascript:toggleInterpreter($num)">[console]</a><% endif %>
31 $code_table(frame)
32 $var_table(frame['vars'])
33 <% if evalex %>
34 <form class="exec_code" action="">
35 <pre class="output">[console ready]</pre>
36 <input type="hidden" name="tb" value="$tb_uid">
37 <input type="hidden" name="frame" value="$frame['frame_uid']">
38 <input type="text" name="cmd" class="input" value="">
39 </form>
40 <% endif %>
41 </div>
42 <% endfor %>
43 </div>
44
45 <div id="plain">
46 <p class="text">Here is the plain Python traceback for copy and paste:</p>
47 <pre class="plain">$escape(plaintb)</pre>
48 <p class="text pastebininfo">
49 <a href="javascript:pasteIt()">Create a new Paste</a> with
50 this traceback in the lodgeit pastebin.
51 </p>
52 </div>
53
54 <% if req_vars %>
55 <h2>Request Data</h2>
56 <p class="text">The following list contains all important request variables.
57 Select a header to expand the list.</p>
58 <% for num, (key, info) in enumerate(req_vars) %>
59 <dl>
60 <dt onclick="toggleTableVars($num)">$escape(key)</dt>
61 <dd id="tvar-$num">$var_table(info)</dd>
62 </dl>
63 <% endfor %>
64 <% endif %>
65 </div>
66
67 <div id="footer">
68 Brought to you by <span class="arthur">DON'T PANIC</span>, your friendly
69 Werkzeug powered traceback interpreter.
70 </div>
71 </body>
72 </html>
73
74 <!-- Plain traceback:
75
76 <%py
77 import re
78 print re.sub('-{2,}', '-', plaintb)
79 %>
80 -->
0 <table class="code">
1 <% for line in lines %>
2 <tr class="$line.mode">
3 <td class="lineno">$line.lineno</td>
4 <td class="code">$line.code</td>
5 </tr>
6 <% endfor %>
7 </table>
0 HISTORY = {};
1 HISTORY_POSITIONS = {};
2
3 function changeTB() {
4 $('#interactive').slideToggle('fast');
5 $('#plain').slideToggle('fast');
6 }
7
8 function toggleFrameVars(num) {
9 $('#frame-' + num + ' .vars').slideToggle('fast');
10 }
11
12 function toggleInterpreter(num) {
13 $('#frame-' + num + ' .exec_code').slideToggle('fast', function() {
14 if ($(this).css('display') == 'block')
15 $('input.input', this).focus();
16 });
17 }
18
19 function toggleTableVars(num) {
20 $('#tvar-' + num + ' .vars').slideToggle('fast');
21 }
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 body {
1 font-family: 'Arial', sans-serif;
2 font-size: 15px;
3 margin: 0;
4 padding: 1.3em;
5 background-color: #07131d;
6 }
7
8 * {
9 margin: 0;
10 padding: 0;
11 }
12
13 #footer {
14 margin: 1em;
15 font-size: 13px;
16 letter-spacing: 0.1em;
17 color: #eee;
18 text-align: right;
19 }
20
21 #footer .arthur {
22 font-weight: bold;
23 }
24
25 .traceback_wrapper {
26 margin: 1em;
27 border: 1px solid #1b3042;
28 background-color: #F6F6F6;
29 }
30
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 }
38
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 }
47
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 }
0 <table class="vars">
1 <% if type == 'empty' %>
2 <tr><th>no data given</th></tr>
3 <% elif type == 'simple' %>
4 <tr><td class="value">$escape(value)</td></tr>
5 <% elif type == 'dict' %>
6 <tr><th>Name</th><th>Value</th></tr>
7 <% for key, item in value %>
8 <tr><td class="name">$escape(key)</td><td class="value">$escape(item)</td></tr>
9 <% endfor %>
10 <% elif type == 'list' %>
11 <% for item in value %>
12 <tr><td class="value">$escape(item)</td></tr>
13 <% endfor %>
14 <% endif %>
15 </table>
210210 variables = tb.tb_frame.f_locals
211211
212212 # get filename
213 fn = tb.tb_frame.f_globals.get('__file__')
214 if not fn:
215 fn = os.path.realpath(inspect.getsourcefile(tb) or
216 inspect.getfile(tb))
217 if fn[-4:] in ('.pyc', '.pyo'):
218 fn = fn[:-1]
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]
219222
220223 # module name
221224 modname = tb.tb_frame.f_globals.get('__name__')
228231 pre_context, post_context = [], []
229232 context_line = raw_context_line = context_lineno = None
230233 try:
231 if not loader is None:
232 source = loader.get_source(modname)
234 if not loader is None and hasattr(loader, 'get_source'):
235 source = loader.get_source(modname) or ''
233236 else:
234237 source = file(fn).read()
235238 except:
22 werkzeug.exceptions
33 ~~~~~~~~~~~~~~~~~~~
44
5 This module implements exceptions for the most important HTTP status
6 codes. Each exception is a small WSGI application you can return
7 in views. Simple usage example would look like this::
8
5 This module implements a number of Python exceptions you can raise from
6 within your views to trigger a standard non 200 response.
7
8
9 Usage Example
10 -------------
11
12 ::
13
14 from werkzeug import BaseRequest, responder
915 from werkzeug.exceptions import HTTPException, NotFound
1016
17 def view(request):
18 raise NotFound()
19
20 @responder
1121 def application(environ, start_response):
12 request = Request(environ)
22 request = BaseRequest(environ)
1323 try:
14 response = view_func(request)
15 except NotFound:
16 response = get_not_found_response(request)
24 return view(request)
1725 except HTTPException, e:
18 response = e
19 return response(environ, start_response)
20
21 This module does only implement error classes for status codes from
22 400 onwards. Everything below that is not a real error and should be
23 returned as normal response in the python layer too because of that.
24
25 Unused exception such as 402 don't have their predefined subclasses
26 but you can easily fill the gap by doing that yourself.
27
28 If you're looking for redirection helpers have a look into the utils
29 module which implements a `redirect` function that generates a simple
30 redirect response.
31
32
33 :copyright: 2007 by Armin Ronacher.
26 return e
27
28
29 As you can see from this example those exceptions are callable WSGI
30 applications. Because of Python 2.3 / 2.4 compatibility those do not
31 extend from the response objects but only from the python exception
32 class.
33
34 As a matter of fact they are not Werkzeug response objects. However you
35 can get a response object by calling ``get_response()`` on a HTTP
36 exception.
37
38 Keep in mind that you have to pass an environment to ``get_response()``
39 because some errors fetch additional information from the WSGI
40 environment.
41
42 If you want to hook in a different exception page to say, an 404 status
43 code, you can add a second except for a specific subclass of an error::
44
45 @responder
46 def application(environ, start_response):
47 request = BaseRequest(environ)
48 try:
49 return view(request)
50 except NotFound, e:
51 return not_found(request)
52 except HTTPException, e:
53 return e
54
55
56 :copyright: 2007-2008 by Armin Ronacher.
3457 :license: BSD, see LICENSE for more details.
3558 """
3659 from werkzeug.utils import escape
60 from werkzeug.wrappers import BaseResponse
3761 from werkzeug.http import HTTP_STATUS_CODES
3862
3963
4064 class HTTPException(Exception):
4165 """
42 Baseclass for all HTTP exceptions.
66 Baseclass for all HTTP exceptions. This exception can be called as WSGI
67 application to render a default error page or you can catch the subclasses
68 of it independently and render nicer error messages.
4369 """
4470
4571 code = None
4672 description = None
73
74 def __init__(self, description=None):
75 Exception.__init__(self, '%d %s' % (self.code, self.name))
76 if description is not None:
77 self.description = description
4778
4879 def name(self):
4980 """The status name."""
5586 return self.description
5687
5788 def get_body(self, environ):
89 """Get the HTML body."""
5890 return (
5991 '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n'
6092 '<title>%(code)s %(name)s</title>\n'
6799 }
68100
69101 def get_headers(self, environ):
102 """Get a list of headers."""
70103 return [('Content-Type', 'text/html')]
71104
105 def get_response(self, environ):
106 """Get a response object."""
107 headers = self.get_headers(environ)
108 return BaseResponse(self.get_body(environ), self.code, headers)
109
72110 def __call__(self, environ, start_response):
73 status = '%d %s' % (self.code, self.name)
74 start_response(status, self.get_headers(environ))
75 if environ['REQUEST_METHOD'] == 'HEAD':
76 return ()
77 return [self.get_body(environ)]
111 """Call the exception as WSGI application."""
112 response = self.get_response(environ)
113 return response(environ, start_response)
114
115
116 class _ProxyException(HTTPException):
117 """
118 An http exception that expands renders a WSGI application on error.
119 """
120
121 def __init__(self, response):
122 Exception.__init__(self, 'proxy exception for %r' % response)
123 self.response = response
124
125 def get_response(self, environ):
126 return self.response
78127
79128
80129 class BadRequest(HTTPException):
130 """
131 *400* `Bad Request`
132
133 Raise if the browser send something to the application the application
134 or server cannot handle.
135 """
81136 code = 400
82137 description = (
83138 '<p>The browser (or proxy) sent a request that this server could '
86141
87142
88143 class Unauthorized(HTTPException):
144 """
145 *401* `Unauthorized`
146
147 Raise if the user is not authorized. Also used if you want to use HTTP
148 basic auth.
149 """
89150 code = 401
90151 description = (
91152 '<p>The server could not verify that you are authorized to access '
98159
99160
100161 class Forbidden(HTTPException):
162 """
163 *403* `Forbidden`
164
165 Raise if the user doesn't have the permission for the requested resource
166 but was authenticated.
167 """
101168 code = 403
102169 description = (
103170 '<p>You don\'t have the permission to access the requested resource. '
106173
107174
108175 class NotFound(HTTPException):
176 """
177 *404* `Not Found`
178
179 Raise if a resource does not exist and never existed.
180 """
109181 code = 404
110182 description = (
111183 '<p>The requested URL was not found on the server.</p>'
115187
116188
117189 class MethodNotAllowed(HTTPException):
190 """
191 *405* `Method Not Allowed`
192
193 Raise if the server used a method the resource does not handle. For
194 example `POST` if the resource is view only. Especially useful for REST.
195
196 The first argument for this exception should be a list of allowed methods.
197 Strictly speaking the response would be invalid if you don't provide valid
198 methods in the header which you can do with that list.
199 """
118200 code = 405
201
202 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 """
207 HTTPException.__init__(self, description)
208 self.valid_methods = valid_methods
209
210 def get_headers(self, environ):
211 headers = HTTPException.get_headers(self, environ)
212 if self.valid_methods:
213 headers.append(('Allow', ', '.join(self.valid_methods)))
214 return headers
119215
120216 def get_description(self, environ):
121217 m = escape(environ.get('REQUEST_METHOD', 'GET'))
122 return '<p>The method %s is not allowed for the requeste URL.</p>' % m
218 return '<p>The method %s is not allowed for the requested URL.</p>' % m
219
220
221 class NotAcceptable(HTTPException):
222 """
223 *406* `Not Acceptable`
224
225 Raise if the server cant return any content conforming to the
226 `Accept` headers of the client.
227 """
228 code = 406
229
230 description = (
231 '<p>The resource identified by the request is only capable of '
232 'generating response entities which have content characteristics '
233 'not acceptable according to the accept headers sent in the '
234 'request.</p>'
235 )
123236
124237
125238 class RequestTimeout(HTTPException):
239 """
240 *408* `Request Timeout`
241
242 Raise to signalize a timeout.
243 """
126244 code = 408
127245 description = (
128246 '<p>The server closed the network connection because the browser '
131249
132250
133251 class Gone(HTTPException):
252 """
253 *410* `Gone`
254
255 Raise if a resource existed previously and went away without new location.
256 """
134257 code = 410
135258 description = (
136259 '<p>The requested URL is no longer available on this server and '
140263
141264
142265 class LengthRequired(HTTPException):
266 """
267 *411* `Length Required`
268
269 Raise if the browser submitted data but no ``Content-Length`` header which
270 is required for the kind of processing the server does.
271 """
143272 code = 411
144273 description = (
145274 '<p>A request with this method requires a valid <code>Content-'
148277
149278
150279 class PreconditionFailed(HTTPException):
280 """
281 *412* `Precondition Failed`
282
283 Status code used in combination with ``If-Match``, ``If-None-Match``, or
284 ``If-Unmodified-Since``.
285 """
151286 code = 412
152287 description = (
153288 '<p>The precondition on the request for the URL failed positive '
156291
157292
158293 class RequestEntityTooLarge(HTTPException):
294 """
295 *413* `Request Entity Too Large`
296
297 The status code one should return if the data submitted exceeded a given
298 limit.
299 """
159300 code = 413
160301 description = (
161302 '<p>The data value transmitted exceed the capacity limit.</p>'
163304
164305
165306 class RequestURITooLarge(HTTPException):
307 """
308 *414* `Request URI Too Large`
309
310 Like *413* but for too long URLs.
311 """
166312 code = 414
167313 description = (
168314 '<p>The length of the requested URL exceeds the capacity limit '
171317
172318
173319 class UnsupportedMediaType(HTTPException):
320 """
321 *415* `Unsupported Media Type`
322
323 The status code returned if the server is unable to handle the media type
324 the client transmitted.
325 """
174326 code = 415
175327 description = (
176328 '<p>The server does not support the media type transmitted in '
179331
180332
181333 class InternalServerError(HTTPException):
334 """
335 *500* `Internal Server Error`
336
337 Raise if an internal server error occoured. This is a good fallback if an
338 unknown error occoured in the dispatcher.
339 """
182340 code = 500
183341 description = (
184342 '<p>The server encountered an internal error and was unable to '
188346
189347
190348 class NotImplemented(HTTPException):
349 """
350 *501* `Not Implemented`
351
352 Raise if the application does not support the action requested by the
353 browser.
354 """
191355 code = 501
192356 description = (
193357 '<p>The server does not support the action requested by the '
196360
197361
198362 class BadGateway(HTTPException):
363 """
364 *502* `Bad Gateway`
365
366 If you do proxing in your application you should return this status code
367 if you received an invalid response from the upstream server it accessed
368 in attempting to fulfill the request.
369 """
199370 code = 502
200371 description = (
201372 '<p>The proxy server received an invalid response from an upstream '
204375
205376
206377 class ServiceUnavailable(HTTPException):
378 """
379 *503* `Service Unavailable`
380
381 Status code you should return if a service is temporarily unavailable.
382 """
207383 code = 503
208384 description = (
209 '<p>The serer is temporarily unabel to service your request due to '
385 '<p>The server is temporarily unable to service your request due to '
210386 'maintenance downtime or capacity problems. Please try again '
211387 'later.</p>'
212388 )
389
390
391 def _find_exceptions():
392 rv = {}
393 for name, obj in globals().iteritems():
394 try:
395 if getattr(obj, 'code', None) is not None:
396 rv[obj.code] = obj
397 except TypeError:
398 continue
399 return rv
400 default_exceptions = _find_exceptions()
401 del _find_exceptions
402
403
404 class Aborter(object):
405 """
406 When passed a dict of code -> exception items it can be used as
407 callable that raises exceptions. If the first argument to the
408 callable is a integer it will be looked up in the mapping, if it's
409 a WSGI application it will be raised in a proxy exception.
410
411 The rest of the arguments are forwarded to the exception constructor.
412 """
413
414 def __init__(self, mapping=None, extra=None):
415 if mapping is None:
416 mapping = default_exceptions
417 self.mapping = dict(mapping)
418 if extra is not None:
419 self.mapping.update(extra)
420
421 def __call__(self, code, *args, **kwargs):
422 if not args and not kwargs and not isinstance(code, (int, long)):
423 raise _ProxyException(code)
424 if code not in self.mapping:
425 raise LookupError('no exception for %r' % code)
426 raise self.mapping[code](*args, **kwargs)
427
428 abort = Aborter()
22 werkzeug.http
33 ~~~~~~~~~~~~~
44
5 Various WSGI related helper functions and classes.
6
7
8 :copyright: 2007 by Armin Ronacher.
5 Werkzeug comes with a bunch of utilties that help Werkzeug to deal with
6 HTTP data. Most of the classes and functions provided by this module are
7 used by the wrappers, but they are useful on their own too, especially if
8 the response and request objects are not used.
9
10 This covers some of the more HTTP centric features of WSGI, some other
11 utilities such as cookie handling are documented in the `werkzeug.utils`
12 module.
13
14
15 :copyright: 2007-2008 by Armin Ronacher.
916 :license: BSD, see LICENSE for more details.
1017 """
18 import re
19 import rfc822
20 from datetime import datetime
21 try:
22 from hashlib import md5
23 except ImportError:
24 from md5 import new as md5
1125 try:
1226 set = set
27 frozenset = frozenset
1328 except NameError:
14 from sets import Set as set
15 import re
29 from sets import Set as set, ImmutableSet as frozenset
1630
1731
1832 _accept_re = re.compile(r'([^\s;,]+)(?:[^,]*?;\s*q=(\d*(?:\.\d+)?))?')
19 _token_chars = set("!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
20 '^_`abcdefghijklmnopqrstuvwxyz|~')
33 _token_chars = frozenset("!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
34 '^_`abcdefghijklmnopqrstuvwxyz|~')
2135 _token = '[%s]' % ''.join(_token_chars).replace('-', '\\-')
22 _cachecontrol_re = re.compile(r'(%s+)(?:=(?:(%s+|".*?")))?' % (_token, _token))
36 _cachecontrol_re = re.compile(r'(%s+)(?:=(?:(%s+|".*?")))?' %
37 (_token, _token))
38 _etag_re = re.compile(r'([Ww]/)?(?:"(.*?)"|(.*?))(?:\s*,\s*|$)')
2339
2440
2541 HTTP_STATUS_CODES = {
3450 205: 'Reset Content',
3551 206: 'Partial Content',
3652 207: 'Multi Status',
53 226: 'IM Used', # see RFC 3229
3754 300: 'Multiple Choices',
3855 301: 'Moved Permanently',
3956 302: 'Found',
4360 307: 'Temporary Redirect',
4461 400: 'Bad Request',
4562 401: 'Unauthorized',
46 402: 'Payment Required', # unused
63 402: 'Payment Required', # unused
4764 403: 'Forbidden',
4865 404: 'Not Found',
4966 405: 'Method Not Allowed',
6380 423: 'Locked',
6481 424: 'Failed Dependency',
6582 426: 'Upgrade Required',
66 449: 'Retry With', # propritary MS extension
83 449: 'Retry With', # propritary MS extension
6784 500: 'Internal Server Error',
6885 501: 'Not Implemented',
6986 502: 'Bad Gateway',
7794
7895 class Accept(list):
7996 """
80 Subclass of a list for easier access to the accept values. Sorted
81 by quality, best first.
97 An `Accept` object is just a list subclass for lists of
98 ``(value, quality)`` tuples. It is automatically sorted by quality.
8299 """
83100
84101 def __init__(self, values=()):
93110 list.__init__(self, [(a, b) for b, a in values])
94111
95112 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
116 returned quality is ``0``.
117 """
96118 if isinstance(key, basestring):
97119 for value in self:
98120 if value[0] == key:
110132 )
111133
112134 def index(self, key):
113 """Get the position of en entry or raise IndexError."""
135 """Get the position of en entry or raise `IndexError`."""
114136 rv = self.find(key)
115137 if rv < 0:
116138 raise IndexError(key)
126148 return list.find(self, key)
127149
128150 def values(self):
129 """Just the values, not the qualities."""
151 """Return a list of the values, not the qualities."""
130152 return [x[1] for x in self]
131153
132154 def itervalues(self):
140162 best = property(best)
141163
142164
165 class HeaderSet(object):
166 """
167 Similar to the `ETags` class this implements a set like structure.
168 Unlike `ETags` this is case insensitive and used for vary, allow, and
169 content-language headers.
170
171 If not constructed using the `parse_set_header` function the instanciation
172 works like this:
173
174 >>> hs = HeaderSet(['foo', 'bar', 'baz'])
175 >>> hs
176 HeaderSet(['foo', 'bar', 'baz'])
177 """
178
179 def __init__(self, headers=None, on_update=None):
180 self._headers = list(headers or ())
181 self._set = set([x.lower() for x in self._headers])
182 self.on_update = on_update
183
184 def add(self, header):
185 """Add a new header to the set."""
186 self.update((header,))
187
188 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 """
193 key = header.lower()
194 if key not in self._set:
195 raise IndexError(header)
196 self._set.remove(key)
197 for idx, key in enumerate(self._headers):
198 if key.lower() == header:
199 del self._headers[idx]
200 break
201 if self.on_update is not None:
202 self.on_update(self)
203
204 def update(self, iterable):
205 """Add all the headers from the iterable to the set."""
206 inserted_any = False
207 for header in iterable:
208 key = header.lower()
209 if key not in self._set:
210 self._headers.append(header)
211 self._set.add(key)
212 inserted_any = True
213 if inserted_any and self.on_update is not None:
214 self.on_update(self)
215
216 def discard(self, header):
217 """Like remove but ignores errors."""
218 try:
219 return self.remove(header)
220 except IndexError:
221 pass
222
223 def find(self, header):
224 """Return the index of the header in the set or return -1 if not found."""
225 header = header.lower()
226 for idx, item in enumerate(self._headers):
227 if item.lower() == header:
228 return idx
229 return -1
230
231 def index(self, header):
232 """Return the index of the headerin the set or raise an `IndexError`."""
233 rv = self.find(header)
234 if rv < 0:
235 raise IndexError(header)
236 return rv
237
238 def clear(self):
239 """Clear the set."""
240 self._set.clear()
241 del self._headers[:]
242 if self.on_update is not None:
243 self.on_update(self)
244
245 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.
249
250 If `preserve_casing` is `True` the items in the set returned will
251 have the original case like in the `HeaderSet`, otherwise they will
252 be lowercase.
253 """
254 if preserve_casing:
255 return set(self._headers)
256 return set(self._set)
257
258 def to_header(self):
259 """Convert the header set into an HTTP header string."""
260 return ', '.join(self._headers)
261
262 def __getitem__(self, idx):
263 return self._headers[idx]
264
265 def __delitem__(self, idx):
266 rv = self._headers.pop(idx)
267 self._set.remove(rv.lower())
268 if self.on_update is not None:
269 self.on_update(self)
270
271 def __setitem__(self, idx, value):
272 old = self._headers[idx]
273 self._set.remove(old.lower())
274 self._headers[idx] = value
275 self._set.add(value.lower())
276 if self.on_update is not None:
277 self.on_update(self)
278
279 def __contains__(self, header):
280 return header.lower() in self._set
281
282 def __len__(self):
283 return len(self._set)
284
285 def __iter__(self):
286 return iter(self._headers)
287
288 def __nonzero__(self):
289 return bool(self._set)
290
291 def __str__(self):
292 return self.to_header()
293
294 def __repr__(self):
295 return '%s(%r)' % (
296 self.__class__.__name__,
297 self._headers
298 )
299
300
143301 class CacheControl(dict):
144302 """
145 Wrapper around a dict for cache control headers.
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.
306
307 Because the cache-control directives in the HTTP header use dashes the
308 python descriptors use underscores for that.
309
310 To get a header of the `CacheControl` object again you can convert the
311 object into a string or call the `to_header()` function. If you plan
312 to subclass it and add your own items have a look at the sourcecode for
313 that class.
314
315 The following attributes are exposed:
316
317 `no_cache`, `no_store`, `max_age`, `max_stale`, `min_fresh`,
318 `no_transform`, `only_if_cached`, `public`, `private`, `must_revalidate`,
319 `proxy_revalidate`, and `s_maxage`
146320 """
147321
148322 def cache_property(key, default, type):
249423 dict.__repr__(self)
250424 )
251425
252 # make cache property a staticmethod so that subclasses of
426 # make cache_property a staticmethod so that subclasses of
253427 # `CacheControl` can use it for new properties.
254428 cache_property = staticmethod(cache_property)
255429 del calls_update
256430
257431
432 class ETags(object):
433 """
434 A set that can be used to check if one etag is present in a collection
435 of etags.
436 """
437
438 def __init__(self, strong_etags=None, weak_etags=None, star_tag=False):
439 self._strong = frozenset(not star_tag and strong_etags or ())
440 self._weak = frozenset(weak_etags or ())
441 self.star_tag = star_tag
442
443 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 """
448 rv = set(self._strong)
449 if include_weak:
450 rv.update(self._weak)
451 return rv
452
453 def is_weak(self, etag):
454 """Check if an etag is weak."""
455 return etag in self._weak
456
457 def contains_weak(self, etag):
458 """Check if an etag is part of the set including weak and strong tags."""
459 return self.is_weak(etag) or self.contains(etag)
460
461 def contains(self, etag):
462 """Check if an etag is part of the set ignoring weak tags."""
463 if self.star_tag:
464 return True
465 return etag in self._strong
466
467 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 """
473 etag, weak = unquote_etag(etag)
474 if weak:
475 return self.contains_weak(etag)
476 return self.contains(etag)
477
478 def to_header(self):
479 """Convert the etags set into a HTTP header string."""
480 if self.star_tag:
481 return '*'
482 return ', '.join(['"%s"' % item for item in self.as_set(True)])
483
484 def __call__(self, etag=None, data=None, include_weak=False):
485 if [etag, data].count(None) != 1:
486 raise TypeError('either tag or data required, but at least one')
487 if etag is None:
488 etag = generate_etag(data)
489 if include_weak:
490 if etag in self._weak:
491 return True
492 return etag in self._strong
493
494 def __nonzero__(self):
495 return bool(self.star_tag or self._strong)
496
497 def __str__(self):
498 return self.to_header()
499
500 def __iter__(self):
501 return iter(self._strong)
502
503 def __contains__(self, etag):
504 return self.contains(etag)
505
506 def __repr__(self):
507 return '<%s %r>' % (self.__class__.__name__, str(self))
508
509
258510 def parse_accept_header(value):
259511 """
260512 Parses an HTTP Accept-* header. This does not implement a complete valid
263515 Returns a new `Accept` object (basicly a list of ``(value, quality)``
264516 tuples sorted by the quality with some additional accessor methods)
265517 """
518 if not value:
519 return Accept(None)
266520 result = []
267521 for match in _accept_re.finditer(value):
268522 quality = match.group(2)
274528 return Accept(result)
275529
276530
277 def parse_cache_control_header(value):
531 def parse_cache_control_header(value, on_update=None):
278532 """
279533 Parse a cache control header. The RFC differs between response and
280534 request cache control, this method does not. It's your responsibility
281535 to not use the wrong control statements.
282536 """
537 if not value:
538 return CacheControl(None, on_update)
283539 result = {}
284540 for match in _cachecontrol_re.finditer(value):
285541 name, value = match.group(1, 2)
286542 if value and value[0] == value[-1] == '"':
287543 value = value[1:-1]
288544 result[name] = value
289 return CacheControl(result)
545 return CacheControl(result, on_update)
546
547
548 def parse_set_header(value, on_update=None):
549 """
550 Parse a set like header and return a `HeaderSet` object. The return
551 value is an object that treats the items case insensitive and keeps the
552 order of the items.
553 """
554 if not value:
555 return HeaderSet(None, on_update)
556 return HeaderSet([x.strip() for x in value.split(',')], on_update)
557
558
559 def quote_etag(etag, weak=False):
560 """Quote an etag."""
561 if '"' in etag:
562 raise ValueError('invalid etag')
563 etag = '"%s"' % etag
564 if weak:
565 etag = 'w/' + etag
566 return etag
567
568
569 def unquote_etag(etag):
570 """Unquote a single etag. Return a ``(etag, weak)`` tuple."""
571 if not etag:
572 return None, None
573 etag = etag.strip()
574 weak = False
575 if etag[:2] in ('w/', 'W/'):
576 weak = True
577 etag = etag[2:]
578 if etag[:1] == etag[-1:] == '"':
579 etag = etag[1:-1]
580 return etag, weak
581
582
583 def parse_etags(value):
584 """Parse and etag header. Returns an `ETags` object."""
585 if not value:
586 return ETags()
587 strong = []
588 weak = []
589 end = len(value)
590 pos = 0
591 while pos < end:
592 match = _etag_re.match(value, pos)
593 if match is None:
594 break
595 is_weak, quoted, raw = match.groups()
596 if raw == '*':
597 return ETags(star_tag=True)
598 elif quoted:
599 raw = quoted
600 if is_weak:
601 weak.append(raw)
602 else:
603 strong.append(raw)
604 pos = match.end()
605 return ETags(strong, weak)
606
607
608 def generate_etag(data):
609 """Generate an etag for some data."""
610 return md5(data).hexdigest()
611
612
613 def parse_date(value):
614 """
615 Parse one of the following date formats into a datetime object:
616
617 .. sourcecode:: text
618
619 Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
620 Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
621 Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
622
623 If parsing fails the return value is `None`.
624 """
625 if value:
626 t = rfc822.parsedate_tz(value)
627 if t is not None:
628 return datetime.utcfromtimestamp(rfc822.mktime_tz(t))
629
630
631 def is_resource_modified(environ, etag=None, data=None, last_modified=None):
632 """Convenience method for conditional requests."""
633 if etag is None and data is not None:
634 etag = generate_etag(data)
635 elif data is not None:
636 raise TypeError('both data and etag given')
637 if environ['REQUEST_METHOD'] not in ('GET', 'HEAD'):
638 return False
639
640 unmodified = False
641 if isinstance(last_modified, basestring):
642 last_modified = parse_date(last_modified)
643 modified_since = parse_date(environ.get('HTTP_IF_MODIFIED_SINCE'))
644
645 if modified_since and last_modified and last_modified <= modified_since:
646 unmodified = True
647 if etag:
648 if_none_match = parse_etags(environ.get('HTTP_IF_NONE_MATCH'))
649 if if_none_match:
650 unmodified = if_none_match.contains_raw(etag)
651
652 return not unmodified
22 werkzeug.local
33 ~~~~~~~~~~~~~~
44
5 Special class to manage request local objects as globals. This is a
6 wrapper around `py.magic.greenlet.getcurrent` if available and
7 `threading.currentThread`.
8
9 Use it like this::
10
11 from werkzeug import Local, LocalManager, ClosingIterator
5 Sooner or later you have some things you want to have in every single view
6 or helper function or whatever. In PHP the way to go are global
7 variables. However that is not possible in WSGI applications without a
8 major drawback: As soon as you operate on the global namespace your
9 application is not thread safe any longer.
10
11 The python standard library comes with a utility called "thread locals".
12 A thread local is a global object where you can put stuff on and get back
13 later in a thread safe way. That means whenever you set or get an object
14 to / from a thread local object the thread local object checks in which
15 thread you are and delivers the correct value.
16
17 This however has a few disadvantages. For example beside threads there
18 are other ways to handle concurrency in Python. A very popular approach
19 are greenlets. Also, whether every request gets its own thread is not
20 guaranteed in WSGI. It could be that a request is reusing a thread from
21 before and data is left in the thread local object.
22
23
24 Nutshell
25 --------
26
27 Here a simple example how you can use werkzeug.local::
28
29 from werkzeug import Local, LocalManager
1230
1331 local = Local()
1432 local_manager = LocalManager([local])
1533
16 def view(request):
17 return Response('...')
18
1934 def application(environ, start_response):
20 request = Request(environ)
21 local.request = request
22 response = view(request)
23 return ClosingIterator(response(environ, start_response),
24 local_manager.cleanup)
25
26 Additionally you can use the `make_middleware` middleware factory to
27 accomplish the same::
28
29 from werkzeug import Local, LocalManager, ClosingIterator
30
31 local = Local()
32 local_manager = LocalManager([local])
33
34 def view(request):
35 return Response('...')
36
37 def application(environ, start_response):
38 request = Request(environ)
39 local.request = request
40 return view(request)(environ, start_response)
35 local.request = request = Request(environ)
36 ...
4137
4238 application = local_manager.make_middleware(application)
4339
44 :copyright: 2007 by Armin Ronacher.
40 Now what this code does is binding request to `local.request`. Every
41 other piece of code executed after this assignment in the same context can
42 safely access local.request and will get the same request object. The
43 `make_middleware` method on the local manager ensures that everything is
44 cleaned up after the request.
45
46 The same context means the same greenlet (if you're using greenlets) in
47 the same thread and same process.
48
49 If a request object is not yet set on the local object and you try to
50 access it you will get an `AttributeError`. You can use `getattr` to avoid
51 that::
52
53 def get_request():
54 return getattr(local, 'request', None)
55
56 This will try to get the request or return `None` if the request is not
57 (yet?) available.
58
59 Note that local objects cannot manage themselves, for that you need a local
60 manager. You can pass a local manager multiple locals or add additionals
61 later by appending them to `manager.locals` and everytime the manager
62 cleans up it will clean up all the data left in the locals for this
63 context.
64
65
66 :copyright: 2007-2008 by Armin Ronacher.
4567 :license: BSD, see LICENSE for more details.
4668 """
4769 try:
4971 get_current_greenlet = greenlet.getcurrent
5072 del greenlet
5173 except (RuntimeError, ImportError):
52 get_current_greenlet = lambda: None
74 get_current_greenlet = int
5375 try:
54 from thread import get_ident as get_current_thread
55 from threading import Lock
76 from thread import get_ident as get_current_thread, allocate_lock
5677 except ImportError:
57 from dummy_thread import get_ident as get_current_thread
58 from dummy_threading import Lock
59 from werkzeug.utils import ClosingIterator
60
61
62 def get_ident():
63 """
64 Return a unique number for the current greenlet in the current thread.
65 """
66 return hash((get_current_thread(), get_current_greenlet()))
78 from dummy_thread import get_ident as get_current_thread, allocate_lock
79 from werkzeug.utils import ClosingIterator, _patch_wrapper
80
81
82 # get the best ident function. if greenlets are not installed we can
83 # savely just use the builtin thread function and save a python methodcall
84 # and the cost of caculating a hash.
85 if get_current_greenlet is int:
86 get_ident = get_current_thread
87 else:
88 get_ident = lambda: hash((get_current_thread(), get_current_greenlet()))
6789
6890
6991 class Local(object):
92 __slots__ = ('__storage__', '__lock__')
7093
7194 def __init__(self):
72 self.__dict__.update(
73 __storage={},
74 __lock=Lock()
75 )
95 object.__setattr__(self, '__storage__', {})
96 object.__setattr__(self, '__lock__', allocate_lock())
7697
7798 def __iter__(self):
78 return self.__dict__['__storage'].iteritems()
99 return self.__storage__.iteritems()
79100
80101 def __call__(self, proxy):
81102 """Create a proxy for a name."""
82103 return LocalProxy(self, proxy)
83104
84105 def __getattr__(self, name):
85 self.__dict__['__lock'].acquire()
86 try:
87 ident = get_ident()
88 if ident not in self.__dict__['__storage']:
89 raise AttributeError(name)
106 self.__lock__.acquire()
107 try:
90108 try:
91 return self.__dict__['__storage'][ident][name]
109 return self.__storage__[get_ident()][name]
92110 except KeyError:
93111 raise AttributeError(name)
94112 finally:
95 self.__dict__['__lock'].release()
113 self.__lock__.release()
96114
97115 def __setattr__(self, name, value):
98 self.__dict__['__lock'].acquire()
116 self.__lock__.acquire()
99117 try:
100118 ident = get_ident()
101 storage = self.__dict__['__storage']
119 storage = self.__storage__
102120 if ident in storage:
103121 storage[ident][name] = value
104122 else:
105123 storage[ident] = {name: value}
106124 finally:
107 self.__dict__['__lock'].release()
125 self.__lock__.release()
108126
109127 def __delattr__(self, name):
110 self.__dict__['__lock'].acquire()
111 try:
112 ident = get_ident()
113 if ident not in self.__dict__['__storage']:
114 raise AttributeError(name)
128 self.__lock__.acquire()
129 try:
115130 try:
116 del self.__dict__['__storage'][ident][name]
131 del self.__storage__[get_ident()][name]
117132 except KeyError:
118133 raise AttributeError(name)
119134 finally:
120 self.__dict__['__lock'].release()
135 self.__lock__.release()
121136
122137
123138 class LocalManager(object):
124139 """
125 Manages local objects.
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.
126144 """
127145
128146 def __init__(self, locals=None):
135153 self.locals = [locals]
136154
137155 def get_ident(self):
138 """Returns the current identifier for this context."""
156 """
157 Return the context identifier the local objects use internally for
158 this context. You cannot override this method to change the behavior
159 but use it to link other context local objects (such as SQLAlchemy's
160 scoped sessions) to the Werkzeug locals.
161 """
139162 return get_ident()
140163
141164 def cleanup(self):
142165 """
143 Call this at the request end to clean up all data stored for
144 the current greenlet / thread.
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()`.
145168 """
146169 ident = self.get_ident()
147170 for local in self.locals:
148 d = local.__dict__
149 d['__lock'].acquire()
150 try:
151 d['__storage'].pop(ident, None)
152 finally:
153 d['__lock'].release()
171 local.__storage__.pop(ident, None)
154172
155173 def make_middleware(self, app):
156174 """
158176 request end.
159177 """
160178 def application(environ, start_response):
161 return ClosingIterator(app(environ, start_response),
162 self.cleanup)
179 return ClosingIterator(app(environ, start_response), self.cleanup)
163180 return application
164181
165182 def middleware(self, func):
175192 will have all the arguments copied from the inner application
176193 (name, docstring, module).
177194 """
178 new_func = self.make_middleware(func)
179 try:
180 new_func.__name__ = func.__name__
181 new_func.__doc__ = func.__doc__
182 new_func.__module__ = func.__module__
183 except:
184 pass
185 return new_func
195 return _patch_wrapper(func, self.make_middleware(func))
186196
187197 def __repr__(self):
188198 return '<%s storages: %d>' % (
199209
200210 Example usage::
201211
202 from werkzeug import Local, LocalProxy
212 from werkzeug import Local
203213 l = Local()
204 request = LocalProxy(l, "request")
205 user = LocalProxy(l, "user")
214 request = l('request')
215 user = l('user')
206216
207217 Whenever something is bound to l.user / l.request the proxy objects
208218 will forward all operations. If no object is bound a `RuntimeError`
22 werkzeug.routing
33 ~~~~~~~~~~~~~~~~
44
5 An extensible URL mapper.
6
7 Map creation::
8
9 >>> m = Map([
10 ... # Static URLs
11 ... Rule('/', endpoint='static/index'),
12 ... Rule('/about', endpoint='static/about'),
13 ... Rule('/help', endpoint='static/help'),
14 ... # Knowledge Base
15 ... Subdomain('kb', [
16 ... Rule('/', endpoint='kb/index'),
17 ... Rule('/browse/', endpoint='kb/browse'),
18 ... Rule('/browse/<int:id>/', endpoint='kb/browse'),
19 ... Rule('/browse/<int:id>/<int:page>', endpoint='kb/browse')
20 ... ])
21 ... ], default_subdomain='www')
22
23 URL building::
24
25 >>> c = m.bind('example.com', '/')
26 >>> c.build("kb/browse", dict(id=42))
27 'http://kb.example.com/browse/42/'
28 >>> c.build("kb/browse", dict())
29 'http://kb.example.com/browse/'
30 >>> c.build("kb/browse", dict(id=42, page=3))
31 'http://kb.example.com/browse/42/3'
32 >>> c.build("static/about")
33 u'/about'
34 >>> c.build("static/about", subdomain="kb")
35 'http://www.example.com/about'
36 >>> c.build("static/index", force_external=True)
37 'http://www.example.com/'
38
39 URL matching::
40
41 >>> c = m.bind('example.com', '/')
42 >>> c.match("/")
43 ('static/index', {})
44 >>> c.match("/about")
45 ('static/about', {})
46 >>> c = m.bind('example.com', '/', 'kb')
47 >>> c.match("/", subdomain="kb")
48 ('kb/index', {})
49 >>> c.match("/browse/42/23", subdomain="kb")
50 ('kb/browse', {'id': 42, 'page': 23})
51
52 Exceptions::
53
54 >>> m.match("/browse/42", subdomain="kb")
55 Traceback (most recent call last):
56 ...
57 werkzeug.routing.RequestRedirect: http://kb.example.com/browse/42/
58 >>> m.match("/missing", subdomain="kb")
59 Traceback (most recent call last):
60 ...
61 werkzeug.routing.NotFound: /missing
62 >>> m.match("/missing", subdomain="kb")
63
64
65 :copyright: 2007 by Armin Ronacher, Leif K-Brooks.
5 When it comes to combining multiple controller or view functions (however
6 you want to call them) you need a dispatcher. A simple way would be
7 applying regular expression tests on the ``PATH_INFO`` and call registered
8 callback functions that return the value then.
9
10 This module implements a much more powerful system than simple regular
11 expression matching because it can also convert values in the URLs and
12 build URLs.
13
14 Here a simple example that creates an URL map for an application with
15 two subdomains (www and kb) and some URL rules::
16
17 m = Map([
18 # Static URLs
19 Rule('/', endpoint='static/index'),
20 Rule('/about', endpoint='static/about'),
21 Rule('/help', endpoint='static/help'),
22
23 # Knowledge Base
24 Subdomain('kb', [
25 Rule('/', endpoint='kb/index'),
26 Rule('/browse/', endpoint='kb/browse'),
27 Rule('/browse/<int:id>/', endpoint='kb/browse'),
28 Rule('/browse/<int:id>/<int:page>', endpoint='kb/browse')
29 ])
30 ], default_subdomain='www')
31
32 If the application doesn't use subdomains it's perfectly fine to not set
33 the default subdomain and use the `Subdomain` rule factory. The endpoint
34 in the rules can be anything, for example import paths or unique
35 identitifers. The WSGI application can use those endpoints to get the
36 handler for that URL. It doesn't have to be a string at all but it's
37 recommended.
38
39 Now it's possible to create a URL adapter for one of the subdomains and
40 build URLs:
41
42 >>> c = m.bind('example.com')
43 >>> c.build("kb/browse", dict(id=42))
44 'http://kb.example.com/browse/42/'
45 >>> c.build("kb/browse", dict())
46 'http://kb.example.com/browse/'
47 >>> c.build("kb/browse", dict(id=42, page=3))
48 'http://kb.example.com/browse/42/3'
49 >>> c.build("static/about")
50 u'/about'
51 >>> c.build("static/about", subdomain="kb")
52 'http://www.example.com/about'
53 >>> c.build("static/index", force_external=True)
54 'http://www.example.com/'
55
56 The first argument to bind is the server name *without* the subdomain.
57 Per default it will assume that the script is mounted on the root, but
58 often that's not the case so you can provide the real mount point as
59 second argument:
60
61 >>> c = m.bind('example.com', '/applications/example')
62
63 The third argument can be the subdomain, if not given the default
64 subdomain is used. For more details about binding have a look at the
65 documentation of the `MapAdapter`.
66
67 And here is how you can match URLs:
68
69 >>> c = m.bind('example.com')
70 >>> c.match("/")
71 ('static/index', {})
72 >>> c.match("/about")
73 ('static/about', {})
74 >>> c = m.bind('example.com', '/', 'kb')
75 >>> c.match("/")
76 ('kb/index', {})
77 >>> c.match("/browse/42/23")
78 ('kb/browse', {'id': 42, 'page': 23})
79
80 If matching fails you get a `NotFound` exception, if the rule thinks
81 it's a good idea to redirect (for example because the URL was defined
82 to have a slash at the end but the request was missing that slash) it
83 will raise a `RequestRedirect` exception. Both are subclasses of the
84 `HTTPException` so you can use those errors as responses in the
85 application.
86
87 If matching succeeded but the URL rule was incompatible to the given
88 method (for example there were only rules for `GET` and `HEAD` and
89 routing system tried to match a `POST` request) a `MethodNotAllowed`
90 method is raised.
91
92
93 :copyright: 2007-2008 by Armin Ronacher, Leif K-Brooks,
94 Thomas Johansson.
6695 :license: BSD, see LICENSE for more details.
6796 """
6897 import sys
6998 import re
7099 from urlparse import urljoin
71 from urllib import quote
72
73 from werkzeug.utils import url_encode, redirect, format_string
74 from werkzeug.exceptions import NotFound
100 from itertools import izip
101
102 from werkzeug.utils import url_encode, url_quote, redirect, format_string
103 from werkzeug.exceptions import HTTPException, NotFound, MethodNotAllowed
75104 try:
76105 set
77106 except NameError:
96125 Parse a rule and return it as generator. Each iteration yields tuples in the
97126 form ``(converter, arguments, variable)``. If the converter is `None` it's a
98127 static url part, otherwise it's a dynamic one.
128
129 :internal:
99130 """
100131 pos = 0
101132 end = len(rule)
126157 """
127158 Create a new converter for the given arguments or raise
128159 exception if the converter does not exist.
160
161 :internal:
129162 """
130163 if not name in map.converters:
131164 raise LookupError('the converter %r does not exist' % name)
132165 if args:
133166 storage = type('_Storage', (), {'__getitem__': lambda s, x: x})()
134 args, kwargs = eval('(lambda *a, **kw: (a, kw))(%s)' % args, {}, storage)
167 args, kwargs = eval(u'(lambda *a, **kw: (a, kw))(%s)' % args, {}, storage)
135168 else:
136169 args = ()
137170 kwargs = {}
142175 """
143176 Special exceptions that require the application to redirect, notifies him
144177 about missing urls etc.
145 """
146
147
148 class RequestRedirect(RoutingException):
178
179 :internal:
180 """
181
182
183 class RequestRedirect(HTTPException, RoutingException):
149184 """
150185 Raise if the map requests a redirect. This is for example the case if
151186 `strict_slashes` are activated and an url that requires a leading slash.
152187
153188 The attribute `new_url` contains the absolute desitination url.
154189 """
190 code = 301
155191
156192 def __init__(self, new_url):
193 RoutingException.__init__(self, new_url)
157194 self.new_url = new_url
158 RoutingException.__init__(self, new_url)
159
160 def __call__(self, environ, start_response):
161 return redirect(self.new_url)(environ, start_response)
195
196 def get_response(self, environ):
197 return redirect(self.new_url, 301)
162198
163199
164200 class RequestSlash(RoutingException):
173209 values provided.
174210 """
175211
176 def __init__(self, endpoint, values):
177 LookupError.__init__(self, endpoint)
212 def __init__(self, endpoint, values, method):
213 LookupError.__init__(self, endpoint, values, method)
178214 self.endpoint = endpoint
179215 self.values = values
216 self.method = method
180217
181218
182219 class ValidationError(ValueError):
183220 """
184 Validation error.
221 Validation error. If a rule converter raises this exception the rule
222 does not match the current URL and the next URL is tried.
185223 """
186224
187225
188226 class RuleFactory(object):
189227 """
190 An object that produces Rules when given a Map.
228 As soon as you have more complex URL setups it's a good idea to use rule
229 factories to avoid repetitive tasks. Some of them are builtin, others can
230 be added by subclassing `RuleFactory` and overriding `get_rules`.
191231 """
192232
193233 def get_rules(self, map):
234 """
235 Subclasses of `RuleFactory` have to override this method and return
236 an iterable of rules.
237 """
194238 raise NotImplementedError()
195239
196240
197241 class Subdomain(RuleFactory):
198242 """
199 Collects rules for a given subdomain.
243 All URLs provided by this factory have the subdomain set to a
244 specific domain. For example if you want to use the subdomain for
245 the current language this can be a good setup::
246
247 url_map = Map([
248 Rule('/', endpoint='#select_language'),
249 Subdomain('<string(length=2):lang_code>', [
250 Rule('/', endpoint='index'),
251 Rule('/about', endpoint='about'),
252 Rule('/help', endpoint='help')
253 ])
254 ])
255
256 All the rules except of the ``'#select_language'`` endpoint will now
257 listen on a two letter long subdomain that helds the language code
258 for the current request.
200259 """
201260
202261 def __init__(self, subdomain, rules):
212271
213272 class Submount(RuleFactory):
214273 """
215 Collects rules for a given path.
274 Like `Subdomain` but prefixes the URL rule with a given string::
275
276 url_map = Map([
277 Rule('/', endpoint='index'),
278 Submount('/blog', [
279 Rule('/', endpoint='blog/index'),
280 Rule('/entry/<entry_slug>', endpoint='blog/show')
281 ])
282 ])
283
284 Now the rule ``'blog/show'`` matches ``/blog/entry/<entry_slug>``.
216285 """
217286
218287 def __init__(self, path, rules):
228297
229298 class EndpointPrefix(RuleFactory):
230299 """
231 Prefixes all endpoints with a given string.
300 Prefixes all endpoints (which must be strings for this factory) with
301 another string. This can be useful for sub applications::
302
303 url_map = Map([
304 Rule('/', endpoint='index'),
305 EndpointPrefix('blog/', [Submount('/blog', [
306 Rule('/', endpoint='index'),
307 Rule('/entry/<entry_slug>', endpoint='show')
308 ])])
309 ])
232310 """
233311
234312 def __init__(self, prefix, rules):
246324 """
247325 Returns copies of the rules wrapped and expands string templates in
248326 the endpoint, rule, defaults or subdomain sections.
327
328 Here a small example for such a rule template::
329
330 from werkzeug.routing import Map, Rule, RuleTemplate
331
332 resource = RuleTemplate([
333 Rule('/$name/', endpoint='$name.list'),
334 Rule('/$name/<int:id>', endpoint='$name.show')
335 ])
336
337 url_map = Map([resource(name='user'), resource(name='page')])
338
339 When a rule template is called the keyword arguments are used to
340 replace the placeholders in all the string parameters.
249341 """
250342
251343 def __init__(self, rules):
259351 """
260352 A factory that fills in template variables into rules. Used by
261353 `RuleTemplate` internally.
354
355 :internal:
262356 """
263357
264358 def __init__(self, rules, context):
293387
294388 class Rule(RuleFactory):
295389 """
296 Represents one url pattern.
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.
394
395 `string`
396 Rule strings basically are just normal URL paths with placeholders in
397 the format ``<converter(arguments):name>`` where the converter and the
398 arguments are optional. If no converter is defined the `default`
399 converter is used which means `string` in the normal configuration.
400
401 URL rules that end with a slash are branch URLs, others are leaves.
402 If you have `strict_slashes` enabled (which is the default), all
403 branch URLs that are matched without a trailing slash will trigger a
404 redirect to the same URL with the missing slash appended.
405
406 The converters are defined on the `Map`.
407
408 `endpoint`
409 The endpoint for this rule. This can be anything. A reference to a
410 function, a string, a number etc. The preferred way is using a string
411 as because the endpoint is used for URL generation.
412
413 `defaults`
414 An optional dict with defaults for other rules with the same endpoint.
415 This is a bit tricky but useful if you want to have unique URLs::
416
417 url_map = Map([
418 Rule('/all/', defaults={'page': 1}, endpoint='all_entries'),
419 Rule('/all/page/<int:page>', endpoint='all_entries')
420 ])
421
422 If a user now visits ``http://example.com/all/page/1`` he will be
423 redirected to ``http://example.com/all/``. If `redirect_defaults` is
424 disabled on the `Map` instance this will only affect the URL
425 generation.
426
427 `subdomain`
428 The subdomain rule string for this rule. If not specified the rule
429 only matches for the `default_subdomain` of the map. If the map is
430 not bound to a subdomain this feature is disabled.
431
432 Can be useful if you want to have user profiles on different subdomains
433 and all subdomains are forwarded to your application::
434
435 url_map = Map([
436 Rule('/', subdomain='<username>', endpoint='user/homepage'),
437 Rule('/stats', subdomain='<username>', endpoint='user/stats')
438 ])
439
440 `methods`
441 A sequence of http methods this rule applies to. If not specified, all
442 methods are allowed. For example this can be useful if you want different
443 endpoints for `POST` and `GET`. If methods are defined and the path
444 matches but the method matched against is not in this list or in the
445 list of another rule for that path the error raised is of the type
446 `MethodNotAllowed` rather than `NotFound`.
447
448 `strict_slashes`
449 Override the `Map` setting for `strict_slashes` only for this rule. If
450 not specified the `Map` setting is used.
451
452 `build_only`
453 Set this to true and the rule will never match but will create a URL
454 that can be build. This is useful if you have resources on a subdomain
455 or folder that are not handled by the WSGI application (like static data)
297456 """
298457
299458 def __init__(self, string, defaults=None, subdomain=None, methods=None,
311470 if methods is None:
312471 self.methods = None
313472 else:
314 self.methods = m = []
315 for method in methods:
316 m.append(method.upper())
317 self.methods.sort(lambda a, b: cmp(len(b), len(a)))
473 self.methods = set([x.upper() for x in methods])
318474 self.endpoint = endpoint
319475 self.greediness = 0
320476
325481 self.arguments = set()
326482 self._converters = {}
327483 self._regex = None
484 self._weights = []
485
486 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 """
491 return Rule(self.rule, self.defaults, self.subdomain, self.methods,
492 self.build_only, self.endpoint, self.strict_slashes)
328493
329494 def get_rules(self, map):
330495 yield self
333498 """
334499 Bind the url to a map and create a regular expression based on
335500 the information from the rule itself and the defaults from the map.
501
502 :internal:
336503 """
337504 if self.map is not None:
338505 raise RuntimeError('url rule %r already bound to map %r' %
351518 if converter is None:
352519 regex_parts.append(re.escape(variable))
353520 self._trace.append((False, variable))
521 self._weights.append(len(variable))
354522 else:
355523 convobj = get_converter(map, converter, arguments)
356524 regex_parts.append('(?P<%s>%s)' % (variable, convobj.regex))
357525 self._converters[variable] = convobj
358526 self._trace.append((True, variable))
527 self._weights.append(convobj.weight)
359528 self.arguments.add(str(variable))
360529 if convobj.is_greedy:
361530 self.greediness += 1
362531 if not self.is_leaf:
363532 self._trace.append((False, '/'))
364533
365 if self.methods is None:
366 method_re = '[^>]*'
367 else:
368 method_re = '|'.join([re.escape(x) for x in self.methods])
369
370534 if not self.build_only:
371 regex = r'^%s%s\(%s\)$' % (
535 regex = r'^%s%s$' % (
372536 u''.join(regex_parts),
373537 (not self.is_leaf or not self.strict_slashes) and \
374 '(?<!/)(?P<__suffix__>/?)' or '',
375 method_re
538 '(?<!/)(?P<__suffix__>/?)' or ''
376539 )
377540 self._regex = re.compile(regex, re.UNICODE)
378541
383546
384547 If the rule matches a dict with the converted values is returned,
385548 otherwise the return value is `None`.
549
550 :internal:
386551 """
387552 if not self.build_only:
388553 m = self._regex.search(path)
415580 """
416581 Assembles the relative url for that rule and the subdomain.
417582 If building doesn't work for some reasons `None` is returned.
583
584 :internal:
418585 """
419586 tmp = []
420587 processed = set(self.arguments)
438605 return subdomain, url
439606
440607 def provides_defaults_for(self, rule):
441 """Check if this rule has defaults for a given rule."""
608 """
609 Check if this rule has defaults for a given rule.
610
611 :internal:
612 """
442613 return not self.build_only and self.defaults is not None and \
443614 self.endpoint == rule.endpoint and self != rule and \
444615 self.arguments == rule.arguments
445616
446617 def suitable_for(self, values, method):
447 """Check if the dict of values has enough data for url generation."""
618 """
619 Check if the dict of values has enough data for url generation.
620
621 :internal:
622 """
448623 if self.methods is not None and method not in self.methods:
449624 return False
450625
464639 return True
465640
466641 def match_compare(self, other):
467 """Compare this object with another one for matching"""
642 """
643 Compare this object with another one for matching.
644
645 :internal:
646 """
647 for sw, ow in izip(self._weights, other._weights):
648 if sw > ow:
649 return -1
650 elif sw < ow:
651 return 1
652 if len(self._weights) > len(other._weights):
653 return -1
654 if len(self._weights) < len(other._weights):
655 return 1
468656 if not other.arguments and self.arguments:
469657 return 1
470658 elif other.arguments and not self.arguments:
484672 return 1
485673
486674 def build_compare(self, other):
487 """Compare this object with another one for building."""
675 """
676 Compare this object with another one for building.
677
678 :internal:
679 """
488680 if not other.arguments and self.arguments:
489681 return -1
490682 elif other.arguments and not self.arguments:
546738 """
547739 regex = '[^/]+'
548740 is_greedy = False
741 weight = 100
549742
550743 def __init__(self, map):
551744 self.map = map
554747 return value
555748
556749 def to_url(self, value):
557 return quote(unicode(value).encode(self.map.charset))
750 return url_quote(value, self.map.charset)
558751
559752
560753 class UnicodeConverter(BaseConverter):
561754 """
562 The default converter for all URL parts. Matches one string without a
563 slash in the part. Can also check for the length of that string.
755 This converter is the default converter and accepts any string but
756 only one one path segment. Thus the string can not include a slash.
757
758 Supported arguments:
759
760 - `minlength` - the minimum length of the string. must be greater
761 than 1.
762 - `maxlength` - the maximum length of the string.
763 - `length` - the exact length of that string.
564764 """
565765
566766 def __init__(self, map, minlength=1, maxlength=None, length=None):
579779 self.regex = '[^/]' + length
580780
581781
782 class AnyConverter(BaseConverter):
783 """
784 Matches one of the items provided. Items can either be Python
785 identifiers or unicode strings::
786
787 Rule('/<any(about, help, imprint, u"class"):page_name>')
788 """
789
790 def __init__(self, map, *items):
791 BaseConverter.__init__(self, map)
792 self.regex = '(?:%s)' % '|'.join([re.escape(x) for x in items])
793
794
582795 class PathConverter(BaseConverter):
583796 """
584 Matches a whole path (including slashes)
585 """
586 regex = '[^/].*'
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 """
802 regex = '[^/].*?'
587803 is_greedy = True
804 weight = 50
588805
589806
590807 class NumberConverter(BaseConverter):
591808 """
592809 Baseclass for `IntegerConverter` and `FloatConverter`.
810
811 :internal:
593812 """
594813
595814 def __init__(self, map, fixed_digits=0, min=None, max=None):
616835
617836 class IntegerConverter(NumberConverter):
618837 """
619 Only accepts integers.
838 This converter only accepts integer values::
839
840 Rule('/page/<int:page>')
841
842 Supported arguments:
843
844 - `fixed_digits` - the number of fixed digits in the URL. If you
845 set this to ``4`` for example, the application will only match
846 if the url looks like ``/0001/``. The default is
847 variable length.
848 - `min` - the minimal value.
849 - `max` - the maximal value.
620850 """
621851 regex = r'\d+'
622852 num_convert = int
624854
625855 class FloatConverter(NumberConverter):
626856 """
627 Only accepts floats and integers.
857 This converter only accepts floating point values::
858
859 Rule('/probability/<float:probability>')
860
861 Supported arguments:
862
863 - `min` - the minimal value.
864 - `max` - the maximal value.
628865 """
629866 regex = r'\d+\.\d+'
630867 num_convert = float
635872
636873 class Map(object):
637874 """
638 The base class for all the url maps.
875 The map class stores all the URL rules and some configuration
876 parameters. Some of the configuration values are only stored on the
877 `Map` instance since those affect all rules, others are just defaults
878 and can be overridden for each rule. Note that you have to specify all
879 arguments beside the `rules` as keywords arguments!
639880 """
640881
641882 def __init__(self, rules=None, default_subdomain='', charset='utf-8',
642883 strict_slashes=True, redirect_defaults=True,
643884 converters=None):
644885 """
645 `rules`
646 sequence of url rules for this map.
647
648 `default_subdomain`
649 The default subdomain for rules without a subdomain defined.
650
651 `charset`
652 charset of the url. defaults to ``"utf-8"``
653
654 `strict_slashes`
655 Take care of trailing slashes.
656
657 `redirect_defaults`
658 This will redirect to the default rule if it wasn't visited
659 that way. This helps creating unique urls.
660
661 `converters`
662 A dict of converters that adds additional converters to the
663 list of converters. If you redefine one converter this will
664 override the original one.
886 Initializes the new URL map.
887
888 :param rules: sequence of url rules for this map.
889 :param default_subdomain: The default subdomain for rules without a
890 subdomain defined.
891 :param charset: charset of the url. defaults to ``"utf-8"``
892 :param strict_slashes: Take care of trailing slashes.
893 :param redirect_defaults: This will redirect to the default rule if it
894 wasn't visited that way. This helps creating
895 unique URLs.
896 :param converters: A dict of converters that adds additional converters
897 to the list of converters. If you redefine one
898 converter this will override the original one.
665899 """
666900 self._rules = []
667901 self._rules_by_endpoint = {}
718952 return self.add(rule)
719953
720954 def bind(self, server_name, script_name=None, subdomain=None,
721 url_scheme='http', default_method='GET'):
722 """
723 Return a new map adapter for this request.
955 url_scheme='http', default_method='GET', path_info=None):
956 """
957 Return a new `MapAdapter` with the details specified to the call.
958 Note that `script_name` will default to ``'/'`` if not further
959 specified or `None`. The `server_name` at least is a requirement
960 because the HTTP RFC requires absolute URLs for redirects and so all
961 redirect exceptions raised by Werkzeug will contain the full canonical
962 URL.
963
964 If no path_info is passed to match() it will use the default path
965 info passed to bind. While this doesn't really make sense for
966 manual bind calls, it's useful if you bind a map to a WSGI
967 environment which already contains the path info.
968
969 `subdomain` will default to the `default_subdomain` for this map if
970 no defined. If there is no `default_subdomain` you cannot use the
971 subdomain feature.
724972 """
725973 if subdomain is None:
726974 subdomain = self.default_subdomain
727975 if script_name is None:
728976 script_name = '/'
729977 return MapAdapter(self, server_name, script_name, subdomain,
730 url_scheme, default_method)
978 url_scheme, path_info, default_method)
731979
732980 def bind_to_environ(self, environ, server_name=None, subdomain=None,
733981 calculate_subdomain=False):
734982 """
735 Like `bind` but the required information are pulled from the
736 WSGI environment provided where possible. For some information
737 this won't work (subdomains), if you want that feature you have
738 to provide the subdomain with the `subdomain` variable.
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.
739989
740990 If `subdomain` is `None` but an environment and a server name is
741991 provided it will calculate the current subdomain automatically.
742992 Example: `server_name` is ``'example.com'`` and the `SERVER_NAME`
743993 in the wsgi `environ` is ``'staging.dev.example.com'`` the calculated
744994 subdomain will be ``'staging.dev'``.
745 """
995
996 If the object passed as environ as an environ attribute, the value of
997 this attribute is used instead. This allows you to pass request
998 objects. Additionally `PATH_INFO` added as a default ot the
999 `MapAdapter` so that you don't have to pass the path info to the
1000 match method.
1001 """
1002 if hasattr(environ, 'environ'):
1003 environ = environ.environ
7461004 if server_name is None:
7471005 if 'HTTP_HOST' in environ:
7481006 server_name = environ['HTTP_HOST']
7561014 real_server_name = server_name.split(':', 1)[0].split('.')
7571015 offset = -len(real_server_name)
7581016 if cur_server_name[offset:] != real_server_name:
759 raise ValueError('the server name provided (%r) does not match the '
760 'server name from the WSGI environment (%r)' %
1017 raise ValueError('the server name provided (%r) does not '
1018 'match the server name from the WSGI '
1019 'environment (%r)' %
7611020 (environ['SERVER_NAME'], server_name))
7621021 subdomain = '.'.join(filter(None, cur_server_name[:offset]))
763 return Map.bind(self, server_name, environ.get('SCRIPT_NAME'), subdomain,
764 environ['wsgi.url_scheme'], environ['REQUEST_METHOD'])
1022 return Map.bind(self, server_name, environ.get('SCRIPT_NAME'),
1023 subdomain, environ['wsgi.url_scheme'],
1024 environ['REQUEST_METHOD'], environ.get('PATH_INFO'))
7651025
7661026 def update(self):
7671027 """
7821042 """
7831043
7841044 def __init__(self, map, server_name, script_name, subdomain,
785 url_scheme, default_method):
1045 url_scheme, path_info, default_method):
7861046 self.map = map
7871047 self.server_name = server_name
7881048 if not script_name.endswith('/'):
7901050 self.script_name = script_name
7911051 self.subdomain = subdomain
7921052 self.url_scheme = url_scheme
1053 self.path_info = path_info or u''
7931054 self.default_method = default_method
7941055
795 def dispatch(self, view_func, path_info, method=None):
1056 def dispatch(self, view_func, path_info=None, method=None,
1057 catch_http_exceptions=False):
7961058 """
7971059 Does the complete dispatching process. `view_func` is called with
7981060 the endpoint and a dict with the values for the view. It should
7991061 look up the view function, call it, and return a response object
800 or WSGI application. http exceptions are not catched.
1062 or WSGI application. http exceptions are not catched by default
1063 so that applications can display nicer error messages by just
1064 catching them by hand. If you want to stick with the default
1065 error messages you can pass it ``catch_http_exceptions=True`` and
1066 it will catch the http exceptions.
1067
1068 Here a small example for the dispatch usage::
1069
1070 from werkzeug import Request, Response, responder
1071 from werkzeug.routing import Map, Rule
1072
1073 def on_index(request):
1074 return Response('Hello from the index')
1075
1076 url_map = Map([Rule('/', endpoint='index')])
1077 views = {'index': on_index}
1078
1079 @responder
1080 def application(environ, start_response):
1081 request = Request(environ)
1082 urls = url_map.bind_to_environ(environ)
1083 return urls.dispatch(lambda e, v: views[e](request, **v),
1084 catch_http_exceptions=True)
8011085 """
8021086 try:
8031087 endpoint, args = self.match(path_info, method)
8041088 except RequestRedirect, e:
805 return e
806 return view_func(endpoint, args)
807
808 def match(self, path_info, method=None):
809 """
810 Match a given path_info, script_name and subdomain against the
811 known rules. If the subdomain is not given it defaults to the
812 default subdomain of the map which is usally `www`. Thus if you
813 don't define it anywhere you can safely ignore it.
1089 return e.get_response()
1090 try:
1091 return view_func(endpoint, args)
1092 except HTTPException, e:
1093 if catch_http_exceptions:
1094 return e.get_response()
1095 raise
1096
1097 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:
1102
1103 - you receive a `NotFound` exception that indicates that no URL is
1104 matching. A `NotFound` exception is also a WSGI application you
1105 can call to get a default page not found page (happens to be the
1106 same object as `werkzeug.exceptions.NotFound`)
1107
1108 - you receive a `MethodNotAllowed` exception that indicates that there
1109 is a match for this URL but non for the current request method.
1110 This is useful for RESTful applications.
1111
1112 - you receive a `RequestRedirect` exception with a `new_url`
1113 attribute. This exception is used to notify you about a request
1114 Werkzeug requests by your WSGI application. This is for example the
1115 case if you request ``/foo`` although the correct URL is ``/foo/``
1116 You can use the `RequestRedirect` instance as response-like object
1117 similar to all other subclasses of `HTTPException`.
1118
1119 - you get a tuple in the form ``(endpoint, arguments)`` when there is
1120 a match.
1121
1122 If the path info is not passed to the match method the default path
1123 info of the map is used (defaults to the root URL if not defined
1124 explicitly).
1125
1126 All of the exceptions raised are subclasses of `HTTPException` so they
1127 can be used as WSGI responses. The will all render generic error or
1128 redirect pages.
1129
1130 Here is a small example for matching:
1131
1132 >>> from werkzeug.routing import Map, Rule
1133 >>> m = Map([
1134 ... Rule('/', endpoint='index'),
1135 ... Rule('/downloads/', endpoint='downloads/index'),
1136 ... Rule('/downloads/<int:id>', endpoint='downloads/show')
1137 ... ])
1138 >>> urls = m.bind("example.com", "/")
1139 >>> urls.match("/", "GET")
1140 ('index', {})
1141 >>> urls.match("/downloads/42")
1142 ('downloads/show', {'id': 42})
1143
1144 And here is what happens on redirect and missing URLs:
1145
1146 >>> urls.match("/downloads")
1147 Traceback (most recent call last):
1148 ...
1149 werkzeug.routing.RequestRedirect: http://example.com/downloads/
1150 >>> urls.match("/missing")
1151 Traceback (most recent call last):
1152 ...
1153 werkzeug.routing.NotFound: /missing
8141154 """
8151155 self.map.update()
1156 if path_info is None:
1157 path_info = self.path_info
8161158 if not isinstance(path_info, unicode):
8171159 path_info = path_info.decode(self.map.charset, 'ignore')
818 path = u'%s|/%s(%s)' % (
819 self.subdomain,
820 path_info.lstrip('/'),
821 (method or self.default_method).upper()
822 )
1160 method = (method or self.default_method).upper()
1161 path = u'%s|/%s' % (self.subdomain, path_info.lstrip('/'))
1162 have_match_for = set()
8231163 for rule in self.map._rules:
8241164 try:
8251165 rv = rule.match(path)
8321172 path_info.lstrip('/')
8331173 )))
8341174 if rv is None:
1175 continue
1176 if rule.methods is not None and method not in rule.methods:
1177 have_match_for.update(rule.methods)
8351178 continue
8361179 if self.map.redirect_defaults:
8371180 for r in self.map._rules_by_endpoint[rule.endpoint]:
8471190 path.lstrip('/')
8481191 )))
8491192 return rule.endpoint, rv
1193 if have_match_for:
1194 raise MethodNotAllowed(valid_methods=list(have_match_for))
8501195 raise NotFound()
8511196
1197 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.
1201 """
1202 try:
1203 self.match(path_info, method)
1204 except RequestRedirect:
1205 pass
1206 except NotFound:
1207 return False
1208 return True
1209
8521210 def build(self, endpoint, values=None, method=None, force_external=False):
8531211 """
854 Build a new url hostname relative to the current one. If you
855 reference a resource on another subdomain the hostname is added
856 automatically. You can force external urls by setting
857 `force_external` to `True`.
1212 Building URLs works pretty much the other way round. Instead of
1213 `match` you call `build` and pass it the endpoint and a dict of
1214 arguments for the placeholders.
1215
1216 The `build` function also accepts an argument called `force_external`
1217 which, if you set it to `True` will force external URLs. Per default
1218 external URLs (include the server name) will only be used if the
1219 target URL is on a
1220 different subdomain.
1221
1222 With the same map as in the example above this code generates some
1223 target URLs:
1224
1225 >>> urls.build("index", {})
1226 '/'
1227 >>> urls.build("downloads/show", {'id': 42})
1228 '/downloads/42'
1229 >>> urls.build("downloads/show", {'id': 42}, force_external=True)
1230 'http://example.com/downloads/42'
1231
1232 Because URLs cannot contain non ASCII data you will always get
1233 bytestrings back. Non ASCII characters are urlencoded with the
1234 charset defined on the map instance.
1235
1236 Additional values are converted to unicode and appended to the URL as
1237 URL querystring parameters:
1238
1239 >>> urls.build("index", {'q': 'My Searchstring'})
1240 '/?q=My+Searchstring'
1241
1242 If a rule does not exist when building a `BuildError` exception is
1243 raised.
1244
1245 The build method accepts an argument called `method` which allows you
1246 to specify the method you want to have an URL builded for if you have
1247 different methods for the same endpoint specified.
8581248 """
8591249 self.map.update()
8601250 method = method or self.default_method
8691259 if rv is not None:
8701260 break
8711261 else:
872 raise BuildError(endpoint, values)
1262 raise BuildError(endpoint, values, method)
8731263 subdomain, path = rv
8741264 if not force_external and subdomain == self.subdomain:
8751265 return str(urljoin(self.script_name, path.lstrip('/')))
8821272 ))
8831273
8841274
1275 #: the default converter mapping for the map.
8851276 DEFAULT_CONVERTERS = {
8861277 'default': UnicodeConverter,
8871278 'string': UnicodeConverter,
1279 'any': AnyConverter,
8881280 'path': PathConverter,
8891281 'int': IntegerConverter,
8901282 'float': FloatConverter
00 # -*- coding: utf-8 -*-
1 """
1 r'''
22 werkzeug.script
33 ~~~~~~~~~~~~~~~
44
5 This module provides classes that simplifies the creation of shell
6 scripts. The `Script` class is very basic and does not contain any
7 default actions. The `ManagementScript` class however includes some
8 common actions such as running a WSGI server and starting a python
9 shell.
10
11 This module is quite magical because it uses frame introspection to
12 locate the action callbacks. You should only use it for small
13 manage scripts and similar things.
14
15
16 :copyright: 2007 by Armin Ronacher.
5 Most of the time you have recurring tasks while writing an application
6 such as starting up an interactive python interpreter with some prefilled
7 imports, starting the development server, initializing the database or
8 something similar.
9
10 For that purpose werkzeug provides the `werkzeug.script` module which
11 helps you writing such scripts.
12
13
14 Basic Usage
15 -----------
16
17 The following snippet is roughly the same in every werkzeug script::
18
19 #!/usr/bin/env python
20 # -*- coding: utf-8 -*-
21 from werkzeug import script
22
23 # actions go here
24
25 if __name__ == '__main__':
26 script.run()
27
28 Starting this script now does nothing because no actions are defined.
29 An action is a function in the same module starting with ``"action_"``
30 which takes a number of arguments where every argument has a default. The
31 type of the default value specifies the type of the argument.
32
33 Arguments can then be passed by position or using ``--name=value`` from
34 the shell.
35
36 Because a runserver and shell command is pretty common there are two
37 factory functions that create such commands::
38
39 def make_app():
40 from yourapplication import YourApplication
41 return YourApplication(...)
42
43 action_runserver = script.make_runserver(make_app, use_reloader=True)
44 action_shell = script.make_shell(lambda: {'app': make_app()})
45
46
47 Using The Scripts
48 -----------------
49
50 The script from above can be used like this from the shell now:
51
52 .. sourcecode:: text
53
54 $ ./manage.py --help
55 $ ./manage.py runserver localhost 8080 --debugger --no-reloader
56 $ ./manage.py runserver -p 4000
57 $ ./manage.py shell
58
59 As you can see it's possible to pass parameters as positional arguments
60 or as named parameters, pretty much like Python function calls.
61
62
63 :copyright: 2007-2008 by Armin Ronacher, Thomas Johansson.
1764 :license: BSD, see LICENSE for more details.
18 """
65 '''
1966 import sys
2067 import inspect
2168 import getopt
69 from os.path import basename
2270 try:
2371 set = set
2472 except NameError:
4189 }
4290
4391
44 def run(namespace=None, action_prefix='action_'):
92 def run(namespace=None, action_prefix='action_', args=None):
4593 """
4694 Run the script. Participating actions are looked up in the callers
4795 namespace if no namespace is given, otherwise in the dict provided.
5199 """
52100 if namespace is None:
53101 namespace = sys._getframe(1).f_locals
54 actions = {}
55 for key, value in namespace.iteritems():
56 if key.startswith(action_prefix):
57 actions[key[len(action_prefix):]] = analyse_action(value)
58
59 args = sys.argv[1:]
102 actions = find_actions(namespace, action_prefix)
103
104 if args is None:
105 args = sys.argv[1:]
60106 if not args or args[0] in ('-h', '--help'):
61107 return print_usage(actions)
62108 elif args[0] not in actions:
72118 real_arg = arg.replace('-', '_')
73119 converter = converters[option_type]
74120 if shortcut:
75 formatstring += shortcut + ':'
121 formatstring += shortcut
122 if not isinstance(default, bool):
123 formatstring += ':'
76124 key_to_arg['-' + shortcut] = real_arg
77 long_options.append(arg + '=')
125 long_options.append(isinstance(default, bool) and arg or arg + '=')
78126 key_to_arg['--' + arg] = real_arg
79127 key_to_arg[idx] = real_arg
80128 conv[real_arg] = converter
101149 arg = key_to_arg[key]
102150 if arg in specified_arguments:
103151 fail('Argument \'%s\' is specified twice' % arg)
152 if arg.startswith('no_'):
153 value = 'no'
154 elif not value:
155 value = 'yes'
104156 try:
105157 arguments[arg] = conv[arg](value)
106158 except ValueError:
107159 fail('Invalid value for \'%s\': %s' % (key, value))
108160
161 newargs = {}
162 for k, v in arguments.iteritems():
163 newargs[k.startswith('no_') and k[3:] or k] = v
164 arguments = newargs
109165 return func(**arguments)
110166
111167
115171 sys.exit(code)
116172
117173
174 def find_actions(namespace, action_prefix):
175 """Find all the actions in the namespace."""
176 actions = {}
177 for key, value in namespace.iteritems():
178 if key.startswith(action_prefix):
179 actions[key[len(action_prefix):]] = analyse_action(value)
180 return actions
181
182
118183 def print_usage(actions):
119184 """Print the usage information. (Help screen)"""
120185 actions = actions.items()
121186 actions.sort()
122 print 'usage: %s <action> [<options>]' % sys.argv[0]
123 print ' %s --help' % sys.argv[0]
187 print 'usage: %s <action> [<options>]' % basename(sys.argv[0])
188 print ' %s --help' % basename(sys.argv[0])
124189 print
125190 print 'actions:'
126191 for name, (func, doc, arguments) in actions:
129194 print ' %s' % line
130195 if arguments:
131196 print
132 for arg, shortcut, default, type in arguments:
133 print ' %-30s%-10s%s' % (
134 (shortcut and '-%s, ' % shortcut or '') + '--' + arg,
135 type,
136 default
137 )
197 for arg, shortcut, default, argtype in arguments:
198 if isinstance(default, bool):
199 print ' %s' % (
200 (shortcut and '-%s, ' % shortcut or '') + '--' + arg
201 )
202 else:
203 print ' %-30s%-10s%s' % (
204 (shortcut and '-%s, ' % shortcut or '') + '--' + arg,
205 argtype, default
206 )
138207 print
139208
140209
157226 else:
158227 shortcut, default = definition
159228 argument_type = argument_types[type(default)]
229 if isinstance(default, bool) and default is True:
230 arg = 'no-' + arg
160231 arguments.append((arg.replace('_', '-'), shortcut,
161232 default, argument_type))
162233 return func, description, arguments
169240 """
170241 if banner is None:
171242 banner = 'Interactive Werkzeug Shell'
172 def action(use_ipython=use_ipython):
243 def action(ipython=use_ipython):
173244 """Start a new interactive python session."""
174245 namespace = init_func()
175 if use_ipython:
246 if ipython:
176247 try:
177248 import IPython
178249 except ImportError:
193264 Returns an action callback that spawns a new wsgiref server.
194265 """
195266 def action(hostname=('h', hostname), port=('p', port),
196 use_reloader=use_reloader, use_debugger=use_debugger,
197 use_evalex=use_evalex, threaded=threaded, processes=processes):
267 reloader=use_reloader, debugger=use_debugger,
268 evalex=use_evalex, threaded=threaded, processes=processes):
198269 """Start a new development server."""
199270 from werkzeug.serving import run_simple
200271 app = app_factory()
201 if use_debugger:
272 if debugger:
202273 from werkzeug.debug import DebuggedApplication
203 app = DebuggedApplication(app, use_evalex)
204 run_simple(hostname, port, app, use_reloader, None, threaded,
274 app = DebuggedApplication(app, evalex)
275 run_simple(hostname, port, app, reloader, None, 1, threaded,
205276 processes)
206277 return action
22 werkzeug.serving
33 ~~~~~~~~~~~~~~~~
44
5 This module wraps the `wsgiref` module so that it reloads code
6 automatically. Works with any WSGI application but it won't help in
7 non `wsgiref` environments. Use it only for development.
8
9 Usage::
10
11 from werkzeug.serving import run_simple
5 There are many ways to serve a WSGI application. While you're developing
6 it you usually don't want a full blown webserver like Apache but a simple
7 standalone one. With Python 2.5 onwards there is the `wsgiref`_ server in
8 the standard library. If you're using older versions of Python you can
9 download the package from the cheeseshop.
10
11 However there are some caveats. Sourcecode won't reload itself when
12 changed and each time you kill the server using ``^C`` you get an
13 `KeyboardInterrupt` error. While the latter is easy to solve the first
14 one can be a pain in the ass in some situations.
15
16 Because of that Werkzeug ships a small wrapper over `wsgiref` that spawns
17 the WSGI application in a subprocess and automatically reloads the
18 application if a module was changed.
19
20 The easiest way is creating a small ``start-myproject.py`` that runs the
21 application::
22
23 #!/usr/bin/env python
24 # -*- coding: utf-8 -*-
1225 from myproject import make_app
13 run_simple('localhost', 8080, make_app())
14
15 :copyright: 2007 by Armin Ronacher.
26 from werkzeug import run_simple
27
28 app = make_app(...)
29 run_simple('localhost', 8080, app, use_reloader=True)
30
31 You can also pass it a `extra_files` keyword argument with a list of
32 additional files (like configuration files) you want to observe.
33
34 For bigger applications you should consider using `werkzeug.script`
35 instead of a simple start file.
36
37 .. _wsgiref: http://cheeseshop.python.org/pypi/wsgiref
38
39
40 :copyright: 2007-2008 by Armin Ronacher.
1641 :license: BSD, see LICENSE for more details.
1742 """
1843 import os
2045 import sys
2146 import time
2247 import thread
23 from wsgiref.simple_server import ServerHandler, WSGIRequestHandler, \
24 WSGIServer
48 from itertools import chain
49 from werkzeug.utils import _log
50 try:
51 from wsgiref.simple_server import ServerHandler, WSGIRequestHandler, \
52 WSGIServer
53 have_wsgiref = True
54 except ImportError:
55 have_wsgiref = False
2556 from SocketServer import ThreadingMixIn, ForkingMixIn
2657
2758
28 class BaseRequestHandler(WSGIRequestHandler):
29 """
30 Subclass of the normal request handler that thinks it is
31 threaded or something like that. The default wsgiref handler
32 has wrong information so we need this class.
33 """
34 multithreaded = False
35 multiprocess = False
36 _handler_class = None
37
38 def get_handler(self):
39 handler = self._handler_class
40 if handler is None:
41 class handler(ServerHandler):
42 wsgi_multithread = self.multithreaded
43 wsgi_multiprocess = self.multiprocess
44 self._handler_class = handler
45
46 rv = handler(self.rfile, self.wfile, self.get_stderr(),
47 self.get_environ())
48 rv.request_handler = self
49 return rv
50
51 def handle(self):
52 self.raw_requestline = self.rfile.readline()
53 if self.parse_request():
54 self.get_handler().run(self.server.get_app())
55
56
57 def make_server(host, port, app=None, threaded=False, processes=1):
59 if have_wsgiref:
60 class BaseRequestHandler(WSGIRequestHandler):
61 """
62 Subclass of the normal request handler that thinks it is
63 threaded or something like that. The default wsgiref handler
64 has wrong information so we need this class.
65 """
66 multithreaded = False
67 multiprocess = False
68 _handler_class = None
69
70 def get_handler(self):
71 handler = self._handler_class
72 if handler is None:
73 class handler(ServerHandler):
74 wsgi_multithread = self.multithreaded
75 wsgi_multiprocess = self.multiprocess
76 self._handler_class = handler
77
78 rv = handler(self.rfile, self.wfile, self.get_stderr(),
79 self.get_environ())
80 rv.request_handler = self
81 return rv
82
83 def handle(self):
84 self.raw_requestline = self.rfile.readline()
85 if self.parse_request():
86 self.get_handler().run(self.server.get_app())
87
88 def log_request(self, code='-', size='-'):
89 _log('info', '%s -- [%s] %s %s',
90 self.address_string(),
91 self.requestline,
92 code,
93 size
94 )
95
96 def log_error(self, format, *args):
97 _log('error', 'Error: %s', format % args)
98
99 def log_message(self, format, *args):
100 _log('info', format, args)
101
102
103 def make_server(host, port, app=None, threaded=False, processes=1,
104 request_handler=None):
58105 """
59106 Create a new wsgiref server that is either threaded, or forks
60107 or just processes one request after another.
61108 """
109 if not have_wsgiref:
110 raise RuntimeError('All the Werkzeug serving features require '
111 'an installed wsgiref library.')
112 request_handler = request_handler or BaseRequestHandler
62113 if threaded and processes > 1:
63114 raise ValueError("cannot have a multithreaded and "
64115 "multi process server.")
65116 elif threaded:
66 class handler(BaseRequestHandler):
117 class request_handler(request_handler):
67118 multithreaded = True
68119 class server(ThreadingMixIn, WSGIServer):
69120 pass
70121 elif processes > 1:
71 class handler(BaseRequestHandler):
122 class request_handler(request_handler):
72123 multiprocess = True
73124 max_children = processes - 1
74125 class server(ForkingMixIn, WSGIServer):
75126 pass
76127 else:
77 handler = BaseRequestHandler
78128 server = WSGIServer
79 srv = server((host, port), handler)
129 srv = server((host, port), request_handler)
80130 srv.set_app(app)
81131 return srv
82132
83133
84 def reloader_loop(extra_files):
134 def reloader_loop(extra_files=None, interval=1):
85135 """When this function is run from the main thread, it will force other
86136 threads to exit when any modules currently loaded change.
87137
138 Copyright notice. This function is based on the autoreload.py from
139 the CherryPy trac which originated from WSGIKit which is now dead.
140
88141 :param extra_files: a list of additional files it should watch.
89142 """
143 def iter_module_files():
144 for module in sys.modules.values():
145 filename = getattr(module, '__file__', None)
146 if filename:
147 while not os.path.isfile(filename):
148 filename = os.path.dirname(filename)
149 if not filename:
150 break
151 else:
152 if filename[-4:] in ('.pyc', '.pyo'):
153 filename = filename[:-1]
154 yield filename
155
90156 mtimes = {}
91 while True:
92 for filename in filter(None, [getattr(module, '__file__', None)
93 for module in sys.modules.values()] +
94 extra_files):
95 while not os.path.isfile(filename):
96 filename = os.path.dirname(filename)
97 if not filename:
98 break
99 if not filename:
100 continue
101
102 if filename[-4:] in ('.pyc', '.pyo'):
103 filename = filename[:-1]
104
157 while 1:
158 for filename in chain(iter_module_files(), extra_files or ()):
105159 try:
106160 mtime = os.stat(filename).st_mtime
107161 except OSError:
108162 continue
109163
110 if filename not in mtimes:
164 old_time = mtimes.get(filename)
165 if old_time is None:
111166 mtimes[filename] = mtime
112167 continue
113 if mtime > mtimes[filename]:
168 elif mtime > old_time:
169 _log('info', ' * Detected change in %r, reloading' % filename)
114170 sys.exit(3)
115 time.sleep(1)
171 time.sleep(interval)
116172
117173
118174 def restart_with_reloader():
121177 but running the reloader thread.
122178 """
123179 while 1:
124 print '* Restarting with reloader...'
180 _log('info', ' * Restarting with reloader...')
125181 args = [sys.executable] + sys.argv
126182 if sys.platform == 'win32':
127183 args = ['"%s"' % arg for arg in args]
132188 return exit_code
133189
134190
135 def run_with_reloader(main_func, extra_watch):
191 def run_with_reloader(main_func, extra_files=None, interval=1):
136192 """
137193 Run the given function in an independent python interpreter.
138194 """
139195 if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
140196 thread.start_new_thread(main_func, ())
141197 try:
142 reloader_loop(extra_watch)
198 reloader_loop(extra_files, interval)
143199 except KeyboardInterrupt:
144200 return
145201 try:
149205
150206
151207 def run_simple(hostname, port, application, use_reloader=False,
152 extra_files=None, threaded=False, processes=1):
153 """
154 Start an application using wsgiref and with an optional reloader.
208 extra_files=None, reloader_interval=1, threaded=False,
209 processes=1, request_handler=None):
210 """
211 Start an application using wsgiref and with an optional reloader. This
212 wraps `wsgiref` to fix the wrong default reporting of the multithreaded
213 WSGI variable and adds optional multithreading and fork support.
214
215 :param hostname: The host for the application. eg: ``'localhost'``
216 :param port: The port for the server. eg: ``8080``
217 :param application: the WSGI application to execute
218 :param use_reloader: should the server automatically restart the python
219 process if modules were changed?
220 :param extra_files: a list of files the reloader should listen for
221 additionally to the modules. For example configuration
222 files.
223 :param reloader_interval: the interval for the reloader in seconds.
224 :param threaded: should the process handle each request in a separate
225 thread?
226 :param processes: number of processes to spawn.
227 :param request_handler: optional parameter that can be used to replace
228 the default wsgiref request handler. Have a look
229 at the `werkzeug.serving` sourcecode for more
230 details.
155231 """
156232 def inner():
157233 srv = make_server(hostname, port, application, threaded,
158 processes)
234 processes, request_handler)
159235 try:
160236 srv.serve_forever()
161237 except KeyboardInterrupt:
162238 pass
163239
164240 if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
165 print '* Running on http://%s:%d/' % (hostname or '0.0.0.0', port)
241 display_hostname = hostname or '127.0.0.1'
242 _log('info', ' * Running on http://%s:%d/', display_hostname, port)
166243 if use_reloader:
167 # Create and destroy a socket so that any exceptions are raised before we
168 # spawn a separate Python interpreter and loose this ability.
244 # Create and destroy a socket so that any exceptions are raised before
245 # we spawn a separate Python interpreter and loose this ability.
169246 test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
170247 test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
171248 test_socket.bind((hostname, port))
172249 test_socket.close()
173 run_with_reloader(inner, extra_files or [])
250 run_with_reloader(inner, extra_files, reloader_interval)
174251 else:
175252 inner()
22 werkzeug.templates
33 ~~~~~~~~~~~~~~~~~~
44
5 A very simple Python "Template Engine". In fact it just recognizes
6 PHP like blocks and executes the code in them::
5 This template engine recognizes ASP/PHP like blocks and executes the code
6 in them::
77
88 t = Template('<% for u in users %>${u['username']}\n<% endfor %>')
99 t.render(users=[{'username': 'John'},
2929 Syntax Elements
3030 ---------------
3131
32 Printing Variables::
32 Printing Variables:
33
34 .. sourcecode:: text
3335
3436 $variable
3537 $variable.attribute[item](some, function)(calls)
3840 Keep in mind that the print statement adds a newline after the call or
3941 a whitespace if it ends with a comma.
4042
41 For Loops::
43 For Loops:
44
45 .. sourcecode:: text
4246
4347 <% for item in seq %>
4448 ...
4549 <% endfor %>
4650
47 While Loops::
51 While Loops:
52
53 .. sourcecode:: text
4854
4955 <% while expression %>
5056 <%py break / continue %>
5157 <% endwhile %>
5258
53 If Conditions::
59 If Conditions:
60
61 .. sourcecode:: text
5462
5563 <% if expression %>
5664 ...
6068 ...
6169 <% endif %>
6270
63 Python Expressions::
71 Python Expressions:
72
73 .. sourcecode:: text
6474
6575 <%py
6676 ...
7181 %>
7282
7383 Note on python expressions: You cannot start a loop in a python block
74 and continue it in another one. This example does *not* work::
84 and continue it in another one. This example does *not* work:
85
86 .. sourcecode:: text
7587
7688 <%python
7789 for item in seq:
7890 %>
7991 ...
8092
81 Comments::
93 Comments:
94
95 .. sourcecode:: text
8296
8397 <%#
8498 This is a comment
91105 If you try to access a missing variable you will get back an `Undefined`
92106 object. You can iterate over such an object or print it and it won't
93107 fail. However every other operation will raise an error. To test if a
94 variable is undefined you can use this expression::
108 variable is undefined you can use this expression:
109
110 .. sourcecode:: text
95111
96112 <% if variable is Undefined %>
97113 ...
98114 <% endif %>
99115
100 Copyright notice: The `parse_data` method uses the string interpolation
101 algorithm by Ka-Ping Yee which originally was part of `ltpl20.py`_
102
103 .. _ltipl20.py: http://lfw.org/python/Itpl20.py
104
105
106 :copyright: 2006 by Armin Ronacher, Ka-Ping Yee.
116
117 :copyright: 2006-2008 by Armin Ronacher, Ka-Ping Yee.
107118 :license: BSD License.
108119 """
109120 import sys
110121 import re
111122 import __builtin__ as builtins
112123 from compiler import ast, parse
124 from compiler.consts import SC_LOCAL, SC_GLOBAL, SC_FREE, SC_CELL
113125 from compiler.pycodegen import ModuleCodeGenerator
114126 from tokenize import PseudoToken
115127 from werkzeug import utils
128
129
130 # Copyright notice: The `parse_data` method uses the string interpolation
131 # algorithm by Ka-Ping Yee which originally was part of `ltpl20.py`_
132 #
133 # .. _ltipl20.py: http://lfw.org/python/Itpl20.py
116134
117135
118136 token_re = re.compile('%s|%s|%s(?i)' % (
129147 '__repr__': lambda x: 'Undefined',
130148 '__str__': lambda x: ''
131149 })()
150 runtime_vars = dict.fromkeys(('Undefined', '__to_unicode', '__context',
151 '__write', '__write_many'))
132152
133153
134154 def call_stmt(func, args, lineno):
168188 node.filename = filename
169189 if node.__class__ in (ast.Printnl, ast.Print):
170190 node.dest = ast.Name('__context')
191 elif node.__class__ is ast.Const and isinstance(node.value, str):
192 try:
193 node.value.decode('ascii')
194 except UnicodeError:
195 node.value = node.value.decode('utf-8')
171196 nodes.extend(node.getChildNodes())
172197 return root
173198
191216 raise TemplateSyntaxError(msg, self.filename, self.lineno)
192217
193218 def parse_python(self, expr, type='exec'):
219 if isinstance(expr, unicode):
220 expr = '\xef\xbb\xbf' + expr.encode('utf-8')
194221 try:
195222 node = parse(expr, type)
196223 except SyntaxError, e:
222249 elif name == 'if':
223250 add(self.parse_if(args))
224251 else:
225 self.fail('unknown directive %S' % name)
252 self.fail('unknown directive %s' % name)
226253 if needle:
227254 self.fail('unexpected end of template')
228255 return ast.Stmt(result, lineno=start_lineno)
229256
230257 def parse_loop(self, args, type):
231258 rv = self.parse_python('%s %s: pass' % (type, args), 'exec').nodes[0]
232 tag, value, rv.body = self.parse(('end' + type,))
259 tag, value, rv.body = self.parse(('end' + type, 'else'))
233260 if value:
234 self.fail('unexpected data after end' + type)
261 self.fail('unexpected data after ' + tag)
262 if tag == 'else':
263 tag, value, rv.else_ = self.parse(('end' + type,))
264 if value:
265 self.fail('unexpected data after else')
235266 return rv
236267
237268 def parse_if(self, args):
345376 self._buffer = []
346377 self._write = self._buffer.append
347378 _extend = self._buffer.extend
348 self._namespace.update(
379 self.runtime = dict(
349380 Undefined=undefined,
350381 __to_unicode=self.to_unicode,
351382 __context=self,
367398 return rv.encode(self.encoding, self.errors)
368399 return rv
369400
370 def __getitem__(self, key):
371 if key in self._namespace:
401 def __getitem__(self, key, default=undefined):
402 try:
372403 return self._namespace[key]
373 return getattr(builtins, key, undefined)
404 except KeyError:
405 return getattr(builtins, key, default)
406
407 def get(self, key, default=None):
408 return self.__getitem__(key, default)
374409
375410 def __setitem__(self, key, value):
376411 self._namespace[key] = value
377412
378413 def __delitem__(self, key):
379414 del self._namespace[key]
415
416
417 class TemplateCodeGenerator(ModuleCodeGenerator):
418
419 def __init__(self, node, filename):
420 ModuleCodeGenerator.__init__(self, transform(node, filename))
421
422 def _nameOp(self, prefix, name):
423 if name in runtime_vars:
424 return self.emit(prefix + '_GLOBAL', name)
425 return ModuleCodeGenerator._nameOp(self, prefix, name)
380426
381427
382428 class Template(object):
395441 errors='strict', unicode_mode=True):
396442 if isinstance(source, str):
397443 source = source.decode(encoding, errors)
398 node = Parser(tokenize('\n'.join(source.splitlines()),
444 node = Parser(tokenize(u'\n'.join(source.splitlines()),
399445 filename), filename).parse()
400 self.code = ModuleCodeGenerator(transform(node, filename)).getCode()
446 self.code = TemplateCodeGenerator(node, filename).getCode()
401447 self.filename = filename
402448 self.encoding = encoding
403449 self.errors = errors
405451
406452 def from_file(cls, file, encoding='utf-8', errors='strict',
407453 unicode_mode=True):
454 """Load a template from a file."""
408455 close = False
409456 if isinstance(file, basestring):
410457 f = open(file, 'r')
419466 from_file = classmethod(from_file)
420467
421468 def render(self, *args, **kwargs):
469 """
470 This function accepts either a dict or some keyword arguments which
471 will then be the context the template is evaluated in. The return
472 value will be the rendered template.
473 """
422474 ns = self.default_context.copy()
423475 ns.update(*args, **kwargs)
424476 context = Context(ns, self.encoding, self.errors)
425 exec self.code in {}, context
477 exec self.code in context.runtime, context
426478 return context.get_value(self.unicode_mode)
427479
428480 def substitute(self, *args, **kwargs):
22 werkzeug.test
33 ~~~~~~~~~~~~~
44
5 Helper module for unittests.
5 Quite often you want to unittest your application or just check the output
6 from an interactive python session. In theory that is pretty simple because
7 you can fake a WSGI environment and call the application with a dummy
8 start_response and iterate over the application iterator but there are
9 argumentably better ways to interact with an application.
10
11 Werkzeug provides an object called `Client` which you can pass a WSGI
12 application (and optionally a response wrapper) which you can use to send
13 virtual requests to the application.
14
15 A response wrapper is a callable that takes three arguments: the application
16 iterator, the status and finally a list of headers. The default response
17 wrapper returns a tuple. Because response objects have the same signature
18 you can use them as response wrapper, ideally by subclassing them and hooking
19 in test functionality.
20
21 >>> from werkzeug import Client, BaseResponse, test_app
22 >>> c = Client(test_app, BaseResponse)
23 >>> resp = c.get('/')
24 >>> resp.status_code
25 200
26 >>> resp.headers
27 Headers([('Content-Type', 'text/html; charset=utf-8')])
28 >>> resp.response_body.splitlines()[:2]
29 ['<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"',
30 ' "http://www.w3.org/TR/html4/loose.dtd">']
31
32 Or here without wrapper defined:
33
34 >>> from werkzeug import Client, test_app
35 >>> c = Client(test_app)
36 >>> app_iter, status, headers = c.get('/')
37 >>> status
38 '200 OK'
39 >>> headers
40 [('Content-Type', 'text/html; charset=utf-8')]
41 >>> ''.join(app_iter).splitlines()[:2]
42 ['<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"',
43 ' "http://www.w3.org/TR/html4/loose.dtd">']
644
745 :copyright: 2007 by Armin Ronacher.
846 :license: BSD, see LICENSE for more details.
112150 def open(self, path='/', base_url=None, query_string=None, method='GET',
113151 data=None, input_stream=None, content_type=None,
114152 content_length=0, errors_stream=None, multithread=False,
115 multiprocess=False, run_once=False, environ_overrides=None):
116 """
117 Open a page for the application. This function takes similar
118 arguments as the `create_environ` method from the utils module. If
119 the first argument is an environ or request object it is used as
120 the environment for the request.
153 multiprocess=False, run_once=False, environ_overrides=None,
154 as_tuple=False):
155 """
156 Takes the same arguments as the `create_environ` function from the utility
157 module with some additions.
158
159 The first parameter should be the path of the request which defaults to
160 '/'. The second one can either be a absolute path (in that case the url
161 host is localhost:80) or a full path to the request with scheme,
162 netloc port and the path to the script.
163
164 If the `path` contains a query string it will be used, even if the
165 `query_string` parameter was given. If it does not contain one
166 the `query_string` parameter is used as querystring. In that case
167 it can either be a dict, MultiDict or string.
168
169 The following options exist:
170
171 `method`
172 The request method. Defaults to `GET`
173
174 `input_stream`
175 The input stream. Defaults to an empty read only stream.
176
177 `data`
178 The data you want to transmit. You can set this to a string and
179 define a content type instead of specifying an input stream.
180 Additionally you can pass a dict with the form data. The values
181 could then be strings (no unicode objects!) which are then url
182 encoded or file objects.
183
184 A file object for this method is either a file descriptor with
185 an additional `name` attribute (like a file descriptor returned
186 by the `open` / `file` function), a tuple in the form
187 ``(fd, filename, mimetype)`` (all arguments except fd optional)
188 or as dict with those keys and values.
189
190 Additionally you can instanciate the `werkzeug.test.File` object
191 (or a subclass of it) and pass it as value.
192
193 `content_type`
194 The content type for this request. Default is an empty content
195 type.
196
197 `content_length`
198 The value for the content length header. Defaults to 0.
199
200 `errors_stream`
201 The wsgi.errors stream. Defaults to `sys.stderr`.
202
203 `multithread`
204 The multithreaded flag for the WSGI Environment. Defaults to
205 `False`.
206
207 `multiprocess`
208 The multiprocess flag for the WSGI Environment. Defaults to
209 `False`.
210
211 `run_once`
212 The run_once flag for the WSGI Environment. Defaults to `False`.
121213 """
122214 if input_stream is None and data and method in ('PUT', 'POST'):
123215 need_multipart = False
160252 if environ_overrides:
161253 environ.update(environ_overrides)
162254 rv = run_wsgi_app(self.application, environ)
163 return self.response_wrapper(*rv)
255 response = self.response_wrapper(*rv)
256 if as_tuple:
257 return environ, response
258 return response
164259
165260 def get(self, *args, **kw):
166261 """Like open but method is enforced to GET"""
55 Provide a small test application that can be used to test a WSGI server
66 and check it for WSGI compliance.
77
8 :copyright: Copyright 2007 by Armin Ronacher.
8 :copyright: Copyright 2007-2008 by Armin Ronacher.
99 :license: BSD, see LICENSE for more details.
1010 """
11 try:
12 import pkg_resources
13 except ImportError:
14 pkg_resources = None
1511 from werkzeug.templates import Template
16 from werkzeug.wrappers import BaseRequest as Request
12 from werkzeug.wrappers import BaseRequest as Request, BaseResponse as Response
1713
1814
19 LOGO = '''\
20 R0lGODlhoACgAOMIAAEDACwpAEpCAGdgAJaKAM28AOnVAP3rAP//////////////////////////
21 /////yH5BAEKAAgALAAAAACgAKAAAAT+EMlJq704680R+F0ojmRpnuj0rWnrvnB8rbRs33gu0bzu
22 /0AObxgsGn3D5HHJbCUFyqZ0ukkSDlAidctNFg7gbI9LZlrBaHGtzAae0eloe257w9EDOX2fst/x
23 enyCIn5/gFqDiVVDV4aGeYiKkhSFjnCQY5OTlZaXgZp8nJ2ekaB0SQOjqphrpnOiqncEn65UsLGy
24 tLVmQ6m4sQazpbtLqL/HwpnER8bHyLrLOc3Oz8PRONPU1crXN9na263dMt/g4SzjMeXm5yDpLqgG
25 7OzJ4u8lT/P69ej3JPn69kHzN2OIAHkB9RUYSFCFQYQJFTIkCDBiwoXWGnowaLEjRm7+Gp9A7Hhx
26 4rUkAUaSLJlxHMqVMD/aSycSZkyTplCqtGnRAM5NQ1Ly5OmzZc6gO4d6DGAUKA+hSocWYAoSlM6o
27 UWX2O/o0KdaVU5vuSQLAa0ADwQgMEMB2AIECZhVSnTno6spgbtXmHcBUrQACcc2FrTrWS8wAf78c
28 MFBgwIBgbN+qvTt3ayikRBk7BoyGAGABAdYyfdzRQGV3l4coxrqQ84GpUBmrdR3xNIDUPAKDBSAA
29 DIGDhhqTZIWaDcrVX8EsbNzbkvCOxG8bN5w8ly9H8jyTJHC6DFndQydbguh2e/ctZJFXRxMAqqPV
30 AtQH5E64SPr1f0zz7sQYjAHg0In+JQ11+N2B0XXBeeYZgBZFx4tqBToiTCPv0YBgQv8JqA6BEf6R
31 hXxw1ENhRBnWV8ctEX4Ul2zc3aVGcQNC2KElyTDYyYUWvShdjDyMOGMuFjqnII45aogPhz/CodUH
32 FwaDxlTgsaOjNyhGWJQd+lFoAGk8ObghI0kawg+EV5blH3dr+digkYuAGSaQZFHFz2P/cTaLmhF5
33 2QeSb45Jwxd+uSVGHlqOZpOeJpCFZ5J+rkAkFjQ0N1tah7JJSZUFNsrkeJUJMIBi8jyaEKIhKPom
34 nC91Uo+NByyaJ5umnnpInIFh4t6ZSpGaAVmizqjpByDegYl8tPE0phCYrhcMWSv+uAqHfgH88ak5
35 UXZmlKLVJhddj78s1Fxnzo6yUCrV6rrDOkluG+QzCAUTbCwf9SrmMLzK6p+OPHx7DF+bsfMRq7Ec
36 61Av9i6GLw23ridnZ+/OO0a99pbIrJkproCQMA17OPG6suq3cca5ruDfXCCDoS7BEdvmJn5otdqs
37 cn+uogRHHXs8cbhEIfYaDY1AkrC0cqwcZpnM6ludx72x0p7Fo/hZAcpJDjax0UdHavMKAbiKltMW
38 CF3xxh9k25N/Viud8ba78iCvUkt+V6BpwMlErmcgc502x+u1nSxJSJP9Mi52awD1V4yB/QHONsnU
39 3L+A/zR4VL/indx/y64gqcj+qgTeweM86f0Qy1QVbvmWH1D9h+alqg254QD8HJXHvjQaGOqEqC22
40 M54PcftZVKVSQG9jhkv7CJyTyDoAJfPdu8v7DRZAxsP/ky9MJ3OL36DJfCFPASC3/aXlfLOOON9v
41 GZZHydGf8LnxYJuuVIbl83yAz5n/RPz07E+9+zw2A2ahz4HxHo9Kt79HTMx1Q7ma7zAzHgHqYH0S
42 oZWyTuOLMiHwSfZDAQTn0ajk9YQqodnUYjByQZhZak9Wu4gYQsMyEpIOAOQKze8CmEF45KuAHTvI
43 DOfHJNipwoHMuGHBnJElUoDmAyXc2Qm/R8Ah/iILCCJOEokGowdhDYc/yoL+vpRGwyVSCWFYZNlj
44 khEirGXsalWcAgOdeAdoXcktF2udbqbUhjWyMQxYO01o6KYKOr6iK3fE4MaS+DsvBsGOBaMb0Y6I
45 xADaJhFICaOLmiWTlDAnY1KzDG4ambLcWBA8mUzjJsN2KjSaSXGqMCVXYpYkj33mcIApyhQf6Yqg
46 eNAmNvuC0t4CsDbSshZJkCS1eNisKqlyGcF8G2JeiDX6tO6Mv0SmjCa3MFb0bJaGPMU0X7c8Xcpv
47 MaOQmCajwSeY9G0WqbBmKv34DsMIEztU6Y2KiDlFdt6jnCSqx7Dmt6XnqSKaFFHNO5+FmODxMCWB
48 Eaco77lNDGXBM0ECYB/+s7nKFdwSF5hgXumQeEZ7amRg39RHy3zIjyRCykQh8Zo2iviRKyTDn/zx
49 6EefptJj2Cw+Ep2FSc01U5ry4KLPYsTyWnVGnvbUpyGlhjBUljyjHhWpf8OFaXwhp9O4T1gU9Uey
50 PPa8A2l0p1kNqPXEVRm1AOs1oAGZU596t6SOR2mcBOco1srWtkaVrMUzIErrKri85keKqRQYX9VX
51 0/eAUK1hrSu6HMEX3Qh2sCh0q0D2CtnUqS4hj62sE/zaDs2Sg7MBS6xnQeooc2R2tC9YrKpEi9pL
52 XfYXp20tDCpSP8rKlrD4axprb9u1Df5hSbz9QU0cRpfgnkiIzwKucd0wsEHlLpe5yHXuc6FrNelO
53 l7pY2+11kTWx7VpRu97dXA3DO1vbkhcb4zyvERYajQgAADs='''
15 logo = Response('''R0lGODlhoACgAOMIAAEDACwpAEpCAGdgAJaKAM28AOnVAP3rAP/////////
16 //////////////////////yH5BAEKAAgALAAAAACgAKAAAAT+EMlJq704680R+F0ojmRpnuj0rWnrv
17 nB8rbRs33gu0bzu/0AObxgsGn3D5HHJbCUFyqZ0ukkSDlAidctNFg7gbI9LZlrBaHGtzAae0eloe25
18 7w9EDOX2fst/xenyCIn5/gFqDiVVDV4aGeYiKkhSFjnCQY5OTlZaXgZp8nJ2ekaB0SQOjqphrpnOiq
19 ncEn65UsLGytLVmQ6m4sQazpbtLqL/HwpnER8bHyLrLOc3Oz8PRONPU1crXN9na263dMt/g4SzjMeX
20 m5yDpLqgG7OzJ4u8lT/P69ej3JPn69kHzN2OIAHkB9RUYSFCFQYQJFTIkCDBiwoXWGnowaLEjRm7+G
21 p9A7Hhx4rUkAUaSLJlxHMqVMD/aSycSZkyTplCqtGnRAM5NQ1Ly5OmzZc6gO4d6DGAUKA+hSocWYAo
22 SlM6oUWX2O/o0KdaVU5vuSQLAa0ADwQgMEMB2AIECZhVSnTno6spgbtXmHcBUrQACcc2FrTrWS8wAf
23 78cMFBgwIBgbN+qvTt3ayikRBk7BoyGAGABAdYyfdzRQGV3l4coxrqQ84GpUBmrdR3xNIDUPAKDBSA
24 ADIGDhhqTZIWaDcrVX8EsbNzbkvCOxG8bN5w8ly9H8jyTJHC6DFndQydbguh2e/ctZJFXRxMAqqPVA
25 tQH5E64SPr1f0zz7sQYjAHg0In+JQ11+N2B0XXBeeYZgBZFx4tqBToiTCPv0YBgQv8JqA6BEf6RhXx
26 w1ENhRBnWV8ctEX4Ul2zc3aVGcQNC2KElyTDYyYUWvShdjDyMOGMuFjqnII45aogPhz/CodUHFwaDx
27 lTgsaOjNyhGWJQd+lFoAGk8ObghI0kawg+EV5blH3dr+digkYuAGSaQZFHFz2P/cTaLmhF52QeSb45
28 Jwxd+uSVGHlqOZpOeJpCFZ5J+rkAkFjQ0N1tah7JJSZUFNsrkeJUJMIBi8jyaEKIhKPomnC91Uo+NB
29 yyaJ5umnnpInIFh4t6ZSpGaAVmizqjpByDegYl8tPE0phCYrhcMWSv+uAqHfgH88ak5UXZmlKLVJhd
30 dj78s1Fxnzo6yUCrV6rrDOkluG+QzCAUTbCwf9SrmMLzK6p+OPHx7DF+bsfMRq7Ec61Av9i6GLw23r
31 idnZ+/OO0a99pbIrJkproCQMA17OPG6suq3cca5ruDfXCCDoS7BEdvmJn5otdqscn+uogRHHXs8cbh
32 EIfYaDY1AkrC0cqwcZpnM6ludx72x0p7Fo/hZAcpJDjax0UdHavMKAbiKltMWCF3xxh9k25N/Viud8
33 ba78iCvUkt+V6BpwMlErmcgc502x+u1nSxJSJP9Mi52awD1V4yB/QHONsnU3L+A/zR4VL/indx/y64
34 gqcj+qgTeweM86f0Qy1QVbvmWH1D9h+alqg254QD8HJXHvjQaGOqEqC22M54PcftZVKVSQG9jhkv7C
35 JyTyDoAJfPdu8v7DRZAxsP/ky9MJ3OL36DJfCFPASC3/aXlfLOOON9vGZZHydGf8LnxYJuuVIbl83y
36 Az5n/RPz07E+9+zw2A2ahz4HxHo9Kt79HTMx1Q7ma7zAzHgHqYH0SoZWyTuOLMiHwSfZDAQTn0ajk9
37 YQqodnUYjByQZhZak9Wu4gYQsMyEpIOAOQKze8CmEF45KuAHTvIDOfHJNipwoHMuGHBnJElUoDmAyX
38 c2Qm/R8Ah/iILCCJOEokGowdhDYc/yoL+vpRGwyVSCWFYZNljkhEirGXsalWcAgOdeAdoXcktF2udb
39 qbUhjWyMQxYO01o6KYKOr6iK3fE4MaS+DsvBsGOBaMb0Y6IxADaJhFICaOLmiWTlDAnY1KzDG4ambL
40 cWBA8mUzjJsN2KjSaSXGqMCVXYpYkj33mcIApyhQf6YqgeNAmNvuC0t4CsDbSshZJkCS1eNisKqlyG
41 cF8G2JeiDX6tO6Mv0SmjCa3MFb0bJaGPMU0X7c8XcpvMaOQmCajwSeY9G0WqbBmKv34DsMIEztU6Y2
42 KiDlFdt6jnCSqx7Dmt6XnqSKaFFHNO5+FmODxMCWBEaco77lNDGXBM0ECYB/+s7nKFdwSF5hgXumQe
43 EZ7amRg39RHy3zIjyRCykQh8Zo2iviRKyTDn/zx6EefptJj2Cw+Ep2FSc01U5ry4KLPYsTyWnVGnvb
44 UpyGlhjBUljyjHhWpf8OFaXwhp9O4T1gU9UeyPPa8A2l0p1kNqPXEVRm1AOs1oAGZU596t6SOR2mcB
45 Oco1srWtkaVrMUzIErrKri85keKqRQYX9VX0/eAUK1hrSu6HMEX3Qh2sCh0q0D2CtnUqS4hj62sE/z
46 aDs2Sg7MBS6xnQeooc2R2tC9YrKpEi9pLXfYXp20tDCpSP8rKlrD4axprb9u1Df5hSbz9QU0cRpfgn
47 kiIzwKucd0wsEHlLpe5yHXuc6FrNelOl7pY2+11kTWx7VpRu97dXA3DO1vbkhcb4zyvERYajQgAADs
48 ='''.decode('base64'), mimetype='image/png')
49
5450
5551 TEMPLATE = Template(ur'''\
5652 <%py
57 import sys
58 import os
53 import sys, os
5954 from textwrap import wrap
60
55 try:
56 import pkg_resources
57 except ImportError:
58 eggs = None
59 else:
60 eggs = list(pkg_resources.working_set)
61 eggs.sort(lambda a, b: cmp(a.project_name.lower(),
62 b.project_name.lower()))
6163 sorted_environ = req.environ.items()
6264 sorted_environ.sort(lambda a, b: cmp(str(a[0]).lower(), str(b[0]).lower()))
6365 %>
6466 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
6567 "http://www.w3.org/TR/html4/loose.dtd">
66 <html>
67 <head>
68 <title>WSGI Information</title>
69 <style type="text/css">
70 body {
71 font-family: sans-serif;
72 background-color: #333;
73 text-align: center;
74 margin: 1em;
75 padding: 0;
76 }
77
78 #logo {
79 float: right;
80 padding: 10px;
81 }
82
83 div.box {
84 text-align: left;
85 width: 45em;
86 padding: 1em;
87 margin: 0 auto 0 auto;
88 border: 1px solid black;
89 background-color: white;
90 }
91
92 h1 {
93 color: #444;
94 font-size: 2em;
95 margin: 0 0 1em 0;
96 font-family: 'Georgia', serif;
97 }
98
99 h2 {
100 color: #333;
101 font-size: 1.4em;
102 margin: 1em 0 0.5em 0;
103 }
104
105 table {
106 width: 100%;
107 border-collapse: collapse;
108 border: 1px solid #ccc;
109 }
110
111 table th {
112 background-color: #555;
113 color: white;
114 font-size: 0.7em;
115 font-weight: normal;
116 width: 18em;
117 padding: 0.5em 0 0.1em 0.5em;
118 vertical-align: top;
119 }
120
121 table td {
122 border: 1px solid #ccc;
123 padding: 0.1em 0 0.1em 0.5em;
124 }
125
126 table td code {
127 font-family: 'Consolas', 'Monaco', 'Bitstream Vera Sans', monospace;
128 font-size: 0.7em;
129 }
130
131 ul li {
132 line-height: 1.5em;
133 }
134 </style>
135 </head>
136 <body>
137 <div class="box">
138 <img src="?resource=logo" id="logo" alt="" />
139 <h1>WSGI Information</h1>
140 <p>
141 This page displays all available information about the WSGI server and
142 the underlaying Python interpreter that are available.
143 </p>
144 <h2 id="python-interpreter">Python Interpreter</h2>
145 <table>
146 <tr>
147 <th>Python Version</th>
148 <td>${'<br>'.join(escape(sys.version).splitlines())}</td>
149 </tr>
150 <tr>
151 <th>Platform</th>
152 <td>$escape(sys.platform) [$escape(os.name)]</td>
153 </tr>
154 <tr>
155 <th>API Version</th>
156 <td>$sys.api_version</td>
157 </tr>
158 <tr>
159 <th>Byteorder</th>
160 <td>$sys.byteorder</td>
161 </tr>
162 </table>
163 <h2 id="wsgi-environment">WSGI Environment</h2>
164 <table>
165 <% for key, value in sorted_environ %>
166 <tr>
167 <th>$escape(str(key))</th>
168 <td><code>${' '.join(wrap(escape(repr(value))))}</code></td>
169 </tr>
170 <% endfor %>
171 </table>
172 <% if eggs %>
173 <h2 id="installed-eggs">Installed Eggs</h2>
174 <ul>
175 <% for egg in eggs %>
176 <li>$escape(egg.project_name) <small>[$escape(egg.version)]</small></li>
177 <% endfor %>
178 </ul>
179 <% endif %>
180 </div>
181 </body>
182 </html>
183 ''', unicode_mode=False)
68 <title>WSGI Information</title>
69 <style type="text/css">
70 body { font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
71 'Verdana', sans-serif; background-color: #AFC1C4; color: #000;
72 text-align: center; margin: 1em; padding: 0; }
73 #logo { float: right; padding: 10px; }
74 div.box { text-align: left; width: 45em; padding: 1em; margin: auto;
75 border: 1px solid #aaa; background-color: white; }
76 h1 { color: #11557C; font-size: 2em; margin: 0 0 0.8em 0; }
77 h2 { font-size: 1.4em; margin: 1em 0 0.5em 0; }
78 table { width: 100%; border-collapse: collapse; border: 1px solid #AFC5C9 }
79 table th { background-color: #AFC1C4; color: white; font-size: 0.72em;
80 font-weight: normal; width: 18em; vertical-align: top;
81 padding: 0.5em 0 0.1em 0.5em; }
82 table td { border: 1px solid #AFC5C9; padding: 0.1em 0 0.1em 0.5em; }
83 code { font-family: 'Consolas', 'Monaco', 'Bitstream Vera Sans Mono',
84 monospace; font-size: 0.7em; }
85 ul li { line-height: 1.5em; }
86 </style>
87 <div class="box">
88 <img src="?resource=logo" id="logo" alt="[The Werkzeug Logo]" />
89 <h1>WSGI Information</h1>
90 <p>
91 This page displays all available information about the WSGI server and
92 the underlying Python interpreter that are available.
93 </p>
94 <h2 id="python-interpreter">Python Interpreter</h2>
95 <table>
96 <tr>
97 <th>Python Version</th>
98 <td>${'<br>'.join(escape(sys.version).splitlines())}</td>
99 </tr>
100 <tr>
101 <th>Platform</th>
102 <td>$escape(sys.platform) [$escape(os.name)]</td>
103 </tr>
104 <tr>
105 <th>API Version</th>
106 <td>$sys.api_version</td>
107 </tr>
108 <tr>
109 <th>Byteorder</th>
110 <td>$sys.byteorder</td>
111 </tr>
112 </table>
113 <h2 id="wsgi-environment">WSGI Environment</h2>
114 <table>
115 <% for key, value in sorted_environ %>
116 <tr>
117 <th>$escape(str(key))</th>
118 <td><code>${' '.join(wrap(escape(repr(value))))}</code></td>
119 </tr>
120 <% endfor %>
121 </table>
122 <% if eggs %>
123 <h2 id="installed-eggs">Installed Eggs</h2>
124 <ul>
125 <% for egg in eggs %>
126 <li>$escape(egg.project_name) <small>[$escape(egg.version)]</small></li>
127 <% endfor %>
128 </ul>
129 <% endif %>
130 </div>''')
184131
185132
186133 def test_app(environ, start_response):
134 """Simple test application that dumps the environment."""
187135 req = Request(environ, populate_request=False)
188136 if req.args.get('resource') == 'logo':
189 image = LOGO.decode('base64')
190 start_response('200 OK', [('Content-Type', 'image/gif'),
191 ('Content-Length', str(len(image)))])
192 return [image]
193 start_response('200 OK', [('Content-Type', 'text/html; charset=utf-8')])
194 eggs = None
195 if pkg_resources is not None:
196 eggs = list(pkg_resources.working_set)
197 eggs.sort(lambda a, b: cmp(a.project_name.lower(),
198 b.project_name.lower()))
199 return [TEMPLATE.render(req=req, eggs=eggs)]
137 response = logo
138 else:
139 response = Response(TEMPLATE.render(req=req), mimetype='text/html')
140 return response(environ, start_response)
200141
201142
202143 if __name__ == '__main__':
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.useragents
3 ~~~~~~~~~~~~~~~~~~~
4
5 This module provides a helper to inspect user agent strings. This module
6 is far from complete but should work for most of the current browsers that
7 are available.
8
9
10 :copyright: 2007-2008 by Armin Ronacher.
11 :license: BSD, see LICENSE for more details.
12 """
13 import re
14
15
16 class UserAgentParser(object):
17 """
18 A simple user agent parser. Used by the `UserAgent`.
19 """
20
21 platforms = (
22 (r'darwin|mac|os\s*x', 'macos'),
23 ('win', 'windows'),
24 (r'x11|lin(\b|ux)?', 'linux'),
25 ('(sun|i86)os', 'solaris'),
26 ('iphone', 'iphone'),
27 (r'nintendo\s+wii', 'wii'),
28 ('irix', 'irix'),
29 ('hp-?ux', 'hpux'),
30 ('aix', 'aix'),
31 ('sco|unix_sv', 'sco'),
32 ('bsd', 'bsd'),
33 ('amiga', 'amiga')
34 )
35 browsers = (
36 ('googlebot', 'google'),
37 ('msnbot', 'msn'),
38 ('yahoo', 'yahoo'),
39 ('ask jeeves', 'ask'),
40 (r'aol|america\s+online\s+browser', 'aol'),
41 ('opera', 'opera'),
42 ('firefox|firebird|phoenix|iceweasel', 'firefox'),
43 ('galeon', 'galeon'),
44 ('safari', 'safari'),
45 ('webkit', 'webkit'),
46 ('camino', 'camino'),
47 ('konqueror', 'konqueror'),
48 ('k-meleon', 'kmeleon'),
49 ('netscape', 'netscape'),
50 (r'msie|microsoft\s+internet\s+explorer', 'msie'),
51 ('lynx', 'lynx'),
52 ('links', 'links'),
53 ('seamonkey|mozilla', 'seamonkey')
54 )
55
56 _browser_version_re = r'(?:%s)[/\sa-z(]*(\d+[.\da-z]+)?(?i)'
57 _language_re = re.compile(
58 r'(?:;\s*|\s+)(\b\w{2}\b(?:-\b\w{2}\b)?)\s*;|'
59 r'(?:\(|\[|;)\s*(\b\w{2}\b(?:-\b\w{2}\b)?)\s*(?:\]|\)|;)'
60 )
61
62 def __init__(self):
63 self.platforms = re.compile(r'|'.join(['(?P<%s>%s)' % (b, a) for a, b
64 in self.platforms]), re.I)
65 self.browsers = [(b, re.compile(self._browser_version_re % a))
66 for a, b in self.browsers]
67
68 def __call__(self, user_agent):
69 match = self.platforms.search(user_agent)
70 if match is not None:
71 for platform, value in match.groupdict().iteritems():
72 if value:
73 break
74 else:
75 platform = None
76 for browser, regex in self.browsers:
77 match = regex.search(user_agent)
78 if match is not None:
79 version = match.group(1)
80 break
81 else:
82 browser = version = None
83 match = self._language_re.search(user_agent)
84 if match is not None:
85 language = match.group(1) or match.group(2)
86 else:
87 language = None
88 return platform, browser, version, language
89
90
91 class UserAgent(object):
92 """
93 Represents a user agent. Pass it a WSGI environment or an user agent
94 string and you can inspect some of the details from the user agent
95 string via the attributes. The following attribute exist:
96
97 - `string`, the raw user agent string
98 - `platform`, the browser platform
99 - `browser`, the name of the browser
100 - `version`, the version of the browser
101 - `language`, the language of the browser
102 """
103 _parser = UserAgentParser()
104
105 def __init__(self, environ_or_string):
106 if isinstance(environ_or_string, dict):
107 environ_or_string = environ_or_string.get('HTTP_USER_AGENT', '')
108 self.string = environ_or_string
109 self.platform, self.browser, self.version, self.language = \
110 self._parser(environ_or_string)
111
112 def to_header(self):
113 return self.string
114
115 def __str__(self):
116 return self.string
117
118 def __nonzero__(self):
119 return bool(self.browser)
120
121 def __repr__(self):
122 return '<%s %r/%s>' % (
123 self.__class__.__name__,
124 self.browser,
125 self.version
126 )
127
128
129 # conceptionally this belongs in this module but because we want to lazily
130 # load the user agent module (which happens in wrappers.py) we have to import
131 # it afterwards. The class itself has the module set to this module so
132 # pickle, inspect and similar modules treat the object as if it was really
133 # implemented here.
134 from werkzeug.wrappers import UserAgentMixin
22 werkzeug.utils
33 ~~~~~~~~~~~~~~
44
5 Various utils.
6
7 :copyright: 2007 by Armin Ronacher, Georg Brandl.
5 This module implements various utilities for WSGI applications. Most of
6 them are used by the request and response wrappers but especially for
7 middleware development it makes sense to use them without the wrappers.
8
9 :copyright: 2007-2008 by Armin Ronacher, Georg Brandl.
810 :license: BSD, see LICENSE for more details.
911 """
1012 import re
1315 import cgi
1416 import urllib
1517 import urlparse
18 from itertools import chain
19 from Cookie import BaseCookie, Morsel, CookieError
1620 from time import asctime, gmtime, time
17 from datetime import datetime
21 from datetime import datetime, timedelta
1822 from cStringIO import StringIO
1923 try:
20 set
24 set = set
2125 except NameError:
2226 from sets import Set as set
23
2427 def reversed(item):
25 return tuple(item)[::-1]
28 return item[::-1]
2629
2730
2831 _empty_stream = StringIO('')
32 _logger = None
2933
3034 _format_re = re.compile(r'\$(%s|\{%s\})' % (('[a-zA-Z_][a-zA-Z0-9_]*',) * 2))
35 _entity_re = re.compile(r'&([^;]+);')
36
37
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)
31108
32109
33110 class MultiDict(dict):
34111 """
35 A dict that takes a list of multiple values as only argument
36 in order to store multiple values per key.
112 A `MultiDict` is a dictionary subclass customized to deal with multiple
113 values for the same key which is for example used by the parsing functions
114 in the wrappers. This is necessary because some HTML form elements pass
115 multiple values for the same key.
116
117 `MultiDict` implements the all standard dictionary methods. Internally,
118 it saves all values for a key as a list, but the standard dict access
119 methods will only return the first value for a key. If you want to gain
120 access to the other values too you have to use the `list` methods as
121 explained below.
122
123 Basic Usage:
124
125 >>> d = MultiDict([('a', 'b'), ('a', 'c')])
126 >>> d
127 MultiDict([('a', 'b'), ('a', 'c')])
128 >>> d['a']
129 'b'
130 >>> d.getlist('a')
131 ['b', 'c']
132 >>> 'a' in d
133 True
134
135 It behaves like a normal dict thus all dict functions will only return the
136 first value when multiple values for one key are found.
37137 """
38138
39139 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.
144 """
40145 if isinstance(mapping, MultiDict):
41146 dict.__init__(self, [(k, v[:]) for k, v in mapping.lists()])
42147 elif isinstance(mapping, dict):
58163 """
59164 Return the first data value for this key;
60165 raises KeyError if not found.
166
167 :raise KeyError: if the key does not exist
61168 """
62169 return dict.__getitem__(self, key)[0]
63170
68175 def get(self, key, default=None, type=None):
69176 """
70177 Return the default value if the requested data doesn't exist.
71 Additionally you can pass it a type function that is used as
72 converter. That function should either conver the value or
73 raise a `ValueError`.
178 If `type` is provided and is a callable it should convert the value,
179 return it or raise a `ValueError` if that is not possible. In this
180 case the function will return the default as if the value was not
181 found.
182
183 Example:
184
185 >>> d = MultiDict(foo='42', bar='blub')
186 >>> d.get('foo', type=int)
187 42
188 >>> d.get('bar', -1, type=int)
189 -1
74190 """
75191 try:
76192 rv = self[key]
81197 return rv
82198
83199 def getlist(self, key, type=None):
84 """Return an empty list if the requested data doesn't exist"""
200 """
201 Return the list of items for a given key. If that key is not in the
202 `MultiDict`, the return value will be an empty list. Just as `get`
203 `getlist` accepts a `type` parameter. All items will be converted
204 with the callable defined there.
205
206 :return: list
207 """
85208 try:
86209 rv = dict.__getitem__(self, key)
87210 except KeyError:
97220 return result
98221
99222 def setlist(self, key, new_list):
100 """Set new values for an key."""
223 """
224 Remove the old values for a key and add new ones. Note that the list
225 you pass the values in will be shallow-copied before it is inserted in
226 the dictionary.
227
228 >>> multidict.setlist('foo', ['1', '2'])
229 >>> multidict['foo']
230 '1'
231 >>> multidict.getlist('foo')
232 ['1', '2']
233 """
101234 dict.__setitem__(self, key, list(new_list))
102235
103236 def setdefault(self, key, default=None):
108241 return default
109242
110243 def setlistdefault(self, key, default_list=()):
244 """Like `setdefault` but sets multiple values."""
111245 if key not in self:
112246 default_list = list(default_list)
113247 dict.__setitem__(self, key, default_list)
148282
149283 def to_dict(self, flat=True):
150284 """
151 Returns the contents as simple dict. If `flat` is `True` the
152 resulting dict will only have the first item present, if `flat`
153 is `False` all values will be lists.
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.
288
289 :return: dict
154290 """
155291 if flat:
156292 return dict(self.iteritems())
190326
191327 class CombinedMultiDict(MultiDict):
192328 """
193 Pass it multiple multidicts to create a new read only
194 dict which resolves items from the passed dicts.
329 A read only `MultiDict` decorator that you can pass multiple `MultiDict`
330 instances as sequence and it will combine the return values of all wrapped
331 dicts:
332
333 >>> from werkzeug import MultiDict, CombinedMultiDict
334 >>> post = MultiDict([('foo', 'bar')])
335 >>> get = MultiDict([('blub', 'blah')])
336 >>> combined = CombinedMultiDict([get, post])
337 >>> combined['foo']
338 'bar'
339 >>> combined['blub']
340 'blah'
341
342 This works for all read operations and will raise a `TypeError` for
343 methods that usually change data which isn't possible.
195344 """
196345
197346 def __init__(self, dicts=None):
213362 if key in d:
214363 if type is not None:
215364 try:
216 type(d[key])
365 return type(d[key])
217366 except ValueError:
218367 continue
219368 return d[key]
312461
313462 class FileStorage(object):
314463 """
315 Represents an uploaded file.
316 """
317
318 def __init__(self, name, filename, content_type, content_length, stream):
464 The `FileStorage` object is a thin wrapper over incoming files. It is
465 used by the request object to represent uploaded files. All the
466 attributes of the wrapper stream are proxied by the file storage so
467 it's possible to do ``storage.read()`` instead of the long form
468 ``storage.stream.read()``.
469 """
470
471 def __init__(self, stream=None, filename=None, name=None,
472 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.
476
477 :param stream: the input stream for uploaded file. Usually this
478 points to a temporary file.
479 :param filename: The filename of the file on the client.
480 :param name: the name of the form field
481 :param content_type: the content type of the file
482 :param content_length: the content length of the file.
483 """
319484 self.name = name
320 self.filename = filename
485 self.stream = stream or _empty_stream
486 self.filename = filename or getattr(stream, 'name', None)
321487 self.content_type = content_type
322488 self.content_length = content_length
323 self.stream = stream
324489
325490 def save(self, dst, buffer_size=16384):
326491 """
327492 Save the file to a destination path or file object. If the
328 destination is a file object you have to close it yourself after the call.
329 The buffer size is the number of bytes held in the memory during the copy
330 process. It defaults to 16KB.
493 destination is a file object you have to close it yourself after the
494 call. The buffer size is the number of bytes held in the memory
495 during the copy process. It defaults to 16KB.
331496 """
332497 from shutil import copyfileobj
333498 if isinstance(dst, basestring):
349514 return bool(self.filename and self.content_length)
350515
351516 def __len__(self):
352 return self.content_length
517 return max(self.content_length, 0)
353518
354519 def __iter__(self):
355520 return iter(self.readline, '')
366531 """
367532 An object that stores some headers. It has a dict like interface
368533 but is ordered and can store keys multiple times.
369 """
370
371 def __init__(self, defaults=None):
372 self._list = []
534
535 This data structure is useful if you want a nicer way to handle WSGI
536 headers which are stored as tuples in a list.
537 """
538
539 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.
546 """
547 if _list is None:
548 _list = []
549 self._list = _list
373550 if isinstance(defaults, dict):
374551 for key, value in defaults.iteritems():
375552 if isinstance(value, (tuple, list)):
380557 elif defaults is not None:
381558 self._list[:] = defaults
382559
560 def linked(cls, headerlist):
561 """
562 Create a new `Headers` object that uses the list of headers passed as
563 internal storage:
564
565 >>> headerlist = [('Content-Length', '40')]
566 >>> headers = Headers.linked(headerlist)
567 >>> headers.add('Content-Type', 'text/html')
568 >>> headerlist
569 [('Content-Length', '40'), ('Content-Type', 'text/html')]
570
571 :return: new linked `Headers` object.
572 """
573 return cls(_list=headerlist)
574 linked = classmethod(linked)
575
383576 def __getitem__(self, key):
384577 ikey = key.lower()
385578 for k, v in self._list:
394587 def __ne__(self, other):
395588 return not self.__eq__(other)
396589
397 def get(self, key, default=None):
590 def get(self, key, default=None, type=None):
591 """
592 Return the default value if the requested data doesn't exist.
593 If `type` is provided and is a callable it should convert the value,
594 return it or raise a `ValueError` if that is not possible. In this
595 case the function will return the default as if the value was not
596 found.
597
598 Example:
599
600 >>> d = Headers([('Content-Length', '42')])
601 >>> d.get('Content-Length', type=int)
602 42
603
604 If a headers object is bound you must notadd unicode strings
605 because no encoding takes place.
606 """
398607 try:
399 return self[key]
608 rv = self[key]
400609 except KeyError:
401610 return default
402
403 def getlist(self, key):
611 if type is None:
612 return rv
613 try:
614 return type(rv)
615 except ValueError:
616 return default
617
618 def getlist(self, key, type=None):
619 """
620 Return the list of items for a given key. If that key is not in the
621 `MultiDict`, the return value will be an empty list. Just as `get`
622 `getlist` accepts a `type` parameter. All items will be converted
623 with the callable defined there.
624
625 :return: list
626 """
404627 ikey = key.lower()
405628 result = []
406629 for k, v in self:
407630 if k.lower() == ikey:
631 if type is not None:
632 try:
633 v = type(v)
634 except ValueError:
635 continue
408636 result.append(v)
409637 return result
410638
430658
431659 def items(self, lower=False):
432660 return list(self.iteritems(lower))
661
662 def extend(self, iterable):
663 """
664 Extend the headers with a dict or an iterable yielding keys and
665 values.
666 """
667 if isinstance(iterable, dict):
668 iterable = iterable.iteritems()
669 for key, value in iterable:
670 self.add(key, value)
433671
434672 def __delitem__(self, key):
435673 key = key.lower()
442680 remove = __delitem__
443681
444682 def __contains__(self, key):
683 """Check if a key is present."""
445684 try:
446685 self[key]
447686 except KeyError:
451690 has_key = __contains__
452691
453692 def __iter__(self):
693 """Yield ``(key, value)`` tuples."""
454694 return iter(self._list)
455695
456696 def add(self, key, value):
471711 __setitem__ = set
472712
473713 def to_list(self, charset='utf-8'):
474 """Create a str only list of the headers."""
714 """
715 Convert the headers into a list and converts the unicode header items
716 to the specified charset.
717
718 :return: list
719 """
475720 result = []
476721 for k, v in self:
477722 if isinstance(v, unicode):
496741
497742 class EnvironHeaders(Headers):
498743 """
499 Read only version of the headers from wsgi environment.
744 Read only version of the headers from a WSGI environment. This
745 provides the same interface as `Headers` and is constructed from
746 a WSGI environment.
500747 """
501748
502749 def __init__(self, environ):
503750 self.environ = environ
751
752 def linked(cls, environ):
753 raise TypeError('%r object is always linked to environment, '
754 'no separate initializer' % self.__class__.__name__)
755 linked = classmethod(linked)
504756
505757 def __eq__(self, other):
506758 return self is other
518770
519771 def _immutable(self, *a, **kw):
520772 raise TypeError('%r is immutable' % self.__class__.__name__)
521 remove = __delitem__ = add = clear = set = __setitem__ = _immutable
773 remove = __delitem__ = add = clear = extend = set = __setitem__ = \
774 _immutable
522775 del _immutable
523776
524777
525778 class SharedDataMiddleware(object):
526779 """
527 Redirects calls to an folder with static data.
780 A WSGI middleware that provides static content for development
781 environments or simple server setups. Usage is quite simple::
782
783 import os
784 from werkzeug import SharedDataMiddleware
785
786 app = SharedDataMiddleware(app, {
787 '/shared': os.path.join(os.path.dirname(__file__), 'shared')
788 })
789
790 The contents of the folder ``./shared`` will now be available on
791 ``http://example.com/shared/``. This is pretty useful during development
792 because a standalone media server is not required. One can also mount
793 files on the root folder and still continue to use the application because
794 the shared data middleware forwards all unhandled requests to the
795 application, even if the requests are below one of the shared folders.
796
797 If `pkg_resources` is available you can also tell the middleware to serve
798 files from package data::
799
800 app = SharedDataMiddleware(app, {
801 '/shared': ('myapplication', 'shared_files')
802 })
803
804 This will then serve the ``shared_files`` folder in the `myapplication`
805 python package.
528806 """
529807
530808 def __init__(self, app, exports, disallow=None):
531809 self.app = app
532 self.exports = exports
533 self.disallow = disallow
534
535 def serve_file(self, filename, start_response):
810 self.exports = {}
811 for key, value in exports.iteritems():
812 if isinstance(value, tuple):
813 loader = self.get_package_loader(*value)
814 elif isinstance(value, basestring):
815 if os.path.isfile(value):
816 loader = self.get_file_loader(value)
817 else:
818 loader = self.get_directory_loader(value)
819 else:
820 raise TypeError('unknown def %r' % value)
821 self.exports[key] = loader
822 if disallow is not None:
823 from fnmatch import fnmatch
824 self.is_allowed = lambda x: not fnmatch(x, disallow)
825
826 def is_allowed(self, filename):
827 return True
828
829 def get_file_loader(self, filename):
830 return lambda x: os.path.basename(filename), \
831 lambda: open(filename, 'rb')
832
833 def get_package_loader(self, package, package_path):
834 from pkg_resources import resource_exists, resource_stream
835 def loader(path):
836 path = posixpath.join(package_path, path)
837 if resource_exists(package, path):
838 return posixpath.basename(path), \
839 lambda: resource_stream(package, path)
840 return None, None
841 return loader
842
843 def get_directory_loader(self, directory):
844 def loader(path):
845 path = os.path.join(directory, path)
846 if os.path.isfile(path):
847 return os.path.basename(path), lambda: open(path, 'rb')
848 return None, None
849 return loader
850
851 def __call__(self, environ, start_response):
852 # sanitize the path for non unix systems
853 cleaned_path = environ.get('PATH_INFO', '').strip('/')
854 for sep in os.sep, os.altsep:
855 if sep and sep != '/':
856 cleaned_path = cleaned_path.replace(sep, '/')
857 path = '/'.join([''] + [x for x in cleaned_path.split('/')
858 if x and x != '..'])
859 stream_maker = None
860 for search_path, loader in self.exports.iteritems():
861 if search_path == path:
862 real_filename, stream_maker = loader.load(None)
863 if stream_maker is not None:
864 break
865 if not search_path.endswith('/'):
866 search_path += '/'
867 if path.startswith(search_path):
868 real_filename, stream_maker = loader(path[len(search_path):])
869 if stream_maker is not None:
870 break
871 if stream_maker is None or not self.is_allowed(real_filename):
872 return self.app(environ, start_response)
536873 from mimetypes import guess_type
537 guessed_type = guess_type(filename)
874 guessed_type = guess_type(real_filename)
538875 mime_type = guessed_type[0] or 'text/plain'
539876 expiry = asctime(gmtime(time() + 3600))
540877 start_response('200 OK', [('Content-Type', mime_type),
541878 ('Cache-Control', 'public'),
542879 ('Expires', expiry)])
543 fp = file(filename, 'rb')
880 stream = stream_maker()
544881 try:
545 return [fp.read()]
882 return [stream.read()]
546883 finally:
547 fp.close()
884 stream.close()
885
886
887 class DispatcherMiddleware(object):
888 """
889 Allows one to mount middlewares or application in a WSGI application.
890 This is useful if you want to combine multiple WSGI applications::
891
892 app = DispatcherMiddleware(app, {
893 '/app2': app2,
894 '/app3': app3
895 })
896 """
897
898 def __init__(self, app, mounts=None):
899 self.app = app
900 self.mounts = mounts or {}
548901
549902 def __call__(self, environ, start_response):
550 path = '/'.join([''] + [x for x in environ.get('PATH_INFO', '').
551 strip('/').split() if x != '..'])
552 for search_path, file_path in self.exports.iteritems():
553 if search_path == path and os.path.isfile(file_path):
554 return self.serve_file(file_path, start_response)
555 if not search_path.endswith('/'):
556 search_path += '/'
557 if path.startswith(search_path):
558 real_path = os.path.join(file_path, path[len(search_path):])
559 if os.path.exists(real_path) and os.path.isfile(real_path):
560 if self.disallow:
561 from fnmatch import fnmatch
562 for pattern in self.disallow:
563 if fnmatch(real_path, pattern):
564 break
565 else:
566 return self.serve_file(real_path, start_response)
567 else:
568 return self.serve_file(real_path, start_response)
569 return self.app(environ, start_response)
903 script = environ.get('PATH_INFO', '')
904 path_info = ''
905 while '/' in script:
906 if script in self.mounts:
907 app = self.mounts[script]
908 break
909 items = script.split('/')
910 script = '/'.join(items[:-1])
911 path_info = '/%s%s' % (items[-1], path_info)
912 else:
913 app = self.mounts.get(script, self.app)
914 original_script_name = environ.get('SCRIPT_NAME', '')
915 environ['SCRIPT_NAME'] = original_script_name + script
916 environ['PATH_INFO'] = path_info
917 return app(environ, start_response)
570918
571919
572920 class ClosingIterator(object):
573921 """
574 A class that wraps an iterator (which can have a close method) and
575 adds a close method for the callback and the iterator.
576
577 Usage::
578
579 return ClosingIterator(iter, [list, of, callbacks])
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::
926
927 return ClosingIterator(app(environ, start_response), [cleanup_session,
928 cleanup_locals])
929
930 If there is just one close function it can be bassed instead of the list.
931
932 A closing iterator is non needed if the application uses response objects
933 and finishes the processing if the resonse is started::
934
935 try:
936 return response(environ, start_response)
937 finally:
938 cleanup_session()
939 cleanup_locals()
580940 """
581941
582942 def __init__(self, iterable, callbacks=None):
604964 callback()
605965
606966
607 class lazy_property(object):
608 """
609 Descriptor implementing a "lazy property", i.e. the function
610 calculating the property value is called only once.
967 class Href(object):
968 """
969 Implements a callable that constructs URLs with the given base. The
970 function can be called with any number of positional and keyword
971 arguments which than are used to assemble the URL. Works with URLs
972 and posix paths.
973
974 Positional arguments are appended as individual segments to
975 the path of the URL:
976
977 >>> href = Href('/foo')
978 >>> href('bar', 23)
979 '/foo/bar/23'
980 >>> href('foo', bar=23)
981 '/foo/foo?bar=23'
982
983 If any of the arguments (positional or keyword) evaluates to `None` it
984 will be skipped. If no keyword arguments are given the last argument
985 can be a `dict` or `MultiDict` (or any other dict subclass), otherwise
986 the keyword arguments are used for the query parameters, cutting off
987 the first trailing underscore of the parameter name:
988
989 >>> href(is_=42)
990 '/foo?is=42'
991
992 Accessing attributes on the href object creates a new href object with
993 the attribute name as prefix:
994
995 >>> bar_href = href.bar
996 >>> bar_href("blub")
997 '/foo/bar/blub'
998 """
999
1000 def __init__(self, base='./', charset='utf-8'):
1001 if not base:
1002 base = './'
1003 self.base = base
1004 self.charset = charset
1005
1006 def __getattr__(self, name):
1007 if name[:2] == '__':
1008 raise AttributeError(name)
1009 base = self.base
1010 if base[-1:] != '/':
1011 base += '/'
1012 return Href(urlparse.urljoin(base, name), self.charset)
1013
1014 def __call__(self, *path, **query):
1015 if query:
1016 if path and isinstance(path[-1], dict):
1017 query, path = path[-1], path[:-1]
1018 else:
1019 query = dict([(k.endswith('_') and k[:-1] or k, v)
1020 for k, v in query.items()])
1021 path = '/'.join([url_quote(x, self.charset) for x in path
1022 if x is not None]).lstrip('/')
1023 rv = self.base
1024 if path:
1025 if not rv.endswith('/'):
1026 rv += '/'
1027 rv = urlparse.urljoin(rv, path)
1028 if query:
1029 rv += '?' + url_encode(query, self.charset)
1030 return str(rv)
1031
1032
1033 class cached_property(object):
1034 """
1035 A decorator that converts a function into a lazy property. The
1036 function wrapped is called the first time to retrieve the result
1037 and than that calculated result is used the next time you access
1038 the value::
1039
1040 class Foo(object):
1041
1042 @cached_property
1043 def foo(self):
1044 # calculate something important here
1045 return 42
6111046 """
6121047
6131048 def __init__(self, func, name=None, doc=None):
6231058 return value
6241059
6251060
626 class environ_property(object):
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
1122 class environ_property(_DictAccessorProperty):
6271123 """
6281124 Maps request attributes to environment variables. This works not only
6291125 for the Werzeug request object, but also any other class with an
6451141 `read_only` to False it will block set/delete.
6461142 """
6471143
648 def __init__(self, name, default=None, convert=None, read_only=False,
649 doc=None):
650 self.name = name
651 self.default = default
652 self.convert = convert
653 self.read_only = read_only
654 self.__doc__ = doc
655
656 def __get__(self, obj, type=None):
657 if obj is None:
658 return self
659 rv = obj.environ.get(self.name, self.default)
660 if rv is self.default or self.convert is None:
661 return rv
662 try:
663 return self.convert(rv)
664 except (ValueError, TypeError):
665 return self.default
666
667 def __set__(self, obj, value):
668 if self.read_only:
669 raise AttributeError('read only property')
670 obj.environ[self.name] = value
671
672 def __delete__(self, obj):
673 if self.read_only:
674 raise AttributeError('read only property')
675 obj.environ.pop(self.name, None)
1144 def lookup(self, obj):
1145 return obj.environ
1146
1147
1148 class header_property(_DictAccessorProperty):
1149 """
1150 Like `environ_property` but for headers.
1151 """
1152
1153 def lookup(self, obj):
1154 return obj.headers
1155
1156
1157 class HTMLBuilder(object):
1158 """
1159 Helper object for HTML generation.
1160
1161 Per default there are two instances of that class. The `html` one, and
1162 the `xhtml` one for those two dialects. The class uses keyword parameters
1163 and positional parameters to generate small snippets of HTML.
1164
1165 Keyword parameters are converted to XML/SGML attributes, positional
1166 arguments are used as children. Because Python accepts positional
1167 arguments before keyword arguments it's a good idea to use a list with the
1168 star-syntax for some children:
1169
1170 >>> html.p(class_='foo', *[html.a('foo', href='foo.html'), ' ',
1171 ... html.a('bar', href='bar.html')])
1172 '<p class="foo"><a href="foo.html">foo</a> <a href="bar.html">bar</a></p>'
1173
1174 This class works around some browser limitations and can not be used for
1175 arbitrary SGML/XML generation. For that purpose lxml and similar
1176 libraries exist.
1177
1178 Calling the builder escapes the string passed:
1179
1180 >>> html.p(html("<foo>"))
1181 '<p>&lt;foo&gt;</p>'
1182 """
1183
1184 from htmlentitydefs import name2codepoint
1185 _entity_re = re.compile(r'&([^;]+);')
1186 _entities = name2codepoint.copy()
1187 _entities['apos'] = 39
1188 _empty_elements = set([
1189 'area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img',
1190 'input', 'isindex', 'link', 'meta', 'param'
1191 ])
1192 _boolean_attributes = set([
1193 'selected', 'checked', 'compact', 'declare', 'defer', 'disabled',
1194 'ismap', 'multiple', 'nohref', 'noresize', 'noshade', 'nowrap'
1195 ])
1196 _plaintext_elements = set(['textarea'])
1197 _c_like_cdata = set(['script', 'style'])
1198 del name2codepoint
1199
1200 def __init__(self, dialect):
1201 self._dialect = dialect
1202
1203 def __call__(self, s):
1204 return escape(s)
1205
1206 def __getattr__(self, tag):
1207 if tag[:2] == '__':
1208 raise AttributeError(tag)
1209 def proxy(*children, **arguments):
1210 buffer = ['<' + tag]
1211 write = buffer.append
1212 for key, value in arguments.iteritems():
1213 if value is None:
1214 continue
1215 if key.endswith('_'):
1216 key = key[:-1]
1217 if key in self._boolean_attributes:
1218 value = self._dialect == 'xhtml' and '="%s"' % key or ''
1219 else:
1220 value = '="%s"' % escape(value, True)
1221 write(' ' + key + value)
1222 if not children and tag in self._empty_elements:
1223 write(self._dialect == 'xhtml' and ' />' or '>')
1224 return ''.join(buffer)
1225 write('>')
1226 children_as_string = ''.join(children)
1227 if children_as_string:
1228 if tag in self._plaintext_elements:
1229 children_as_string = escape(children_as_string)
1230 elif tag in self._c_like_cdata and self._dialect == 'xhtml':
1231 children_as_string = '/*<![CDATA[*/%s/*]]>*/' % \
1232 children_as_string
1233 buffer.extend((children_as_string, '</%s>' % tag))
1234 return ''.join(buffer)
1235 return proxy
6761236
6771237 def __repr__(self):
678 return '<%s %s>' % (
1238 return '<%s for %r>' % (
6791239 self.__class__.__name__,
680 self.name
1240 self._dialect
6811241 )
1242
1243
1244 html = HTMLBuilder('html')
1245 xhtml = HTMLBuilder('xhtml')
1246
1247
1248 def get_content_type(mimetype, charset):
1249 """
1250 Return the full content type string with charset for a mimetype.
1251
1252 If the mimetype represents text the charset will be appended as charset
1253 parameter, otherwise the mimetype is returned unchanged.
1254 """
1255 if mimetype.startswith('text/') or \
1256 mimetype == 'application/xml' or \
1257 (mimetype.startswith('application/') and
1258 mimetype.endswith('+xml')):
1259 mimetype += '; charset=' + charset
1260 return mimetype
6821261
6831262
6841263 def format_string(string, context):
6991278 return _format_re.sub(lookup_arg, string)
7001279
7011280
702 def url_decode(s, charset='utf-8'):
703 """
704 Parse a querystring and return it as `MultiDict`.
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.
1286
1287 Per default a missing value for a key will default to an empty key. If
1288 you don't want that behavior you can set `include_empty` to `False`.
7051289 """
7061290 tmp = []
707 for key, values in cgi.parse_qs(str(s)).iteritems():
1291 for key, values in cgi.parse_qs(str(s), include_empty).iteritems():
7081292 for value in values:
1293 if decode_keys:
1294 key = key.decode(charset, 'ignore')
7091295 tmp.append((key, value.decode(charset, 'ignore')))
7101296 return MultiDict(tmp)
7111297
7121298
713 def url_encode(obj, charset='utf-8'):
714 """Urlencode a dict/MultiDict."""
1299 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
1303 charset strings. If `encode_keys` is set to ``True`` unicode keys are
1304 supported too.
1305 """
7151306 if obj is None:
7161307 items = []
7171308 elif isinstance(obj, MultiDict):
7221313 items = obj
7231314 tmp = []
7241315 for key, values in items:
1316 if encode_keys and isinstance(key, unicode):
1317 key = key.encode(charset)
1318 else:
1319 key = str(key)
7251320 for value in values:
7261321 if value is None:
7271322 continue
7341329 return '&'.join(tmp)
7351330
7361331
737 def url_quote(s, charset='utf-8'):
1332 def url_quote(s, charset='utf-8', safe='/:'):
7381333 """
7391334 URL encode a single string with a given encoding.
7401335 """
7411336 if isinstance(s, unicode):
7421337 s = s.encode(charset)
743 return urllib.quote(s)
744
745
746 def url_quote_plus(s, charset='utf-8'):
1338 elif not isinstance(s, str):
1339 s = str(s)
1340 return urllib.quote(s, safe=safe)
1341
1342
1343 def url_quote_plus(s, charset='utf-8', safe=''):
7471344 """
7481345 URL encode a single string with the given encoding and convert
7491346 whitespace to "+".
7501347 """
7511348 if isinstance(s, unicode):
7521349 s = s.encode(charset)
753 return urllib.quote_plus(s)
1350 elif not isinstance(s, str):
1351 s = str(s)
1352 return urllib.quote_plus(s, safe=safe)
7541353
7551354
7561355 def url_unquote(s, charset='utf-8'):
7681367 return urllib.unquote_plus(s).decode(charset, 'ignore')
7691368
7701369
771 escape = cgi.escape
1370 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
1373 it contains unsafe characters like ' ' and so on. This function can fix
1374 some of the problems in a similar way browsers handle data entered by the
1375 user:
1376
1377 >>> url_fix(u'http://de.wikipedia.org/wiki/Elf (Begriffsklärung)')
1378 'http://de.wikipedia.org/wiki/Elf%20%28Begriffskl%C3%A4rung%29'
1379
1380 :param charset: The target charset for the URL if the url was given as
1381 unicode string.
1382 """
1383 if isinstance(s, unicode):
1384 s = s.encode(charset, 'ignore')
1385 scheme, netloc, path, qs, anchor = urlparse.urlsplit(s)
1386 path = urllib.quote(path, '/%')
1387 qs = urllib.quote_plus(qs, ':&=')
1388 return urlparse.urlunsplit((scheme, netloc, path, qs, anchor))
1389
1390
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.
1396
1397 There is a special handling for `None` which escapes to an empty string.
1398 """
1399 if s is None:
1400 return ''
1401 elif not isinstance(s, basestring):
1402 s = unicode(s)
1403 s = s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
1404 if quote:
1405 s = s.replace('"', "&quot;")
1406 return s
1407
1408
1409 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`.
1413 """
1414 def handle_match(m):
1415 name = m.group(1)
1416 if name in HTMLBuilder._entities:
1417 return unichr(HTMLBuilder._entities[name])
1418 if name[:2] in ('#x', '#X'):
1419 try:
1420 return unichr(int(name[2:], 16))
1421 except ValueError:
1422 return u''
1423 elif name.startswith('#'):
1424 try:
1425 return unichr(int(name[1:]))
1426 except ValueError:
1427 return u''
1428 return u''
1429 return _entity_re.sub(handle_match, s)
7721430
7731431
7741432 def get_host(environ):
7751433 """
776 Return the real host for the given environment.
1434 Return the real host for the given WSGI enviornment. This takes care
1435 of the `X-Forwarded-Host` header.
7771436 """
7781437 if 'HTTP_X_FORWARDED_HOST' in environ:
7791438 return environ['HTTP_X_FORWARDED_HOST']
7891448 def get_current_url(environ, root_only=False, strip_querystring=False,
7901449 host_only=False):
7911450 """
792 Recreate the URL of the current request.
1451 A handy helper function that recreates the full URL for the current
1452 request or parts of it. Here an example:
1453
1454 >>> env = create_environ("/?param=foo", "http://localhost/script")
1455 >>> get_current_url(env)
1456 'http://localhost/script/?param=foo'
1457 >>> get_current_url(env, root_only=True)
1458 'http://localhost/script/'
1459 >>> get_current_url(env, host_only=True)
1460 'http://localhost/'
1461 >>> get_current_url(env, strip_querystring=True)
1462 'http://localhost/script/'
7931463 """
7941464 tmp = [environ['wsgi.url_scheme'], '://']
7951465 cat = tmp.append
8131483 return ''.join(tmp)
8141484
8151485
816 def cookie_date(expires, _date_delim='-'):
1486 def cookie_date(expires=None, _date_delim='-'):
8171487 """
8181488 Formats the time to ensure compatibility with Netscape's cookie standard.
8191489
8201490 Accepts a floating point number expressed in seconds since the epoc in, a
821 datetime object or a timetuple. All times in UTC.
822
823 Outputs a string in the format 'Wdy, DD-Mon-YYYY HH:MM:SS GMT'.
824 """
825 if isinstance(expires, datetime):
1491 datetime object or a timetuple. All times in UTC. The `parse_date`
1492 function in `werkzeug.http` can be used to parse such a date.
1493
1494 Outputs a string in the format ``Wdy, DD-Mon-YYYY HH:MM:SS GMT``.
1495 """
1496 if expires is None:
1497 expires = gmtime()
1498 elif isinstance(expires, datetime):
8261499 expires = expires.utctimetuple()
827 elif isinstance(expires, (int, long)):
1500 elif isinstance(expires, (int, long, float)):
8281501 expires = gmtime(expires)
8291502
8301503 return '%s, %02d%s%s%s%s %02d:%02d:%02d GMT' % (
8411514 )
8421515
8431516
844 def http_date(timestamp):
1517 def parse_cookie(header, charset='utf-8'):
1518 """Parse a cookie. Either from a string or WSGI environ."""
1519 if isinstance(header, dict):
1520 header = header.get('HTTP_COOKIE', '')
1521 cookie = _ExtendedCookie()
1522 cookie.load(header)
1523 result = {}
1524
1525 # decode to unicode and skip broken items. Our extended morsel
1526 # and extended cookie will catch CookieErrors and convert them to
1527 # `None` items which we have to skip here.
1528 for key, value in cookie.iteritems():
1529 if value.value is not None:
1530 result[key] = value.value.decode(charset, 'ignore')
1531
1532 return result
1533
1534
1535 def dump_cookie(key, value='', max_age=None, expires=None, path='/',
1536 domain=None, secure=None, httponly=False, charset='utf-8',
1537 sync_expires=True):
1538 """
1539 Creates a new Set-Cookie header without the ``Set-Cookie`` prefix
1540 The parameters are the same as in the cookie Morsel object in the
1541 Python standard library but it accepts unicode data too.
1542
1543 :param max_age: should be a number of seconds, or `None` (default) if
1544 the cookie should last only as long as the client's
1545 browser session. Additionally `timedelta` objects
1546 are accepted too.
1547 :param expires: should be a `datetime` object or unix timestamp.
1548 :param path: limits the cookie to a given path, per default it will
1549 span the whole domain.
1550 :param domain: Use this if you want to set a cross-domain cookie. For
1551 example, ``domain=".example.com"`` will set a cookie
1552 that is readable by the domain ``www.example.com``,
1553 ``foo.example.com`` etc. Otherwise, a cookie will only
1554 be readable by the domain that set it.
1555 :param secure: The cookie will only be available via HTTPS
1556 :param httponly: disallow JavaScript to access the cookie. This is an
1557 extension to the cookie standard and probably not
1558 supported by all browsers.
1559 :param charset: the encoding for unicode values.
1560 :param sync_expires: automatically set expires if max_age is defined
1561 but expires not.
1562 """
1563 try:
1564 key = str(key)
1565 except UnicodeError:
1566 raise TypeError('invalid key %r' % key)
1567 if isinstance(value, unicode):
1568 value = value.encode(charset)
1569 morsel = _ExtendedMorsel(key, value)
1570 if isinstance(max_age, timedelta):
1571 max_age = (max_age.days * 60 * 60 * 24) + max_age.seconds
1572 if expires is not None:
1573 if not isinstance(expires, basestring):
1574 expires = cookie_date(expires)
1575 morsel['expires'] = expires
1576 elif max_age is not None and sync_expires:
1577 morsel['expires'] = cookie_date(time() + max_age)
1578 for k, v in (('path', path), ('domain', domain), ('secure', secure),
1579 ('max-age', max_age), ('httponly', httponly)):
1580 if v is not None and v is not False:
1581 morsel[k] = str(v)
1582 return morsel.output(header='').lstrip()
1583
1584
1585 def http_date(timestamp=None):
8451586 """
8461587 Formats the time to match the RFC1123 date format.
8471588
8481589 Accepts a floating point number expressed in seconds since the epoc in, a
849 datetime object or a timetuple. All times in UTC.
850
851 Outputs a string in the format 'Wdy, DD Mon YYYY HH:MM:SS GMT'.
1590 datetime object or a timetuple. All times in UTC. The `parse_date`
1591 function in `werkzeug.http` can be used to parse such a date.
1592
1593 Outputs a string in the format ``Wdy, DD Mon YYYY HH:MM:SS GMT``.
8521594 """
8531595 return cookie_date(timestamp, ' ')
8541596
8741616 return response
8751617
8761618
1619 def append_slash_redirect(environ, code=301):
1620 """
1621 Redirect to the same URL but with a slash appended. The behavior
1622 of this function is undefined if the path ends with a slash already.
1623 """
1624 new_path = environ['PATH_INFO'].strip('/') + '/'
1625 query_string = environ['QUERY_STRING']
1626 if query_string:
1627 new_path += '?' + query_string
1628 if not new_path.startswith('/'):
1629 new_path = '/' + new_path
1630 return redirect(new_path)
1631
1632
1633 def responder(f):
1634 """
1635 Marks a function as responder. Decorate a function with it and it
1636 will automatically call the return value as WSGI application.
1637
1638 Example::
1639
1640 @responder
1641 def application(environ, start_response):
1642 return Response('Hello World!')
1643 """
1644 return _patch_wrapper(f, lambda *a: f(*a)(*a[-2:]))
1645
1646
1647 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
1651 be specified either in dotted notation (``xml.sax.saxutils.escape``)
1652 or with a colon as object delimiter (``xml.sax.saxutils:escape``).
1653
1654 If the `silent` is True the return value will be `None` if the import
1655 fails.
1656
1657 :return: imported object
1658 """
1659 try:
1660 if ':' in import_name:
1661 module, obj = import_name.split(':', 1)
1662 elif '.' in import_name:
1663 items = import_name.split('.')
1664 module = '.'.join(items[:-1])
1665 obj = items[-1]
1666 else:
1667 return __import__(import_name)
1668 return getattr(__import__(module, None, None, [obj]), obj)
1669 except (ImportError, AttributeError):
1670 if not silent:
1671 raise
1672
1673
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
1693 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.
1698
1699 Packages are not returned unless `include_packages` is `True`. This can
1700 also recursively list modules but in that case it will import all the
1701 packages to get the correct load path of that module.
1702
1703 :return: generator
1704 """
1705 module = import_string(import_path)
1706 path = getattr(module, '__path__', None)
1707 if path is None:
1708 raise ValueError('%r is not a package' % import_path)
1709 basename = module.__name__ + '.'
1710 for modname, ispkg in _iter_modules(path):
1711 modname = basename + modname
1712 if ispkg:
1713 if include_packages:
1714 yield modname
1715 if recursive:
1716 for item in find_modules(modname, include_packages, True):
1717 yield item
1718 else:
1719 yield modname
1720
1721
8771722 def create_environ(path='/', base_url=None, query_string=None, method='GET',
8781723 input_stream=None, content_type=None, content_length=0,
8791724 errors_stream=None, multithread=False,
8811726 """
8821727 Create a new WSGI environ dict based on the values passed. The first
8831728 parameter should be the path of the request which defaults to '/'.
884 The second one can either be a absolute path (in that case the url
1729 The second one can either be a absolute path (in that case the URL
8851730 host is localhost:80) or a full path to the request with scheme,
8861731 netloc port and the path to the script.
1732
1733 If the `path` contains a query string it will be used, even if the
1734 `query_string` parameter was given. If it does not contain one
1735 the `query_string` parameter is used as querystring. In that case
1736 it can either be a dict, MultiDict or string.
1737
1738 The following options exist:
1739
1740 `method`
1741 The request method. Defaults to `GET`
1742
1743 `input_stream`
1744 The input stream. Defaults to an empty read only stream.
1745
1746 `content_type`
1747 The content type for this request. Default is an empty content
1748 type.
1749
1750 `content_length`
1751 The value for the content length header. Defaults to 0.
1752
1753 `errors_stream`
1754 The wsgi.errors stream. Defaults to `sys.stderr`.
1755
1756 `multithread`
1757 The multithreaded flag for the WSGI Environment. Defaults to `False`.
1758
1759 `multiprocess`
1760 The multiprocess flag for the WSGI Environment. Defaults to `False`.
1761
1762 `run_once`
1763 The run_once flag for the WSGI Environment. Defaults to `False`.
8871764 """
8881765 if base_url is not None:
8891766 scheme, netloc, script_name, qs, fragment = urlparse.urlsplit(base_url)
9351812 """
9361813 Return a tuple in the form (app_iter, status, headers) of the application
9371814 output. This works best if you pass it an application that returns a
938 generator all the time.
1815 iterator all the time.
9391816
9401817 Sometimes applications may use the `write()` callable returned
9411818 by the `start_response` function. This tries to resolve such edge
9421819 cases automatically. But if you don't get the expected output you
9431820 should set `buffered` to `True` which enforces buffering.
944 """
945 # TODO: only read until a response is set, then return a closing
946 # iterator that yields the buffer first and then the data.
1821
1822 If passed an invalid WSGI application the behavior of this function is
1823 undefined. Never pass non-conforming WSGI applications to this function.
1824 """
9471825 response = []
9481826 buffer = []
9491827
9551833
9561834 app_iter = app(environ, start_response)
9571835
958 if buffered or buffer or not response:
1836 # when buffering we emit the close call early and conver the
1837 # application iterator into a regular list
1838 if buffered:
1839 close_func = getattr(app_iter, 'close', None)
9591840 try:
960 buffer.extend(app_iter)
1841 app_iter = list(app_iter)
9611842 finally:
962 if hasattr(app_iter, 'close'):
963 app_iter.close()
964 app_iter = buffer
1843 if close_func is not None:
1844 close_func()
1845
1846 # otherwise we iterate the application iter until we have
1847 # a response, chain the already received data with the already
1848 # collected data and wrap it in a new `ClosingIterator` if
1849 # we have a close callable.
1850 else:
1851 while not response:
1852 buffer.append(app_iter.next())
1853 if buffer:
1854 app_iter = chain(buffer, app_iter)
1855 close_func = getattr(app_iter, 'close', None)
1856 if close_func is not None:
1857 app_iter = ClosingIterator(app_iter, close_func)
9651858
9661859 return app_iter, response[0], response[1]
22 werkzeug.wrappers
33 ~~~~~~~~~~~~~~~~~
44
5 This module provides simple wrappers around `environ`,
6 `start_response` and `wsgi.input`.
7
8 :copyright: 2007 by Armin Ronacher, Georg Brandl.
5 The wrappers are simple request and response objects which you can
6 subclass to do whatever you want them to do. The request object contains
7 the information transmitted by the client (webbrowser) and the response
8 object contains all the information sent back to the browser.
9
10 An important detail is that the request object is created with the WSGI
11 environ and will act as high-level proxy whereas the response object is an
12 actual WSGI application.
13
14 Like everything else in Werkzeug these objects will work correctly with
15 unicode data. Incoming form data parsed by the response object will be
16 decoded into an unicode object if possible and if it makes sense.
17
18
19 :copyright: 2007-2008 by Armin Ronacher, Georg Brandl.
920 :license: BSD, see LICENSE for more details.
1021 """
1122 import cgi
1223 import tempfile
1324 import urlparse
14 try:
15 from hashlib import md5
16 except ImportError:
17 from md5 import new as md5
18 from Cookie import SimpleCookie
25 from datetime import datetime, timedelta
1926 from werkzeug.http import HTTP_STATUS_CODES, Accept, CacheControl, \
20 parse_accept_header, parse_cache_control_header
27 parse_accept_header, parse_cache_control_header, parse_etags, \
28 parse_date, generate_etag, is_resource_modified, unquote_etag, \
29 quote_etag, parse_set_header
2130 from werkzeug.utils import MultiDict, CombinedMultiDict, FileStorage, \
22 Headers, EnvironHeaders, lazy_property, environ_property, \
31 Headers, EnvironHeaders, cached_property, environ_property, \
2332 get_current_url, create_environ, url_encode, run_wsgi_app, get_host, \
24 cookie_date, http_date, escape, _empty_stream
33 cookie_date, parse_cookie, dump_cookie, http_date, escape, \
34 header_property, get_content_type, _empty_stream
2535
2636
2737 class _StorageHelper(cgi.FieldStorage):
6272
6373 class BaseRequest(object):
6474 """
65 Base Request class.
75 Very basic request object. This does not implement advanced stuff like
76 entity tag parsing or cache controls. The request object is created with
77 the WSGI environment as first argument and will add itself to the WSGI
78 environment as ``'werkzeug.request'`` unless it's created with
79 `populate_request` set to False.
80
81 There are a couple of mixins available that add additional functionality
82 to the request object, there is also a class called `Request` which
83 subclasses `BaseRequest` and all the important mixins.
84
85 It's a good idea to create a custom subclass of the `BaseRequest` and add
86 missing functionality either via mixins or direct implementation. Here
87 an example for such subclasses::
88
89 from werkzeug import BaseRequest, ETagRequestMixin
90
91 class Request(BaseRequest, ETagRequestMixin):
92 pass
93
94 Request objects should be considered *read only*. Even though the object
95 doesn't enforce read only access everywhere you should never modify any
96 data on the object itself unless you know exactly what you are doing.
6697 """
6798 charset = 'utf-8'
6899 is_behind_proxy = False
69100
70101 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`.
106 """
71107 self.environ = environ
72108 if populate_request:
73109 self.environ['werkzeug.request'] = self
75111
76112 def from_values(cls, path='/', base_url=None, query_string=None, **options):
77113 """
78 Create a new request object based on the values provided. If
79 environ is given missing values are filled from there. This method
80 is useful for small scripts when you need to simulate a request
81 from an url. Do not use this method for unittesting, there is a
82 full featured client object in `werkzeug.test` that allows to create
83 multipart requests etc.
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
118 object in `werkzeug.test` that allows to create multipart requests
119 etc.
120
121 This accepts the same options as the `create_environ` function from the
122 utils module and additionally an `environ` parameter that can contain
123 values which will override the values from dict returned by
124 `create_environ`.
125
126 Additionally a dict passed to `query_string` will be encoded in the
127 request class charset.
128
129 :return: request object
84130 """
85131 if isinstance(query_string, dict):
86132 query_string = url_encode(query_string, cls.charset)
94140 from_values = classmethod(from_values)
95141
96142 def _get_file_stream(self):
97 """Called to get a stream for the file upload.
143 """
144 Called to get a stream for the file upload.
98145
99146 This must provide a file-like class with `read()`, `readline()`
100 and `seek()` methods that is both writeable and readable."""
147 and `seek()` methods that is both writeable and readable.
148
149 The default implementation returns a temporary file.
150 """
101151 return tempfile.TemporaryFile('w+b')
102152
103 def _load_post_data(self):
104 """Method used internally to retrieve submitted data."""
153 def _load_form_data(self):
154 """
155 Method used internally to retrieve submitted data. After calling
156 this sets `_form` and `_files` on the request object to multi dicts
157 filled with the incoming form data. As a matter of fact the input
158 stream will be empty afterwards.
159
160 :internal:
161 """
105162 self._data_stream = _empty_stream
106 post = []
163 form = []
107164 files = []
108165 if self.environ['REQUEST_METHOD'] in ('POST', 'PUT'):
109166 storage = _StorageHelper(self.environ, self._get_file_stream)
118175 if getattr(item, 'filename', None) is not None:
119176 fn = item.filename.decode(self.charset, 'ignore')
120177 # fix stupid IE bug (IE6 sends the whole path)
121 if fn[1:3] == ':\\':
178 if fn[1:3] == ':\\' or fn[:2] == '\\\\':
122179 fn = fn.split('\\')[-1]
123 files.append((key, FileStorage(key, fn, item.type,
124 item.length, item.file)))
180 files.append((key, FileStorage(item.file, fn,
181 key, item.type, item.length)))
125182 else:
126 post.append((key, item.value.decode(self.charset,
183 form.append((key, item.value.decode(self.charset,
127184 'ignore')))
128 self._form = MultiDict(post)
185 self._form = MultiDict(form)
129186 self._files = MultiDict(files)
130187
131188 def stream(self):
132189 """
133190 The parsed stream if the submitted data was not multipart or
134 urlencoded form data.
191 urlencoded form data. This stream is the stream left by the CGI
192 module after parsing. This is *not* the WSGI input stream.
135193 """
136194 if self._data_stream is None:
137 self._load_post_data()
195 self._load_form_data()
138196 return self._data_stream
139197 stream = property(stream, doc=stream)
140198 input_stream = environ_property('wsgi.input', 'The WSGI input stream.')
141199
142200 def args(self):
143 """URL parameters"""
201 """The parsed URL parameters as `MultiDict`."""
144202 items = []
145203 qs = self.environ.get('QUERY_STRING', '')
146204 for key, values in cgi.parse_qs(qs, True).iteritems():
148206 value = value.decode(self.charset, 'ignore')
149207 items.append((key, value))
150208 return MultiDict(items)
151 args = lazy_property(args)
209 args = cached_property(args)
152210
153211 def data(self):
154212 """
155213 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.
156216 """
157217 return self.stream.read()
158 data = lazy_property(data)
218 data = cached_property(data)
159219
160220 def form(self):
161221 """
165225 uses a dict internally and loses the ordering.
166226 """
167227 if not hasattr(self, '_form'):
168 self._load_post_data()
228 self._load_form_data()
169229 return self._form
170 form = lazy_property(form)
230 form = property(form, doc=form.__doc__)
171231
172232 def values(self):
173 """combined multi dict for `args` and `form`"""
233 """Combined multi dict for `args` and `form`"""
174234 return CombinedMultiDict([self.args, self.form])
175 values = lazy_property(values)
235 values = cached_property(values)
176236
177237 def files(self):
178 """File uploads."""
238 """
239 A `MultiDict` containing all uploaded files. Each key in
240 `files` is the name from the ``<input type="file" name="" />``. Each
241 value in `files` is a Werkzeug `FileStorage` object.
242 """
179243 if not hasattr(self, '_files'):
180 self._load_post_data()
244 self._load_form_data()
181245 return self._files
182 files = lazy_property(files)
246 files = property(files, doc=files.__doc__)
183247
184248 def cookies(self):
185 """Stored Cookies."""
186 cookie = SimpleCookie()
187 cookie.load(self.environ.get('HTTP_COOKIE', ''))
188 result = {}
189 for key, value in cookie.iteritems():
190 result[key] = value.value.decode(self.charset, 'ignore')
191 return result
192 cookies = lazy_property(cookies)
249 """The retreived cookie values as regular dictionary."""
250 return parse_cookie(self.environ, self.charset)
251 cookies = cached_property(cookies)
193252
194253 def headers(self):
195 """The headers from the WSGI environ."""
254 """The headers from the WSGI environ as immutable `EnvironHeaders`."""
196255 return EnvironHeaders(self.environ)
197 headers = lazy_property(headers)
198
199 def accept_mimetypes(self):
200 """List of mimetypes this client supports."""
201 if not 'HTTP_ACCEPT' in self.environ:
202 return Accept(None)
203 return parse_accept_header(self.environ['HTTP_ACCEPT'])
204 accept_mimetypes = lazy_property(accept_mimetypes)
205
206 def accept_charsets(self):
207 """list of charsets this client supports."""
208 if not 'HTTP_ACCEPT_CHARSET' in self.environ:
209 return Accept(None)
210 return parse_accept_header(self.environ['HTTP_ACCEPT_CHARSET'])
211 accept_charsets = lazy_property(accept_charsets)
212
213 def accept_encodings(self):
214 """
215 List of encodings this client accepts. Encodings in a HTTP term are
216 compression encodings such as gzip. For charsets have a look at
217 `accept_charset`.
218 """
219 if not 'HTTP_ACCEPT_ENCODING' in self.environ:
220 return Accept(None)
221 return parse_accept_header(self.environ['HTTP_ACCEPT_ENCODING'])
222 accept_encodings = lazy_property(accept_encodings)
223
224 def accept_languages(self):
225 """List of languages this client accepts."""
226 if not 'HTTP_ACCEPT_LANGUAGE' in self.environ:
227 return Accept(None)
228 return parse_accept_header(self.environ['HTTP_ACCEPT_LANGUAGE'])
229 accept_languages = lazy_property(accept_languages)
230
231 def cache_control(self):
232 """A `CacheControl` object for the incoming cache control headers."""
233 if not 'HTTP_CACHE_CONTROL' in self.environ:
234 return CacheControl(None)
235 return parse_cache_control_header(self.environ['HTTP_CACHE_CONTROL'])
256 headers = cached_property(headers)
236257
237258 def path(self):
238 """Requested path."""
259 """
260 Requested path as unicode. This works a bit like the regular path
261 info in the WSGI environment but will always include a leading slash,
262 even if the URL root is accessed.
263 """
239264 path = '/' + (self.environ.get('PATH_INFO') or '').lstrip('/')
240265 return path.decode(self.charset, 'ignore')
241 path = lazy_property(path)
266 path = cached_property(path)
242267
243268 def script_root(self):
244 """The root path of the script."""
269 """The root path of the script without the trailing slash."""
245270 path = (self.environ.get('SCRIPT_NAME') or '').rstrip('/')
246271 return path.decode(self.charset, 'ignore')
247 script_root = lazy_property(script_root)
272 script_root = cached_property(script_root)
248273
249274 def url(self):
250275 """The reconstructed current URL"""
251276 return get_current_url(self.environ)
252 url = lazy_property(url)
277 url = cached_property(url)
253278
254279 def base_url(self):
255280 """Like `url` but without the querystring"""
256281 return get_current_url(self.environ, strip_querystring=True)
257 base_url = lazy_property(base_url)
282 base_url = cached_property(base_url)
258283
259284 def url_root(self):
260285 """The full URL root (with hostname), this is the application root."""
261286 return get_current_url(self.environ, True)
262 url_root = lazy_property(url_root)
287 url_root = cached_property(url_root)
263288
264289 def host_url(self):
265290 """Just the host with scheme."""
266291 return get_current_url(self.environ, host_only=True)
267 host_url = lazy_property(host_url)
292 host_url = cached_property(host_url)
268293
269294 def host(self):
270295 """Just the host including the port if available."""
271296 return get_host(self.environ)
272 host = lazy_property(host)
273
274 def is_secure(self):
275 """True if the request is secure."""
276 return self.environ['wsgi.url_scheme'] == 'https'
277 is_secure = property(is_secure, doc=is_secure.__doc__)
278
279 query_string = environ_property('QUERY_STRING', '', read_only=True)
280 remote_addr = environ_property('REMOTE_ADDR', read_only=True)
281 method = environ_property('REQUEST_METHOD', 'GET', read_only=True)
297 host = cached_property(host)
298
299 query_string = environ_property('QUERY_STRING', '', read_only=True, doc=
300 '''The URL parameters as raw bytestring.''')
301 method = environ_property('REQUEST_METHOD', 'GET', read_only=True, doc=
302 '''The transmission method. (For example ``'GET'`` or ``'POST'``).''')
282303
283304 def access_route(self):
284305 """
291312 elif 'REMOTE_ADDR' in self.environ:
292313 return [self.environ['REMOTE_ADDR']]
293314 return []
294 access_route = lazy_property(access_route)
315 access_route = cached_property(access_route)
295316
296317 def remote_addr(self):
297318 """The remote address of the client."""
300321 return self.environ.get('REMOTE_ADDR')
301322 remote_addr = property(remote_addr)
302323
303 def is_xhr(self):
304 """
324 is_xhr = property(lambda x: x.environ.get('X_REQUESTED_WITH') ==
325 'XmlHttpRequest', doc='''
305326 True if the request was triggered via an JavaScript XMLHttpRequest.
306327 This only works with libraries that support the X-Requested-With
307328 header and set it to "XMLHttpRequest". Libraries that do that are
308 prototype, jQuery and Mochikit.
309 """
310 return self.environ.get('X_REQUESTED_WITH') == 'XmlHttpRequest'
311 is_xhr = property(is_xhr, doc=is_xhr.__doc__)
329 prototype, jQuery and Mochikit and probably some more.''')
330 is_secure = property(lambda x: x.environ['wsgi.url_scheme'] == 'https',
331 doc='`True` if the request is secure.')
332 is_multithread = environ_property('wsgi.multithread', doc='''
333 boolean that is `True` if the application is served by
334 a multithreaded WSGI server.''')
335 is_multiprocess = environ_property('wsgi.multiprocess', doc='''
336 boolean that is `True` if the application is served by
337 a WSGI server that spawns multiple processes.''')
338 is_run_once = environ_property('wsgi.run_once', doc='''
339 boolean that is `True` if the application will be executed only
340 once in a process lifetime. This is the case for CGI for example,
341 but it's not guaranteed that the exeuction only happens one time.''')
312342
313343
314344 class BaseResponse(object):
315345 """
316 Base response class.
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
349 valid WSGI response when called with the environ and start response
350 callable.
351
352 Because it's a WSGI application itself processing usually ends before the
353 actual response is sent to the server. This helps debugging systems
354 because they can catch all the exceptions before responses are started.
355
356 Here a small example WSGI application that takes advantage of the
357 response objects::
358
359 from werkzeug import BaseResponse as Response
360
361 def index():
362 return Response('Index page')
363
364 def application(environ, start_response):
365 path = environ.get('PATH_INFO') or '/'
366 if path == '/':
367 response = index()
368 else:
369 response = Response('Not Found', status=404)
370 return response(environ, start_response)
371
372 Like `BaseRequest` which object is lacking a lot of functionality
373 implemented in mixins. This gives you a better control about the actual
374 API of your response objects, so you can create subclasses and add custom
375 functionality. A full featured response object is available as `Response`
376 which implements a couple of useful mixins.
377
378 To enforce a new type of already existing responses you can use the
379 `force_type` method. This is useful if you're working with different
380 subclasses of response objects and you want to post process them with a
381 know interface.
317382 """
318383 charset = 'utf-8'
384 default_status = 200
319385 default_mimetype = 'text/plain'
320386
321 def __init__(self, response=None, status=200, headers=None, mimetype=None,
322 content_type=None):
387 def __init__(self, response=None, status=None, headers=None,
388 mimetype=None, content_type=None):
323389 """
324390 Response can be any kind of iterable or string. If it's a string it's
325391 considered being an iterable with one item which is the string passed.
347413 if content_type is None:
348414 if mimetype is None and 'Content-Type' not in self.headers:
349415 mimetype = self.default_mimetype
350 if mimetype is not None and mimetype.startswith('text/'):
351 mimetype += '; charset=' + self.charset
416 if mimetype is not None:
417 mimetype = get_content_type(mimetype, self.charset)
352418 content_type = mimetype
353419 if content_type is not None:
354420 self.headers['Content-Type'] = content_type
421 if status is None:
422 status = self.default_status
355423 if isinstance(status, (int, long)):
356424 self.status_code = status
357425 else:
358426 self.status = status
427
428 def force_type(cls, response, environ=None):
429 """
430 Enforce that the WSGI response is a response object of the current
431 type. Werkzeug will use the `BaseResponse` internally in many
432 situations like the exceptions. If you call `get_response` on an
433 exception you will get back a regular `BaseResponse` object, even if
434 you are using a custom subclass.
435
436 This method can enforce a given response type, and it will also
437 convert arbitrary WSGI callables into response objects if an environ
438 is provided::
439
440 # convert a Werkzeug response object into an instance of the
441 # MyResponseClass subclass.
442 response = MyResponseClass.force_type(response)
443
444 # convert any WSGI application into a request object
445 response = MyResponseClass.force_type(response, environ)
446
447 This is especially useful if you want to post-process responses in
448 the main dispatcher and use functionality provided by your subclass.
449
450 Keep in mind that this will modify response objects in place if
451 possible!
452 """
453 if not isinstance(response, BaseResponse):
454 if environ is None:
455 raise TypeError('cannot convert WSGI application into '
456 'response objects without an environ')
457 response = BaseResponse(*run_wsgi_app(response, environ))
458 response.__class__ = cls
459 return response
460 force_type = classmethod(force_type)
359461
360462 def from_app(cls, app, environ, buffered=False):
361463 """
370472 from_app = classmethod(from_app)
371473
372474 def _get_status_code(self):
373 return int(self.status.split(None, 1)[0])
475 try:
476 return int(self.status.split(None, 1)[0])
477 except ValueError:
478 return 0
374479 def _set_status_code(self, code):
375 self.status = '%d %s' % (code, HTTP_STATUS_CODES[code].upper())
480 try:
481 self.status = '%d %s' % (code, HTTP_STATUS_CODES[code].upper())
482 except KeyError:
483 self.status = '%d UNKNOWN' % code
376484 status_code = property(_get_status_code, _set_status_code,
377 'Get the HTTP Status code as number')
485 'The HTTP Status code as number')
378486 del _get_status_code, _set_status_code
379487
380 def cache_control(self):
381 def on_update(cache_control):
382 if not cache_control and 'cache-control' in self.headers:
383 del self.headers['cache-control']
384 elif cache_control:
385 self.headers['Cache-Control'] = cache_control.to_header()
386 value = self.headers.get('Cache-Control')
387 if value is not None:
388 value = parse_cache_control_header(value)
389 return CacheControl(value, on_update)
390 cache_control = lazy_property(cache_control)
391
392488 def write(self, data):
393 """If we have a buffered response this writes to the buffer."""
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`.'))
394499 if not isinstance(self.response, list):
395500 raise RuntimeError('cannot write to a streamed response.')
396501 self.response.append(data)
397502
398503 def writelines(self, lines):
399 """Write lines."""
504 """
505 **deprecated**
506
507 :see: `write`
508 """
400509 self.write(''.join(lines))
401510
402 def _get_response_body(self):
511 def _get_data(self):
403512 """
404513 The string representation of the request body. Whenever you access
405514 this property the request iterable is encoded and flattened. This
408517 if not isinstance(self.response, list):
409518 self.response = list(self.response)
410519 return ''.join(self.iter_encoded())
411 def _set_response_body(self, value):
412 """Set a new string as response body."""
520 def _set_data(self, value):
413521 self.response = [value]
414 response_body = property(_get_response_body, _set_response_body,
415 doc=_get_response_body.__doc__)
416 del _get_response_body, _set_response_body
522 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
417535
418536 def iter_encoded(self, charset=None):
419537 """
430548
431549 def set_cookie(self, key, value='', max_age=None, expires=None,
432550 path='/', domain=None, secure=None, httponly=False):
433 """Set a new cookie."""
434 try:
435 key = str(key)
436 except UnicodeError:
437 raise TypeError('invalid key %r' % key)
438 c = SimpleCookie()
439 if isinstance(value, unicode):
440 value = value.encode(self.charset)
441 c[key] = value
442 if expires is not None:
443 if not isinstance(expires, basestring):
444 expires = cookie_date(expires)
445 c[key]['expires'] = expires
446 for k, v in (('path', path), ('domain', domain), ('secure', secure),
447 ('max-age', max_age), ('HttpOnly', httponly)):
448 if v is not None and v is not False:
449 c[key][k] = str(v)
450 self.headers.add('Set-Cookie', c[key].output(header='').lstrip())
551 """
552 Sets a cookie. The parameters are the same as in the cookie `Morsel`
553 object in the Python standard library but it accepts unicode data too:
554
555 - `max_age` should be a number of seconds, or `None` (default) if the
556 cookie should last only as long as the client’s browser session.
557 - `expires` should be a `datetime` object or UNIX timestamp.
558 - Use `domain` if you want to set a cross-domain cookie. For example,
559 ``domain=".example.com"`` will set a cookie that is readable by the
560 domain ``www.example.com``, ``foo.example.com`` etc. Otherwise, a
561 cookie will only be readable by the domain that set it.
562 - `path` limits the cookie to a given path, per default it will span
563 the whole domain.
564 """
565 self.headers.add('Set-Cookie', dump_cookie(key, value, max_age,
566 expires, path, domain, secure, httponly,
567 self.charset))
451568
452569 def delete_cookie(self, key, path='/', domain=None):
453 """Delete a cookie."""
570 """Delete a cookie. Fails silently if key doesn't exist."""
454571 self.set_cookie(key, expires=0, max_age=0, path=path, domain=domain)
455572
456573 def header_list(self):
463580
464581 def is_streamed(self):
465582 """
466 If the request is streamed (the response is not a sequence) this
467 property is `True`.
583 If the response is streamed (the response is not a sequence) this
584 property is `True`. In this case streamed means that there is no
585 information about the number of iterations. This is usully `True`
586 if a generator is passed to the response object.
468587 """
469588 try:
470589 len(self.response)
481600 """
482601 if 'Location' in self.headers:
483602 self.headers['Location'] = urlparse.urljoin(
484 get_current_url(environ, host_only=True),
603 get_current_url(environ, root_only=True),
485604 self.headers['Location']
486605 )
487
488 def make_conditional(self, request_or_environ):
489 """
490 Make the response conditional to the request. This method works best
491 if an etag was defined for the response already. The `add_etag`
492 method can be used to do that. If called without etag just the date
493 header is set.
494
495 This does nothing if the request method in the request or enviorn is
496 anything but GET.
497 """
498 if environ['REQUEST_METHOD'] not in ('GET', 'HEAD'):
499 return
500 environ = getattr(request_or_environ, 'environ', request_or_environ)
501 self.headers['Date'] = http_date()
502 if 'etag' in self.headers:
503 if_none_match = environ.get('HTTP_IF_NONE_MATCH')
504 last_modified = self.headers.get('last-modified')
505 if_modified_since = environ.get('HTTP_IF_MODIFIED_SINCE')
506 # we only set the status code because the request object removes
507 # contents for 304 responses automatically on `__call__`
508 if if_none_match and if_none_match == self.headers['etag'] or \
509 if_modified_since == last_modified:
510 self.status_code = 304
511
512 def add_etag(self, overwrite=False):
513 """Add an etag for the current response if there is none yet."""
514 if not overwrite and 'etag' in self.headers:
515 return
516 etag = md5(self.response_body).hexdigest()
517 self.headers['ETag'] = etag
518606
519607 def close(self):
520608 """Close the wrapped response if possible."""
521609 if hasattr(self.response, 'close'):
522610 self.response.close()
611
612 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 """
617 BaseResponse.data.__get__(self)
523618
524619 def __call__(self, environ, start_response):
525620 """Process this response as WSGI application."""
535630 return resp
536631
537632
538 class BaseReporterStream(object):
539 """
540 This class can be used to wrap `wsgi.input` in order to get informed about
541 changes of the stream.
542
543 Usage::
544
545 from random import randrange
546
547 class ReporterStream(BaseReporterStream):
548
549 def __init__(self, environ):
550 super(ReporterStream, self).__init__(environ, 1024 * 16)
551 self.transport_id = randrange(0, 100000)
552
553 def processed(self):
554 s = self.environ['my.session.service']
555 s.store['upload/%s' % self.transport_id] = (self.pos, self.length)
556 s.flush()
557
558
559 And before accessing `request.form` or similar attributes add the stream:
560
561 stream = ReporterStream(environ)
562 environ['wsgi.input'] = stream
563 """
564
565 def __init__(self, environ, threshold):
566 self.threshold = threshold
567 self.length = int(environ.get('CONTENT_LENGTH') or 0)
568 self.pos = 0
569 self.environ = environ
570 self._stream = environ['wsgi.input']
571
572 def processed(self):
573 """Called after pos has changed for threshold or a line was read."""
574
575 def read(self, size=None):
576 length = self.length
577 threshold = self.threshold
578 buffer = []
579
580 if size is None:
581 while self.pos < length:
582 step = min(threshold, length - self.pos)
583 data = self._stream.read(step)
584 self.pos += step
585 self.processed()
586 buffer.append(data)
633 class AcceptMixin(object):
634 """
635 A mixin for classes with an `environ` attribute to get and all the HTTP
636 accept headers as `Accept` objects. This can be mixed in request objects
637 or any other object that has a WSGI environ available as `environ`.
638 """
639
640 def accept_mimetypes(self):
641 """List of mimetypes this client supports."""
642 return parse_accept_header(self.environ.get('HTTP_ACCEPT'))
643 accept_mimetypes = cached_property(accept_mimetypes)
644
645 def accept_charsets(self):
646 """List of charsets this client supports."""
647 return parse_accept_header(self.environ.get('HTTP_ACCEPT_CHARSET'))
648 accept_charsets = cached_property(accept_charsets)
649
650 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 """
656 return parse_accept_header(self.environ.get('HTTP_ACCEPT_ENCODING'))
657 accept_encodings = cached_property(accept_encodings)
658
659 def accept_languages(self):
660 """List of languages this client accepts."""
661 return parse_accept_header(self.environ.get('HTTP_ACCEPT_LANGUAGE'))
662 accept_languages = cached_property(accept_languages)
663
664
665 class ETagRequestMixin(object):
666 """
667 Add entity tag and cache descriptors to a request object or object with
668 an WSGI environment available as `environ`. This not only provides
669 access to etags but also to the cache control header.
670 """
671
672 def cache_control(self):
673 """A `CacheControl` object for the incoming cache control headers."""
674 cache_control = self.environ.get('HTTP_CACHE_CONTROL')
675 return parse_cache_control_header(cache_control)
676 cache_control = cached_property(cache_control)
677
678 def if_match(self):
679 """An object containing all the etags in the `If-Match` header."""
680 return parse_etags(self.environ.get('HTTP_IF_MATCH'))
681 if_match = cached_property(if_match)
682
683 def if_none_match(self):
684 """An object containing all the etags in the `If-None-Match` header."""
685 return parse_etags(self.environ.get('HTTP_IF_NONE_MATCH'))
686 if_none_match = cached_property(if_none_match)
687
688 def if_modified_since(self):
689 """The parsed `If-Modified-Since` header as datetime object."""
690 return parse_date(self.environ.get('HTTP_IF_MODIFIED_SINCE'))
691 if_modified_since = cached_property(if_modified_since)
692
693 def if_unmodified_since(self):
694 """The parsed `If-Unmodified-Since` header as datetime object."""
695 return parse_date(self.environ.get('HTTP_IF_UNMODIFIED_SINCE'))
696 if_unmodified_since = cached_property(if_unmodified_since)
697
698
699 class UserAgentMixin(object):
700 """
701 Adds a `user_agent` attribute to the request object which contains the
702 parsed user agent of the browser that triggered the request as `UserAgent`
703 object.
704 """
705
706 # this class actually belongs to a different module. For more details
707 # have a look at `werkzeug.useragents`. On the bottom of that module is
708 # a small comment that explains it.
709 __module__ = 'werkzeug.useragents'
710
711 def user_agent(self):
712 """The current user agent."""
713 from werkzeug.useragents import UserAgent
714 return UserAgent(self.environ)
715 user_agent = cached_property(user_agent)
716
717
718 class ETagResponseMixin(object):
719 """
720 Adds extra functionality to a response object for etag and cache
721 handling. This mixin requires an object with at least a `headers`
722 object that implements a dict like interface similar to `Headers`.
723 """
724
725 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
729 request/response chain.
730 """
731 def on_update(cache_control):
732 if not cache_control and 'cache-control' in self.headers:
733 del self.headers['cache-control']
734 elif cache_control:
735 self.headers['Cache-Control'] = cache_control.to_header()
736 return parse_cache_control_header(self.headers.get('cache-control'),
737 on_update)
738 cache_control = property(cache_control, doc=cache_control.__doc__)
739
740 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`
744 method can be used to do that. If called without etag just the date
745 header is set.
746
747 This does nothing if the request method in the request or enviorn is
748 anything but GET or HEAD.
749
750 It does not remove the body of the response because that's something
751 the `__call__` function does for us automatically.
752
753 Returns self so that you can do ``return resp.make_conditional(req)``
754 but modifies the object in-place.
755 """
756 environ = getattr(request_or_environ, 'environ', request_or_environ)
757 if environ['REQUEST_METHOD'] not in ('GET', 'HEAD'):
758 return
759 self.headers['Date'] = http_date()
760 if 'content-length' in self.headers:
761 self.headers['Content-Length'] = len(self.data)
762 if not is_resource_modified(environ, self.headers.get('etag'), None,
763 self.headers.get('last-modified')):
764 self.status_code = 304
765 return self
766
767 def add_etag(self, overwrite=False, weak=False):
768 """Add an etag for the current response if there is none yet."""
769 if overwrite or 'etag' not in self.headers:
770 self.set_etag(generate_etag(self.data), weak)
771
772 def set_etag(self, etag, weak=False):
773 """Set the etag, and override the old one if there was one."""
774 self.headers['ETag'] = quote_etag(etag, weak)
775
776 def get_etag(self):
777 """
778 Return a tuple in the form ``(etag, is_weak)``. If there is no
779 ETag the return value is ``(None, None)``.
780 """
781 return unquote_etag(self.headers.get('ETag'))
782
783 def freeze(self, no_etag=False):
784 """
785 Call this method if you want to make your response object ready for
786 pickeling. This buffers the generator if there is one. This also
787 sets the etag unless `no_etag` is set to `True`.
788 """
789 if not no_etag:
790 self.add_etag()
791 super(ETagResponseMixin, self).freeze()
792
793
794 class ResponseStream(object):
795 """
796 A file descriptor like object used by the `ResponseStreamMixin` to
797 represent the body of the stream. It directly pushes into the response
798 iterable of the response object.
799 """
800
801 closed = False
802 mode = 'wb'
803
804 def __init__(self, response):
805 self.response = response
806
807 def write(self, value):
808 if self.closed:
809 raise ValueError('I/O operation on closed file')
810 buf = self.response.response
811 if not isinstance(buf, list):
812 self.response.response = buf = list(self.response.response)
813 buf.append(value)
814
815 def writelines(self, seq):
816 for item in seq:
817 self.write(item)
818
819 def close(self):
820 self.closed = True
821
822 def flush(self):
823 if self.closed:
824 raise ValueError('I/O operation on closed file')
825
826 def isatty(self):
827 if self.closed:
828 raise ValueError('I/O operation on closed file')
829 return False
830
831 def encoding(self):
832 return self.response.charset
833 encoding = property(encoding)
834
835
836 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.
841 """
842
843 def stream(self):
844 """The response iterable as write-only stream."""
845 return ResponseStream(self)
846 stream = cached_property(stream)
847
848
849 class CommonResponseDescriptorsMixin(object):
850 """
851 A mixin for `BaseResponse` subclasses. Response objects that mix this
852 class in will automatically get descriptors for a couple of HTTP headers
853 with automatic type conversion.
854 """
855
856 def _get_mimetype(self):
857 """The mimetype (content type without charset etc.)"""
858 ct = self.headers.get('Content-Type')
859 if ct:
860 return ct.split(';')[0].strip()
861
862 def _set_mimetype(self, value):
863 self.headers['Content-Type'] = get_content_type(value, self.charset)
864
865 mimetype = property(_get_mimetype, _set_mimetype, doc='''
866 The mimetype (content type without charset etc.)''')
867 location = header_property('Location', doc='''
868 The Location response-header field is used to redirect the recipient
869 to a location other than the Request-URI for completion of the request
870 or identification of a new resource.''')
871 age = header_property('Age', None, parse_date, http_date, doc='''
872 The Age response-header field conveys the sender's estimate of the
873 amount of time since the response (or its revalidation) was
874 generated at the origin server.
875
876 Age values are non-negative decimal integers, representing time in
877 seconds.''')
878 content_type = header_property('Content-Type', doc='''
879 The Content-Type entity-header field indicates the media type of the
880 entity-body sent to the recipient or, in the case of the HEAD method,
881 the media type that would have been sent had the request been a GET.
882 ''')
883 content_length = header_property('Content-Length', None, int, str, doc='''
884 The Content-Length entity-header field indicates the size of the
885 entity-body, in decimal number of OCTETs, sent to the recipient or,
886 in the case of the HEAD method, the size of the entity-body that would
887 have been sent had the request been a GET.''')
888 content_location = header_property('Content-Location', doc='''
889 The Content-Location entity-header field MAY be used to supply the
890 resource location for the entity enclosed in the message when that
891 entity is accessible from a location separate from the requested
892 resource's URI.''')
893 content_encoding = header_property('Content-Encoding', doc='''
894 The Content-Encoding entity-header field is used as a modifier to the
895 media-type. When present, its value indicates what additional content
896 codings have been applied to the entity-body, and thus what decoding
897 mechanisms must be applied in order to obtain the media-type
898 referenced by the Content-Type header field.''')
899 content_md5 = header_property('Content-MD5', doc='''
900 The Content-MD5 entity-header field, as defined in RFC 1864, is an
901 MD5 digest of the entity-body for the purpose of providing an
902 end-to-end message integrity check (MIC) of the entity-body. (Note:
903 a MIC is good for detecting accidental modification of the
904 entity-body in transit, but is not proof against malicious attacks.)
905 ''')
906 date = header_property('Date', None, parse_date, http_date, doc='''
907 The Date general-header field represents the date and time at which
908 the message was originated, having the same semantics as orig-date
909 in RFC 822.''')
910 expires = header_property('Expires', None, parse_date, http_date, doc='''
911 The Expires entity-header field gives the date/time after which the
912 response is considered stale. A stale cache entry may not normally be
913 returned by a cache.''')
914 last_modified = header_property('Last-Modified', None, parse_date,
915 http_date, doc='''
916 The Last-Modified entity-header field indicates the date and time at
917 which the origin server believes the variant was last modified.''')
918
919 def _get_retry_after(self):
920 value = self.headers.get('retry-after')
921 if value is None:
922 return
923 elif value.isdigit():
924 return datetime.utcnow() + timedelta(seconds=int(value))
925 return parse_date(value)
926 def _set_retry_after(self, value):
927 if value is None:
928 if 'retry-after' in self.headers:
929 del self.headers['retry-after']
930 return
931 elif isinstance(value, datetime):
932 value = http_date(value)
587933 else:
588 read = 0
589 while read < size:
590 step = min(threshold, length - self.pos)
591 step = min(step, size)
592 data = self._stream.read(step)
593 self.pos += step
594 read += step
595 self.processed()
596 buffer.append(data)
597
598 return ''.join(buffer)
599
600 def readline(self, *args):
601 line = self._stream.readline(*args)
602 self.pos += len(line)
603 self.processed()
604 return line
605
606 def readlines(self, hint=None):
607 result = []
608 while self.pos < self.length:
609 result.append(self.readline())
610 return result
934 value = str(value)
935 self.headers['Retry-After'] = value
936
937 retry_after = property(_get_retry_after, _set_retry_after, doc='''
938 The Retry-After response-header field can be used with a 503 (Service
939 Unavailable) response to indicate how long the service is expected
940 to be unavailable to the requesting client.
941
942 Time in seconds until expiration or date.''')
943
944 def _set_property(name, doc=None):
945 def fget(self):
946 def on_update(header_set):
947 if not header_set and name in self.headers:
948 del self.headers[name]
949 elif header_set:
950 self.headers[name] = header_set.to_header()
951 return parse_set_header(self.headers.get(name), on_update)
952 return property(fget, doc=doc)
953
954 vary = _set_property('Vary', doc='''
955 The Vary field value indicates the set of request-header fields that
956 fully determines, while the response is fresh, whether a cache is
957 permitted to use the response to reply to a subsequent request
958 without revalidation.''')
959 content_language = _set_property('Content-Language', doc='''
960 The Content-Language entity-header field describes the natural
961 language(s) of the intended audience for the enclosed entity. Note
962 that this might not be equivalent to all the languages used within
963 the entity-body.''')
964 allow = _set_property('Allow', doc='''
965 The Allow entity-header field lists the set of methods supported
966 by the resource identified by the Request-URI. The purpose of this
967 field is strictly to inform the recipient of valid methods
968 associated with the resource. An Allow header field MUST be
969 present in a 405 (Method Not Allowed) response.''')
970
971 del _set_property, _get_mimetype, _set_mimetype, _get_retry_after, \
972 _set_retry_after
973
974
975 class Request(BaseRequest, AcceptMixin, ETagRequestMixin,
976 UserAgentMixin):
977 """
978 Full featured request object implementing the following mixins:
979
980 - `AcceptMixin` for accept header parsing
981 - `ETagRequestMixin` for etag and cache control handling
982 - `UserAgentMixin` for user agent introspection
983 """
984
985
986 class Response(BaseResponse, ETagResponseMixin, ResponseStreamMixin,
987 CommonResponseDescriptorsMixin):
988 """
989 Full featured response object implementing the following mixins:
990
991 - `ETagResponseMixin` for etag and cache control handling
992 - `ResponseStreamMixin` to add support for the `stream` property
993 - `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)