Werkzeug

wsgi utility collection


root/werkzeug/contrib/kickstart.py

Revision 970:830139123984, 10.9 kB (checked in by mitsuhiko, 7 months ago)

It's 2010, who would have thought.

Line 
1# -*- coding: utf-8 -*-
2"""
3    werkzeug.contrib.kickstart
4    ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6    This module provides some simple shortcuts to make using Werkzeug simpler
7    for small scripts.
8
9    These improvements include predefined `Request` and `Response` objects as
10    well as a predefined `Application` object which can be customized in child
11    classes, of course.  The `Request` and `Reponse` objects handle URL
12    generation as well as sessions via `werkzeug.contrib.sessions` and are
13    purely optional.
14
15    There is also some integration of template engines.  The template loaders
16    are, of course, not neccessary to use the template engines in Werkzeug,
17    but they provide a common interface.  Currently supported template engines
18    include Werkzeug's minitmpl and Genshi_.  Support for other engines can be
19    added in a trivial way.  These loaders provide a template interface
20    similar to the one used by Django_.
21
22    .. _Genshi: http://genshi.edgewall.org/
23    .. _Django: http://www.djangoproject.com/
24
25    :copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
26    :license: BSD, see LICENSE for more details.
27"""
28from os import path
29from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
30from werkzeug.templates import Template
31from werkzeug.exceptions import HTTPException
32from werkzeug.routing import RequestRedirect
33
34__all__ = ['Request', 'Response', 'TemplateNotFound', 'TemplateLoader',
35           'GenshiTemplateLoader', 'Application']
36
37
38class Request(RequestBase):
39    """A handy subclass of the base request that adds a URL builder.
40    It when supplied a session store, it is also able to handle sessions.
41    """
42
43    def __init__(self, environ, url_map,
44            session_store=None, cookie_name=None):
45        # call the parent for initialization
46        RequestBase.__init__(self, environ)
47        # create an adapter
48        self.url_adapter = url_map.bind_to_environ(environ)
49        # create all stuff for sessions
50        self.session_store = session_store
51        self.cookie_name = cookie_name
52
53        if session_store is not None and cookie_name is not None:
54            if cookie_name in self.cookies:
55                # get the session out of the storage
56                self.session = session_store.get(self.cookies[cookie_name])
57            else:
58                # create a new session
59                self.session = session_store.new()
60
61    def url_for(self, callback, **values):
62        return self.url_adapter.build(callback, values)
63
64
65class Response(ResponseBase):
66    """
67    A subclass of base response which sets the default mimetype to text/html.
68    It the `Request` that came in is using Werkzeug sessions, this class
69    takes care of saving that session.
70    """
71    default_mimetype = 'text/html'
72
73    def __call__(self, environ, start_response):
74        # get the request object
75        request = environ['werkzeug.request']
76
77        if request.session_store is not None:
78            # save the session if neccessary
79            request.session_store.save_if_modified(request.session)
80
81            # set the cookie for the browser if it is not there:
82            if request.cookie_name not in request.cookies:
83                self.set_cookie(request.cookie_name, request.session.sid)
84
85        # go on with normal response business
86        return ResponseBase.__call__(self, environ, start_response)
87
88
89class Processor(object):
90    """A request and response processor - it is what Django calls a
91    middleware, but Werkzeug also includes straight-foward support for real
92    WSGI middlewares, so another name was chosen.
93
94    The code of this processor is derived from the example in the Werkzeug
95    trac, called `Request and Response Processor
96    <http://dev.pocoo.org/projects/werkzeug/wiki/RequestResponseProcessor>`_
97    """
98
99    def process_request(self, request):
100        return request
101
102    def process_response(self, request, response):
103        return response
104
105    def process_view(self, request, view_func, view_args, view_kwargs):
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
119class Application(object):
120    """A generic WSGI application which can be used to start with Werkzeug in
121    an easy, straightforward way.
122    """
123
124    def __init__(self, name, url_map, session=False, processors=None):
125        # save the name and the URL-map, as it'll be needed later on
126        self.name = name
127        self.url_map = url_map
128        # save the list of processors if supplied
129        self.processors = processors or []
130        # create an instance of the storage
131        if session:
132            self.store = session
133        else:
134            self.store = None
135
136    def __call__(self, environ, start_response):
137        # create a request - with or without session support
138        if self.store is not None:
139            request = Request(environ, self.url_map,
140                session_store=self.store, cookie_name='%s_sid' % self.name)
141        else:
142            request = Request(environ, self.url_map)
143
144        # apply the request processors
145        for processor in self.processors:
146            request = processor.process_request(request)
147
148        try:
149            # find the callback to which the URL is mapped
150            callback, args = request.url_adapter.match(request.path)
151        except (HTTPException, RequestRedirect), e:
152            response = e
153        else:
154            # check all view processors
155            for processor in self.processors:
156                action = processor.process_view(request, callback, (), args)
157                if action is not None:
158                    # it is overriding the default behaviour, this is
159                    # short-circuiting the processing, so it returns here
160                    return action(environ, start_response)
161
162            try:
163                response = callback(request, **args)
164            except Exception, exception:
165                # the callback raised some exception, need to process that
166                for processor in reversed(self.processors):
167                    # filter it through the exception processor
168                    action = processor.process_exception(request, exception)
169                    if action is not None:
170                        # the exception processor returned some action
171                        return action(environ, start_response)
172                # still not handled by a exception processor, so re-raise
173                raise
174
175        # apply the response processors
176        for processor in reversed(self.processors):
177            response = processor.process_response(request, response)
178
179        # return the completely processed response
180        return response(environ, start_response)
181
182
183    def config_session(self, store, expiration='session'):
184        """
185        Configures the setting for cookies. You can also disable cookies by
186        setting store to None.
187        """
188        self.store = store
189        # expiration=session is the default anyway
190        # TODO: add settings to define the expiration date, the domain, the
191        # path any maybe the secure parameter.
192
193
194class TemplateNotFound(IOError, LookupError):
195    """
196    A template was not found by the template loader.
197    """
198
199    def __init__(self, name):
200        IOError.__init__(self, name)
201        self.name = name
202
203
204class TemplateLoader(object):
205    """
206    A simple loader interface for the werkzeug minitmpl
207    template language.
208    """
209
210    def __init__(self, search_path, encoding='utf-8'):
211        self.search_path = path.abspath(search_path)
212        self.encoding = encoding
213
214    def get_template(self, name):
215        """Get a template from a given name."""
216        filename = path.join(self.search_path, *[p for p in name.split('/')
217                                                 if p and p[0] != '.'])
218        if not path.exists(filename):
219            raise TemplateNotFound(name)
220        return Template.from_file(filename, self.encoding)
221
222    def render_to_response(self, *args, **kwargs):
223        """Load and render a template into a response object."""
224        return Response(self.render_to_string(*args, **kwargs))
225
226    def render_to_string(self, *args, **kwargs):
227        """Load and render a template into a unicode string."""
228        try:
229            template_name, args = args[0], args[1:]
230        except IndexError:
231            raise TypeError('name of template required')
232        return self.get_template(template_name).render(*args, **kwargs)
233
234
235class GenshiTemplateLoader(TemplateLoader):
236    """A unified interface for loading Genshi templates. Actually a quite thin
237    wrapper for Genshi's TemplateLoader.
238
239    It sets some defaults that differ from the Genshi loader, most notably
240    auto_reload is active. All imporant options can be passed through to
241    Genshi.
242    The default output type is 'html', but can be adjusted easily by changing
243    the `output_type` attribute.
244    """
245    def __init__(self, search_path, encoding='utf-8', **kwargs):
246        TemplateLoader.__init__(self, search_path, encoding)
247        # import Genshi here, because we don't want a general Genshi
248        # dependency, only a local one
249        from genshi.template import TemplateLoader as GenshiLoader
250        from genshi.template.loader import TemplateNotFound
251
252        self.not_found_exception = TemplateNotFound
253        # set auto_reload to True per default
254        reload_template = kwargs.pop('auto_reload', True)
255        # get rid of default_encoding as this template loaders overwrites it
256        # with the value of encoding
257        kwargs.pop('default_encoding', None)
258
259        # now, all arguments are clean, pass them on
260        self.loader = GenshiLoader(search_path, default_encoding=encoding,
261                auto_reload=reload_template, **kwargs)
262
263        # the default output is HTML but can be overridden easily
264        self.output_type = 'html'
265        self.encoding = encoding
266
267    def get_template(self, template_name):
268        """Get the template which is at the given name"""
269        try:
270            return self.loader.load(template_name, encoding=self.encoding)
271        except self.not_found_exception, e:
272            # catch the exception raised by Genshi, convert it into a werkzeug
273            # exception (for the sake of consistency)
274            raise TemplateNotFound(template_name)
275
276    def render_to_string(self, template_name, context=None):
277        """Load and render a template into an unicode string"""
278        # create an empty context if no context was specified
279        context = context or {}
280        tmpl = self.get_template(template_name)
281        # render the template into a unicode string (None means unicode)
282        return tmpl. \
283            generate(**context). \
284            render(self.output_type, encoding=None)
Note: See TracBrowser for help on using the browser.