Source code for bag.web.pyramid.genshi

"""Genshi integration for Pyramid.

This module allows the Genshi templating language --
http://pypi.python.org/pypi/Genshi/
-- to be used in the Pyramid web framework --
http://docs.pylonshq.com/

To enable this extension, just include it as your app starts up:

.. code-block:: python

    config.include('bag.web.pyramid.genshi')

Then you can decorate your views like the other Pyramid templating languages,
passing an asset specification to the ``renderer`` argument::

    @view_config(route_name='faq', renderer='mial:templates/faq-page.genshi')

Configuring a set of template directories
=========================================

You don't need to do this. But if you would like to
provide only the file name (without a path) to the renderer, like this::

    @view_config(route_name='faq', renderer='faq-page.genshi')

...then you must set ``genshi.directories`` in the application section
of your Pyramid application’s .ini file::

    [app:myapp]
    # ... other stuff ...
    genshi.directories = myapp:templates
                         myapp:another/templates/directory

This configures a set of directories that are searched.
The portion of each directory argument before the colon is a
package name. The remainder is a subpath within the package which
houses the templates.

But I have optimized for asset specifications. The renderer name is interpreted
first as an asset specification, then if the file is not found,
the directories are searched.

Other settings
==============

You can configure these rendering parameters::

    genshi.doctype = html5
    genshi.method = xhtml

You can also set the file extension that triggers the Genshi renderer.
The default is ".genshi"::

    genshi.extension = .genshi

The Genshi template loader keeps templates cached in memory. You can control
the size of this LRU cache through this setting (my default is 100)::

    genshi.max_cache_size = 100

Finally, internationalization of Genshi templates is enabled by the value of
``genshi.translation_domain``. By default it is the name of your
application package.

    genshi.translation_domain = myapp

Rendering a page fragment
=========================

From anywhere in your web app you can use the renderer like this::

    some_html = settings['genshi_renderer'].fragment(
        'myapp:templates/menu.genshi', template_context_dict)
"""

from os import path
from bag.settings import asbool
from zope.interface import implementer
from pyramid.interfaces import ITemplateRenderer
from pyramid.resource import abspath_from_resource_spec


[docs]def to_list(sequence): return [sequence] if isinstance(sequence, str) else sequence
[docs]def load_template(asset): """Make the Genshi TemplateLoader work with typical Pyramid asset specifications by passing this function to the TemplateLoader constructor as one of the paths. """ # print('LOAD {}'.format(asset)) abspath = abspath_from_resource_spec(asset) stream = open(abspath, 'r') # Genshi catches the possible IOError. mtime = path.getmtime(abspath) filename = path.basename(abspath) def file_not_changed(): # debug = 'SAME' if mtime == path.getmtime(abspath) else 'MODIFIED' # print(debug, abspath) return mtime == path.getmtime(abspath) return (abspath, filename, stream, file_not_changed)
[docs]@implementer(ITemplateRenderer) class GenshiTemplateRenderer: def __init__(self, settings): dirs = settings.get('genshi.directories', []) paths = [abspath_from_resource_spec(p) for p in to_list(dirs)] paths.insert(0, load_template) # enable Pyramid asset specifications # http://genshi.edgewall.org/wiki/Documentation/i18n # If genshi.translation_domain has a value, # we set up a callback in the loader domain = settings.get('genshi.translation_domain') if domain: from genshi.filters import Translator from pyramid.i18n import get_localizer from pyramid.threadlocal import get_current_request def translate(text): return get_localizer(get_current_request()) \ .translate(text, domain=domain) def callback(template): Translator(translate).setup(template) else: callback = None from genshi.template import TemplateLoader self.loader = TemplateLoader( paths, callback=callback, auto_reload=asbool(settings.get('pyramid.reload_templates')), max_cache_size=int(settings.get('genshi.max_cache_size', 100))) self.strip_whitespace = settings.get('genshi.strip_whitespace', True) self.doctype = settings.get('genshi.doctype', 'html5') self.method = settings.get('genshi.method', 'xhtml')
[docs] def implementation(self): return self
def __call__(self, value, system): """ ``value`` is the result of the view. Returns a result (a string or unicode object useful as a response body). Values computed by the system are passed by the system in the ``system`` parameter, which is a dictionary. Keys in the dictionary include: ``view`` (the view callable that returned the value), ``renderer_name`` (the template name or simple name of the renderer), ``context`` (the context object passed to the view), and ``request`` (the request object passed to the view). """ rn = system.get('renderer_name') or system['renderer_info'].name template = self.loader.load(rn) # Mix the *system* and *value* dictionaries try: system.update(value) except (TypeError, ValueError): raise ValueError('GenshiTemplateRenderer was passed a ' 'non-dictionary as value.') # Render the template and return a string return template.generate(**system) \ .render(method=self.method, encoding=None, # so Genshi outputs a unicode object doctype=self.doctype, strip_whitespace=self.strip_whitespace)
[docs] def fragment(self, template, dic): """Loads a Genshi template and returns its output as a unicode object containing an HTML fragment, taking care of some details. * template is the Genshi template file to be rendered. * dic is a dictionary to populate the template instance. """ t = self.loader.load(template) return t.generate(**dic).render(method=self.method, encoding=None)
[docs]def includeme(config): """Easily integrate Genshi template rendering into Pyramid.""" if hasattr(config, 'bag_genshi_included'): return # Include only once per config config.bag_genshi_included = True settings = config.get_settings() # By default, the translation domain is the application name: settings.setdefault('genshi.translation_domain', config.registry.__name__) # TODO: Evaluate pyramid_genshi which maps to TranslationString calls. # The renderer must be available to views so fragment templates can be # rendered. So we store it in the settings object: renderer = settings['genshi_renderer'] = GenshiTemplateRenderer(settings) def factory(info): """info.name is the value passed by the user as the renderer name. info.package is the "current package" when the renderer configuration statement was found. info.type is the renderer type name, i.e. ".genshi". info.registry is the "current" application registry when the renderer was created. info.settings is the ISettings dictionary related to the current app. """ return renderer extension = settings.get('genshi.extension', '.genshi') config.add_renderer(extension, factory)