Source code for kuha_oai_pmh_repo_handler.genshi_loader

#!/usr/bin/env python3
# Author(s): Toni Sissala
# Copyright 2020 Finnish Social Science Data Archive FSD / University of Tampere
# Licensed under the EUPL. See LICENSE.txt for full license.
"""Load genshi templates.

Usage::

    from genshi_loader import add_template_folder, GenPlate

    add_template_folder('/path/to/genshi/templates')

    @GenPlate('identify.xml')
    def handler_identify(genplate_instance):
        return {}

"""
import logging
from genshi.template import (
    TemplateLoader,
    TemplateNotFound
)
from genshi.output import (
    XMLSerializer,
    PI
)


_logger = logging.getLogger(__name__)
#: Template folders
FOLDERS = []


[docs]def add_template_folders(*folders): """Add folder to lookup for templates. :param folder: absolute path to folder containing genshi templates. :type folder: str """ FOLDERS.extend(folders)
[docs]def get_template_folders(): """Get template folders. :returns: template folders. :rtype: list """ return FOLDERS
class _KuhaXMLStylesheetFilter: """Add or discard stylesheet to XML documents that are rendered by Genshi. :param str or None stylesheet_url: Stylesheet URL that replaces '${stylesheet_url}' notation in templates. Set to falsey to filter out stylesheets from templates. """ def __init__(self, stylesheet_url): self.stylesheet_url = stylesheet_url def __call__(self, stream): for kind, data, pos in stream: if kind is PI and '${stylesheet_url}' in data[1]: if self.stylesheet_url: newdata = (data[0], data[1].replace('${stylesheet_url}', self.stylesheet_url)) yield kind, newdata, pos continue yield kind, data, pos
[docs]class KuhaXMLSerializer(XMLSerializer): """Subclass XMLSerializer to add a custom filter.""" stylesheet_url = None def __init__(self, **kw): super().__init__(**kw) self.filters.append(_KuhaXMLStylesheetFilter(self.stylesheet_url))
[docs]class GenPlate: """Genshi template decorator. Decorate functions that should write output to genshi-templates. The decorated function must be an asynchronous function and it must return a dictionary. Example:: from genshi_loader import GenPlate class Handler: @GenPlate('error.xml') async def build_error_message(self, genplate_instance): ... return {'msg': 'there was an error'} :param template_file: filename of the template to use. :type template_file: str :param template_folder: optional parameter to use a different template folder to lookup for given template_file. :type template_folder: str :raises: :exc:`ValueError` if decorated function returns invalid type. """ def __init__(self, template_file, **kw): self._template_file = template_file self.properties = kw self._loader = None def __call__(self, ctx_func): """Retuns asynchronous wrapper to serialize XML for output. .. Note:: This is the wrapper-function's docstring. The wrapper function's __doc__ is overwritten by ctx_func's __doc__ to make docstring lookup display correct docs in Sphinx. The wrapper calls :func:`ctx_func` with parameter :obj:`ctx` and gets the returned value. Passes this value to the template and serializes it as XML. Finally sends the XML-serialization to the template_writer function. :note: :func:`ctx_func` and :obj:`ctx` are transparent for this function. The only requirement regarding these objets is that :func:`ctx_func` MUST be asynchronous function and it should return a dictionary. :param ctx: Context that gets passed to the decorated function. :raises: :exc:`ValueError` for invalid return type of :func:`ctx_func` """ async def wrapper(ctx, *args, **kwargs): folders = get_template_folders() if self._loader is None: self._loader = TemplateLoader(folders) try: template = self._loader.load(self._template_file) except TemplateNotFound: _logger.error("Could not load template from folders: %s", ', '.join(folders)) raise context = await ctx_func(ctx, *args, **kwargs) if not isinstance(context, dict): raise ValueError("'{}' returns invalid type '{}'. Expecting dict".format( ctx_func, type(context))) if 'genplate' in context: raise ValueError("Found 'genplate' key in context. It is reserved for GenPlate properties.") _logger.debug("Serializing template '%s'", self._template_file) return template.generate(**{**context, **{'genplate': self.properties}})\ .serialize(method=KuhaXMLSerializer, strip_whitespace=True) wrapper.__doc__ = ctx_func.__doc__ return wrapper
[docs] @staticmethod def set_stylesheet_url(path): """Set stylesheet URL to KuhaXMLSerializer. Call this to replace '${stylesheet_url}' notation in templates with 'path'. :param path: Replaces stylesheet_url in templates. """ KuhaXMLSerializer.stylesheet_url = path