You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

288 lines
11 KiB

# -*- coding: utf-8 -*-
"""
werkzeug.contrib.kickstart
~~~~~~~~~~~~~~~~~~~~~~~~~~
This module provides some simple shortcuts to make using Werkzeug simpler
for small scripts.
These improvements include predefined `Request` and `Response` objects as
well as a predefined `Application` object which can be customized in child
classes, of course. The `Request` and `Reponse` objects handle URL
generation as well as sessions via `werkzeug.contrib.sessions` and are
purely optional.
There is also some integration of template engines. The template loaders
are, of course, not neccessary to use the template engines in Werkzeug,
but they provide a common interface. Currently supported template engines
include Werkzeug's minitmpl and Genshi_. Support for other engines can be
added in a trivial way. These loaders provide a template interface
similar to the one used by Django_.
.. _Genshi: http://genshi.edgewall.org/
.. _Django: http://www.djangoproject.com/
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from os import path
from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
from werkzeug.templates import Template
from werkzeug.exceptions import HTTPException
from werkzeug.routing import RequestRedirect
__all__ = ['Request', 'Response', 'TemplateNotFound', 'TemplateLoader',
'GenshiTemplateLoader', 'Application']
from warnings import warn
warn(DeprecationWarning('werkzeug.contrib.kickstart is deprecated and '
'will be removed in Werkzeug 1.0'))
class Request(RequestBase):
"""A handy subclass of the base request that adds a URL builder.
It when supplied a session store, it is also able to handle sessions.
"""
def __init__(self, environ, url_map,
session_store=None, cookie_name=None):
# call the parent for initialization
RequestBase.__init__(self, environ)
# create an adapter
self.url_adapter = url_map.bind_to_environ(environ)
# create all stuff for sessions
self.session_store = session_store
self.cookie_name = cookie_name
if session_store is not None and cookie_name is not None:
if cookie_name in self.cookies:
# get the session out of the storage
self.session = session_store.get(self.cookies[cookie_name])
else:
# create a new session
self.session = session_store.new()
def url_for(self, callback, **values):
return self.url_adapter.build(callback, values)
class Response(ResponseBase):
"""
A subclass of base response which sets the default mimetype to text/html.
It the `Request` that came in is using Werkzeug sessions, this class
takes care of saving that session.
"""
default_mimetype = 'text/html'
def __call__(self, environ, start_response):
# get the request object
request = environ['werkzeug.request']
if request.session_store is not None:
# save the session if neccessary
request.session_store.save_if_modified(request.session)
# set the cookie for the browser if it is not there:
if request.cookie_name not in request.cookies:
self.set_cookie(request.cookie_name, request.session.sid)
# go on with normal response business
return ResponseBase.__call__(self, environ, start_response)
class Processor(object):
"""A request and response processor - it is what Django calls a
middleware, but Werkzeug also includes straight-foward support for real
WSGI middlewares, so another name was chosen.
The code of this processor is derived from the example in the Werkzeug
trac, called `Request and Response Processor
<http://dev.pocoo.org/projects/werkzeug/wiki/RequestResponseProcessor>`_
"""
def process_request(self, request):
return request
def process_response(self, request, response):
return response
def process_view(self, request, view_func, view_args, view_kwargs):
"""process_view() is called just before the Application calls the
function specified by view_func.
If this returns None, the Application processes the next Processor,
and if it returns something else (like a Response instance), that
will be returned without any further processing.
"""
return None
def process_exception(self, request, exception):
return None
class Application(object):
"""A generic WSGI application which can be used to start with Werkzeug in
an easy, straightforward way.
"""
def __init__(self, name, url_map, session=False, processors=None):
# save the name and the URL-map, as it'll be needed later on
self.name = name
self.url_map = url_map
# save the list of processors if supplied
self.processors = processors or []
# create an instance of the storage
if session:
self.store = session
else:
self.store = None
def __call__(self, environ, start_response):
# create a request - with or without session support
if self.store is not None:
request = Request(environ, self.url_map,
session_store=self.store, cookie_name='%s_sid' % self.name)
else:
request = Request(environ, self.url_map)
# apply the request processors
for processor in self.processors:
request = processor.process_request(request)
try:
# find the callback to which the URL is mapped
callback, args = request.url_adapter.match(request.path)
except (HTTPException, RequestRedirect), e:
response = e
else:
# check all view processors
for processor in self.processors:
action = processor.process_view(request, callback, (), args)
if action is not None:
# it is overriding the default behaviour, this is
# short-circuiting the processing, so it returns here
return action(environ, start_response)
try:
response = callback(request, **args)
except Exception, exception:
# the callback raised some exception, need to process that
for processor in reversed(self.processors):
# filter it through the exception processor
action = processor.process_exception(request, exception)
if action is not None:
# the exception processor returned some action
return action(environ, start_response)
# still not handled by a exception processor, so re-raise
raise
# apply the response processors
for processor in reversed(self.processors):
response = processor.process_response(request, response)
# return the completely processed response
return response(environ, start_response)
def config_session(self, store, expiration='session'):
"""
Configures the setting for cookies. You can also disable cookies by
setting store to None.
"""
self.store = store
# expiration=session is the default anyway
# TODO: add settings to define the expiration date, the domain, the
# path any maybe the secure parameter.
class TemplateNotFound(IOError, LookupError):
"""
A template was not found by the template loader.
"""
def __init__(self, name):
IOError.__init__(self, name)
self.name = name
class TemplateLoader(object):
"""
A simple loader interface for the werkzeug minitmpl
template language.
"""
def __init__(self, search_path, encoding='utf-8'):
self.search_path = path.abspath(search_path)
self.encoding = encoding
def get_template(self, name):
"""Get a template from a given name."""
filename = path.join(self.search_path, *[p for p in name.split('/')
if p and p[0] != '.'])
if not path.exists(filename):
raise TemplateNotFound(name)
return Template.from_file(filename, self.encoding)
def render_to_response(self, *args, **kwargs):
"""Load and render a template into a response object."""
return Response(self.render_to_string(*args, **kwargs))
def render_to_string(self, *args, **kwargs):
"""Load and render a template into a unicode string."""
try:
template_name, args = args[0], args[1:]
except IndexError:
raise TypeError('name of template required')
return self.get_template(template_name).render(*args, **kwargs)
class GenshiTemplateLoader(TemplateLoader):
"""A unified interface for loading Genshi templates. Actually a quite thin
wrapper for Genshi's TemplateLoader.
It sets some defaults that differ from the Genshi loader, most notably
auto_reload is active. All imporant options can be passed through to
Genshi.
The default output type is 'html', but can be adjusted easily by changing
the `output_type` attribute.
"""
def __init__(self, search_path, encoding='utf-8', **kwargs):
TemplateLoader.__init__(self, search_path, encoding)
# import Genshi here, because we don't want a general Genshi
# dependency, only a local one
from genshi.template import TemplateLoader as GenshiLoader
from genshi.template.loader import TemplateNotFound
self.not_found_exception = TemplateNotFound
# set auto_reload to True per default
reload_template = kwargs.pop('auto_reload', True)
# get rid of default_encoding as this template loaders overwrites it
# with the value of encoding
kwargs.pop('default_encoding', None)
# now, all arguments are clean, pass them on
self.loader = GenshiLoader(search_path, default_encoding=encoding,
auto_reload=reload_template, **kwargs)
# the default output is HTML but can be overridden easily
self.output_type = 'html'
self.encoding = encoding
def get_template(self, template_name):
"""Get the template which is at the given name"""
try:
return self.loader.load(template_name, encoding=self.encoding)
except self.not_found_exception, e:
# catch the exception raised by Genshi, convert it into a werkzeug
# exception (for the sake of consistency)
raise TemplateNotFound(template_name)
def render_to_string(self, template_name, context=None):
"""Load and render a template into an unicode string"""
# create an empty context if no context was specified
context = context or {}
tmpl = self.get_template(template_name)
# render the template into a unicode string (None means unicode)
return tmpl. \
generate(**context). \
render(self.output_type, encoding=None)