Back to index

moin  1.9.0~rc2
kickstart.py
Go to the documentation of this file.
00001 # -*- coding: utf-8 -*-
00002 """
00003     werkzeug.contrib.kickstart
00004     ~~~~~~~~~~~~~~~~~~~~~~~~~~
00005 
00006     This module provides some simple shortcuts to make using Werkzeug simpler
00007     for small scripts.
00008 
00009     These improvements include predefined `Request` and `Response` objects as
00010     well as a predefined `Application` object which can be customized in child
00011     classes, of course.  The `Request` and `Reponse` objects handle URL
00012     generation as well as sessions via `werkzeug.contrib.sessions` and are
00013     purely optional.
00014 
00015     There is also some integration of template engines.  The template loaders
00016     are, of course, not neccessary to use the template engines in Werkzeug,
00017     but they provide a common interface.  Currently supported template engines
00018     include Werkzeug's minitmpl and Genshi_.  Support for other engines can be
00019     added in a trivial way.  These loaders provide a template interface
00020     similar to the one used by Django_.
00021 
00022     .. _Genshi: http://genshi.edgewall.org/
00023     .. _Django: http://www.djangoproject.com/
00024 
00025     :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details.
00026     :license: BSD, see LICENSE for more details.
00027 """
00028 from os import path
00029 from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
00030 from werkzeug.templates import Template
00031 from werkzeug.exceptions import HTTPException
00032 from werkzeug.routing import RequestRedirect
00033 
00034 __all__ = ['Request', 'Response', 'TemplateNotFound', 'TemplateLoader',
00035            'GenshiTemplateLoader', 'Application']
00036 
00037 
00038 class Request(RequestBase):
00039     """A handy subclass of the base request that adds a URL builder.
00040     It when supplied a session store, it is also able to handle sessions.
00041     """
00042 
00043     def __init__(self, environ, url_map,
00044             session_store=None, cookie_name=None):
00045         # call the parent for initialization
00046         RequestBase.__init__(self, environ)
00047         # create an adapter
00048         self.url_adapter = url_map.bind_to_environ(environ)
00049         # create all stuff for sessions
00050         self.session_store = session_store
00051         self.cookie_name = cookie_name
00052 
00053         if session_store is not None and cookie_name is not None:
00054             if cookie_name in self.cookies:
00055                 # get the session out of the storage
00056                 self.session = session_store.get(self.cookies[cookie_name])
00057             else:
00058                 # create a new session
00059                 self.session = session_store.new()
00060 
00061     def url_for(self, callback, **values):
00062         return self.url_adapter.build(callback, values)
00063 
00064 
00065 class Response(ResponseBase):
00066     """
00067     A subclass of base response which sets the default mimetype to text/html.
00068     It the `Request` that came in is using Werkzeug sessions, this class
00069     takes care of saving that session.
00070     """
00071     default_mimetype = 'text/html'
00072 
00073     def __call__(self, environ, start_response):
00074         # get the request object
00075         request = environ['werkzeug.request']
00076 
00077         if request.session_store is not None:
00078             # save the session if neccessary
00079             request.session_store.save_if_modified(request.session)
00080 
00081             # set the cookie for the browser if it is not there:
00082             if request.cookie_name not in request.cookies:
00083                 self.set_cookie(request.cookie_name, request.session.sid)
00084 
00085         # go on with normal response business
00086         return ResponseBase.__call__(self, environ, start_response)
00087 
00088 
00089 class Processor(object):
00090     """A request and response processor - it is what Django calls a
00091     middleware, but Werkzeug also includes straight-foward support for real
00092     WSGI middlewares, so another name was chosen.
00093 
00094     The code of this processor is derived from the example in the Werkzeug
00095     trac, called `Request and Response Processor
00096     <http://dev.pocoo.org/projects/werkzeug/wiki/RequestResponseProcessor>`_
00097     """
00098 
00099     def process_request(self, request):
00100         return request
00101 
00102     def process_response(self, request, response):
00103         return response
00104 
00105     def process_view(self, request, view_func, view_args, view_kwargs):
00106         """process_view() is called just before the Application calls the
00107         function specified by view_func.
00108 
00109         If this returns None, the Application processes the next Processor,
00110         and if it returns something else (like a Response instance), that
00111         will be returned without any further processing.
00112         """
00113         return None
00114 
00115     def process_exception(self, request, exception):
00116         return None
00117 
00118 
00119 class Application(object):
00120     """A generic WSGI application which can be used to start with Werkzeug in
00121     an easy, straightforward way.
00122     """
00123 
00124     def __init__(self, name, url_map, session=False, processors=None):
00125         # save the name and the URL-map, as it'll be needed later on
00126         self.name = name
00127         self.url_map = url_map
00128         # save the list of processors if supplied
00129         self.processors = processors or []
00130         # create an instance of the storage
00131         if session:
00132             self.store = session
00133         else:
00134             self.store = None
00135 
00136     def __call__(self, environ, start_response):
00137         # create a request - with or without session support
00138         if self.store is not None:
00139             request = Request(environ, self.url_map,
00140                 session_store=self.store, cookie_name='%s_sid' % self.name)
00141         else:
00142             request = Request(environ, self.url_map)
00143 
00144         # apply the request processors
00145         for processor in self.processors:
00146             request = processor.process_request(request)
00147 
00148         try:
00149             # find the callback to which the URL is mapped
00150             callback, args = request.url_adapter.match(request.path)
00151         except (HTTPException, RequestRedirect), e:
00152             response = e
00153         else:
00154             # check all view processors
00155             for processor in self.processors:
00156                 action = processor.process_view(request, callback, (), args)
00157                 if action is not None:
00158                     # it is overriding the default behaviour, this is
00159                     # short-circuiting the processing, so it returns here
00160                     return action(environ, start_response)
00161 
00162             try:
00163                 response = callback(request, **args)
00164             except Exception, exception:
00165                 # the callback raised some exception, need to process that
00166                 for processor in reversed(self.processors):
00167                     # filter it through the exception processor
00168                     action = processor.process_exception(request, exception)
00169                     if action is not None:
00170                         # the exception processor returned some action
00171                         return action(environ, start_response)
00172                 # still not handled by a exception processor, so re-raise
00173                 raise
00174 
00175         # apply the response processors
00176         for processor in reversed(self.processors):
00177             response = processor.process_response(request, response)
00178 
00179         # return the completely processed response
00180         return response(environ, start_response)
00181 
00182 
00183     def config_session(self, store, expiration='session'):
00184         """
00185         Configures the setting for cookies. You can also disable cookies by
00186         setting store to None.
00187         """
00188         self.store = store
00189         # expiration=session is the default anyway
00190         # TODO: add settings to define the expiration date, the domain, the
00191         # path any maybe the secure parameter.
00192 
00193 
00194 class TemplateNotFound(IOError, LookupError):
00195     """
00196     A template was not found by the template loader.
00197     """
00198 
00199     def __init__(self, name):
00200         IOError.__init__(self, name)
00201         self.name = name
00202 
00203 
00204 class TemplateLoader(object):
00205     """
00206     A simple loader interface for the werkzeug minitmpl
00207     template language.
00208     """
00209 
00210     def __init__(self, search_path, encoding='utf-8'):
00211         self.search_path = path.abspath(search_path)
00212         self.encoding = encoding
00213 
00214     def get_template(self, name):
00215         """Get a template from a given name."""
00216         filename = path.join(self.search_path, *[p for p in name.split('/')
00217                                                  if p and p[0] != '.'])
00218         if not path.exists(filename):
00219             raise TemplateNotFound(name)
00220         return Template.from_file(filename, self.encoding)
00221 
00222     def render_to_response(self, *args, **kwargs):
00223         """Load and render a template into a response object."""
00224         return Response(self.render_to_string(*args, **kwargs))
00225 
00226     def render_to_string(self, *args, **kwargs):
00227         """Load and render a template into a unicode string."""
00228         try:
00229             template_name, args = args[0], args[1:]
00230         except IndexError:
00231             raise TypeError('name of template required')
00232         return self.get_template(template_name).render(*args, **kwargs)
00233 
00234 
00235 class GenshiTemplateLoader(TemplateLoader):
00236     """A unified interface for loading Genshi templates. Actually a quite thin
00237     wrapper for Genshi's TemplateLoader.
00238 
00239     It sets some defaults that differ from the Genshi loader, most notably
00240     auto_reload is active. All imporant options can be passed through to
00241     Genshi.
00242     The default output type is 'html', but can be adjusted easily by changing
00243     the `output_type` attribute.
00244     """
00245     def __init__(self, search_path, encoding='utf-8', **kwargs):
00246         TemplateLoader.__init__(self, search_path, encoding)
00247         # import Genshi here, because we don't want a general Genshi
00248         # dependency, only a local one
00249         from genshi.template import TemplateLoader as GenshiLoader
00250         from genshi.template.loader import TemplateNotFound
00251 
00252         self.not_found_exception = TemplateNotFound
00253         # set auto_reload to True per default
00254         reload_template = kwargs.pop('auto_reload', True)
00255         # get rid of default_encoding as this template loaders overwrites it
00256         # with the value of encoding
00257         kwargs.pop('default_encoding', None)
00258 
00259         # now, all arguments are clean, pass them on
00260         self.loader = GenshiLoader(search_path, default_encoding=encoding,
00261                 auto_reload=reload_template, **kwargs)
00262 
00263         # the default output is HTML but can be overridden easily
00264         self.output_type = 'html'
00265         self.encoding = encoding
00266 
00267     def get_template(self, template_name):
00268         """Get the template which is at the given name"""
00269         try:
00270             return self.loader.load(template_name, encoding=self.encoding)
00271         except self.not_found_exception, e:
00272             # catch the exception raised by Genshi, convert it into a werkzeug
00273             # exception (for the sake of consistency)
00274             raise TemplateNotFound(template_name)
00275 
00276     def render_to_string(self, template_name, context=None):
00277         """Load and render a template into an unicode string"""
00278         # create an empty context if no context was specified
00279         context = context or {}
00280         tmpl = self.get_template(template_name)
00281         # render the template into a unicode string (None means unicode)
00282         return tmpl. \
00283             generate(**context). \
00284             render(self.output_type, encoding=None)