"""
WSGI Application with pvl.web.urls-mapped Handlers building pvl.web.html.
"""
import werkzeug
from werkzeug.wrappers import Request, Response
from werkzeug.exceptions import HTTPException
import pvl.web.response
import logging; log = logging.getLogger('pvl.web.application')
class Application (object) :
"""
Main wsgi entry point state.
"""
URLS = None
# TODO: charset
def __init__ (self, urls=None, layout=None) :
"""
urls - werkzeug.routing.Map -> Handler
layout - template with {TITLE} and {HEAD} and {BODY}
"""
if not urls :
urls = self.URLS
if not urls :
raise ValueError("No URLS/urls=... given")
self.layout = layout
self.urls = urls
def respond (self, request) :
"""
Lookup Request -> web.Handler, params
"""
# bind to request
urls = self.urls.bind_to_environ(request)
# lookup
handler, params = urls.match()
# handler instance
handler = handler(self, request, urls, params)
try :
# apply
return handler.respond()
finally :
handler.cleanup()
@Request.application
def __call__ (self, request) :
"""
WSGI entry point, werkzeug Request -> Response
"""
try :
return self.respond(request)
except HTTPException as ex :
return ex
from pvl.invoke import merge # XXX
from pvl.web.html import tags as html
class Handler (object) :
"""
Per-Request controller/view, containing the request context and generating the response.
"""
DOCTYPE = 'html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"'
HTML_XMLNS = 'http://www.w3.org/1999/xhtml'
HTML_LANG = None
TITLE = None
STYLE = None
SCRIPT = None
CSS = (
#"/static/...css",
)
JS = (
#"/static/....js"
)
STATUS = 200
def __init__ (self, app, request, urls, params) :
"""
app - wsgi.Application
request - werkzeug.Request
urls - werkzeug.routing.Map.bind_to_environ()
"""
self.app = app
self.request = request
self.urls = urls
self.params = params
self.init()
def init (self) :
"""
Initialize on request.
"""
def url (self, handler=None, **params) :
"""
Return an URL for given endpoint, with parameters,
"""
if not handler :
# XXX: just generate a plain-relative '?foo=...' url instead?
handler = self.__class__
params = merge(self.params, params)
return self.urls.build(handler, params)
def status (self) :
"""
Return HTTP status code for response.
"""
return self.STATUS
def title (self) :
"""
Render site/page title as text.
"""
return self.TITLE
def render (self) :
"""
Render page content (as <body>...</body>).
"""
raise NotImplementedError()
def render_html (self, body=None, extrahead=None) :
"""
Render page layout (as <html>).
"""
title = self.title()
head = html(
(
html.link(rel='Stylesheet', type="text/css", href=src) for src in self.CSS
),
(
html.script(src=src, type='text/javascript', _selfclosing=False) for src in self.JS
),
html.style(type='text/css')(self.STYLE) if self.STYLE else None,
html.script(type='text/javascript')(self.SCRIPT) if self.SCRIPT else None,
extrahead,
)
if body is None :
body = html(self.render())
if not title :
raise Exception("%s: no page title!" % (self, ))
if self.app.layout :
return self.app.layout.format(
TITLE = unicode(title),
HEAD = unicode(head),
BODY = unicode(body),
)
else :
return html.document(html.html(
html.head(
html.title(title),
head,
),
html.body(
html.h1(title),
body,
)
), doctype=self.DOCTYPE, html_xmlns=self.HTML_XMLNS, html_lang=self.HTML_LANG)
def response_file (self, file) :
"""
Wrap a file for returning in a response with direct_passthrough=True.
"""
return werkzeug.wrap_file(self.request.environ, file)
def process (self, **params) :
"""
Process request args to build internal request state.
"""
pass
def respond (self) :
"""
Generate a response, or raise an HTTPException
Does an HTML layout'd response per default.
"""
# returning e.g. redirect?
response = self.process(**self.params)
if response :
return response
# render as html per default
# XXX: might also be string?
text = unicode(self.render_html())
return pvl.web.response.html(text,
status = self.status(),
)
def cleanup (self) :
"""
After request processing. Do not fail :)
"""
pass