Source code for uw.web.wsgi.function

"""Support for creating WSGI handlers from functions.

This module makes it easy to create a WSGI handler whose implementation is a
function with a convenient signature.  The function body does not need to
deal with the WSGI interface directly but can instead accept parameters
corresponding to whatever information it needs from the request and return
a result which is a data structure representing the HTML page to be rendered.

There are also routines making it easy to create handlers which return PDF,
ZIP, CSV, or text files.
"""

import json
from urllib.parse import quote

from . import parameter

[docs]class FinalHandler (object): """A handler class to create handlers with convenient signature. This allows the body of a handler to be written as a function which takes parameter values extracted from the environment by a number of standard methods. The return value of the function can be converted into an actual CGI response by any desired conversion routine. """ def __init__ (self, handler, convert, use_set_status=False, use_add_header=False): """ handler (**params) -> response: the request handler convert (environ, outer_headers, *result) -> wsgi response use_set_status indicates whether or not to provide a set_status parameter to allow the nested function to provide a different HTTP status than the default 200 OK. use_add_header indicates whether or not to provide an add_header parameter to allow the nested function to add an extra header to the HTTP response. """ self.__handler = handler self.__convert = convert self.__use_set_status = use_set_status self.__use_add_header = use_add_header @property def handler (self): """The handler function. Note that this is NOT a WSGI handler. Instead the parameters it expects are determined by the parameters expected to be set by previous handlers in the handler chain. """ return self.__handler @property def convert (self): """The result conversion routine. This converts the result of the handler function into a proper return value for a WSGI handler. """ return self.__convert @property def use_set_status (self): """Whether the handler function needs to set the HTTP status. """ return self.__use_set_status @property def use_add_header (self): """Whether the handler function needs to add HTTP headers. """ return self.__use_add_header def __call__ (self, environ, start_response): outer_status = ["200 OK"] outer_headers = [] params = parameter.get_params (environ) if self.use_set_status: def set_status (status): outer_status[0] = status params['set_status'] = set_status if self.use_add_header: def add_header (header): outer_headers.append (header) params['add_header'] = add_header result = self.handler (**params) response = self.convert (environ, outer_headers, result) start_response (outer_status[0], outer_headers) return [response]
[docs]def return_html_template (handler, template): def result (environ, start_response): environ['template'] = template return handler (environ, start_response) return result
[docs]def header_content_disposition (filename=None): param = "" if filename is None else "; filename*=utf-8''" + quote (filename) return ('Content-Disposition', "attachment" + param)
[docs]def return_html (handler, encoding='utf-8', use_set_status=False, use_add_header=False): """A FinalHandler subclass for returning HTML using a template. The handler implementation is expected to return a 2-tuple consisting of the page title and the page contents. The final response is obtained by passing the results through a template found in the 'template' environment entry and converting the result to bytes using the specified encoding. This is meant to work with the ll.xist XML/HTML library. """ def convert (environ, outer_headers, result): title, page = result response = environ['template'] (title, page, environ).bytes (encoding=encoding) outer_headers.append (('Content-Type', 'text/html; charset=%s' % encoding)) outer_headers.append (('Content-Length', str (len (response)))) return response return FinalHandler (handler, convert, use_set_status, use_add_header)
[docs]def return_json (handler, encoding='utf-8', use_set_status=False, use_add_header=False): """A FinalHandler subclass for returning a JSON result. The handler implementation is expected to return an object that can be written as JSON using :func:`json.dumps()`. """ def convert (environ, outer_headers, json_object): response = json.dumps (json_object).encode (encoding=encoding) outer_headers.append (('Content-Type', 'application/json; charset=%s' % encoding)) outer_headers.append (('Content-Length', str (len (response)))) return response return FinalHandler (handler, convert, use_set_status, use_add_header)
[docs]def return_pdf (handler, use_set_status=False, use_add_header=False): """A FinalHandler subclass for returning a PDF file. The handler implementation is expected to return a 2-tuple consisting of the PDF file content bytes and filename. The HTTP response sets the Content-Length header to the length of the PDF file contents. """ def convert (environ, outer_headers, result): response, filename = result response = bytes (response) outer_headers.append (('Content-Type', 'application/pdf')) outer_headers.append (('Content-Length', str (len (response)))) outer_headers.append (header_content_disposition (filename)) return response return FinalHandler (handler, convert, use_set_status, use_add_header)
[docs]def return_png (handler, use_set_status=False, use_add_header=False): """A FinalHandler subclass for returning a PNG file. The handler implementation is expected to return a 2-tuple consisting of the PNG file content bytes and filename. The HTTP response sets the Content-Length header to the length of the PNG file contents. """ def convert (environ, outer_headers, result): response, filename = result response = bytes (response) outer_headers.append (('Content-Type', 'image/png')) outer_headers.append (('Content-Length', str (len (response)))) return response return FinalHandler (handler, convert, use_set_status, use_add_header)
[docs]def return_zip (handler, use_set_status=False, use_add_header=False): """A FinalHandler subclass for returning a ZIP file. The handler implementation is expected to return a 2-tuple consisting of the .zip file content bytes and filename. The HTTP response sets the Content-Length header to the length of the .zip file contents. """ def convert (environ, outer_headers, result): response, filename = result outer_headers.append (('Content-Type', 'application/zip')) outer_headers.append (('Content-Length', str (len (response)))) outer_headers.append (header_content_disposition (filename)) return response return FinalHandler (handler, convert, use_set_status, use_add_header)
[docs]def return_csv (handler, encoding='utf-8', use_set_status=False, use_add_header=False): """A FinalHandler subclass for returning a CSV file. The handler implementation is expected to return a 2-tuple consisting of the .csv file content text and filename. The HTTP response sets the Content-Length header to the length of the encoded .csv file contents. """ def convert (environ, outer_headers, result): response, filename = result response = response.encode (encoding) outer_headers.append (('Content-Type', 'text/csv; charset=%s' % encoding)) outer_headers.append (('Content-Length', str (len (response)))) outer_headers.append (header_content_disposition (filename)) return response return FinalHandler (handler, convert, use_set_status, use_add_header)
[docs]def return_text (handler, encoding='utf-8', use_set_status=False, use_add_header=False): """A FinalHandler subclass for returning a text file. The handler implementation is expected to return just the file contents. The HTTP response sets the Content-Length header to the length of the text file contents once encoded as bytes. """ def convert (environ, outer_headers, page): response = page.encode (encoding) outer_headers.append (('Content-Type', 'text/plain; charset=%s' % encoding)) outer_headers.append (('Content-Length', str (len (response)))) return response return FinalHandler (handler, convert, use_set_status, use_add_header)
[docs]def return_text_download (handler, encoding='utf-8', use_set_status=False, use_add_header=False): """A FinalHandler subclass for returning a text file as a download. The handler implementation is expected to return the file contents and a suggested filename as which it should be saved. The HTTP response sets the Content-Length header to the length of the text file contents once encoded as bytes. """ def convert (environ, outer_headers, result): response, filename = result response = response.encode (encoding) outer_headers.append (('Content-Type', 'text/plain; charset=%s' % encoding)) outer_headers.append (('Content-Length', str (len (response)))) outer_headers.append (header_content_disposition (filename)) return response return FinalHandler (handler, convert, use_set_status, use_add_header)