Latest Version: 0.9.6.2
/Users/bbangert/Programming/Python/pylons/pylons/templating.py
0001"""Buffet templating plugin and render functions
0002
0003The Buffet object is styled after the original Buffet module that implements
0004template language neutral rendering for CherryPy. This version of Buffet also
0005contains caching functionality that utilizes 
0006`Beaker middleware <http://beaker.groovie.org/>`_ to provide template language
0007neutral caching functionality.
0008
0009A customized version of 
0010`BuffetMyghty <http://projects.dowski.com/projects/buffetmyghty>`_ is included
0011that provides a template API hook as the ``pylonsmyghty`` engine. This version
0012of BuffetMyghty disregards some of the TurboGears API spec so that traditional
0013Myghty template names can be used with ``/`` and file extensions.
0014
0015The render functions are intended as the primary user-visible rendering 
0016commands and hook into Buffet to make rendering content easy.
0017"""
0018import logging
0019import os
0020import warnings
0021
0022try:
0023    from cStringIO import StringIO
0024except ImportError:
0025    from StringIO import StringIO
0026import pkg_resources
0027
0028import pylons
0029
0030__all__ = ['Buffet', 'MyghtyTemplatePlugin', 'render', 'render_response']
0031
0032PYLONS_VARS = ['c', 'g', 'h', 'render', 'request', 'session', 'translator',
0033               'ungettext', '_', 'N_']
0034
0035log = logging.getLogger(__name__)
0036
0037class BuffetError(Exception):
0038    """Buffet Exception"""
0039    pass
0040
0041
0042class Buffet(object):
0043    """Buffet style plug-in template rendering
0044    
0045    Buffet implements template language plug-in support modeled highly on the
0046    `Buffet Project <http://projects.dowski.com/projects/buffet>`_ from which
0047    this class inherits its name.
0048    """
0049    def __init__(self, default_engine=None, template_root=None,
0050        default_options=None, **config):
0051        """Initialize the Buffet renderer, and optionally set a default
0052        engine/options"""
0053        if default_options is None:
0054            default_options = {}
0055        self.default_engine = default_engine
0056        self.template_root = template_root
0057        self.default_options = default_options
0058        self.engines = {}
0059        log.debug("Initialized Buffet object")
0060        if self.default_engine:
0061            self.prepare(default_engine, template_root, **config)
0062
0063    def prepare(self, engine_name, template_root=None, alias=None, **config):
0064        """Prepare a template engine for use
0065        
0066        This method must be run before the `render <#render>`_ method is called
0067        so that the ``template_root`` and options can be set. Template engines
0068        can also be aliased if you wish to use multiplate configurations of the
0069        same template engines, or prefer a shorter name when rendering a
0070        template with the engine of your choice.
0071        """
0072        Engine = available_engines.get(engine_name, None)
0073        if not Engine:
0074            raise TemplateEngineMissing('Please install a plugin for '
0075                '"%s" to use its functionality' % engine_name)
0076        engine_name = alias or engine_name
0077        extra_vars_func = config.pop(engine_name + '.extra_vars_func', None)
0078        self.engines[engine_name] =               dict(engine=Engine(extra_vars_func=extra_vars_func,
0080                               options=config),
0081                 root=template_root)
0082        log.debug("Adding %s template language for use with Buffet",
0083                  engine_name)
0084
0085    def _update_names(self, ns):
0086        """Return a dict of Pylons vars and their respective objects updated
0087        with the ``ns`` dict."""
0088        d = dict(
0089            c=pylons.c._current_obj(),
0090            g=pylons.g._current_obj(),
0091            h=pylons.config.get('pylons.h') or pylons.h._current_obj(),
0092            render=render,
0093            request=pylons.request._current_obj(),
0094            translator=pylons.translator,
0095            ungettext=pylons.i18n.ungettext,
0096            _=pylons.i18n._,
0097            N_=pylons.i18n.N_
0098            )
0099
0100        # If the session was overriden to be None, don't populate the session
0101        # var
0102        if pylons.config['pylons.environ_config'].get('session', True):
0103            d['session'] = pylons.session._current_obj()
0104        d.update(ns)
0105        log.debug("Updated render namespace with pylons vars: %s", d)
0106        return d
0107
0108    def render(self, engine_name=None, template_name=None,
0109               include_pylons_variables=True, namespace=None,
0110               cache_key=None, cache_expire=None, cache_type=None, **options):
0111        """Render a template using a template engine plug-in
0112        
0113        To use templates it is expected that you will attach data to be used in
0114        the template to the ``c`` variable which is available in the controller
0115        and the template. 
0116        
0117        When porting code from other projects it is sometimes easier to use an
0118        exisitng dictionary which can be specified with ``namespace``.
0119        
0120        ``engine_name``
0121            The name of the template engine to use, which must be
0122            'prepared' first.
0123        ``template_name``
0124            Name of the template to render
0125        ``include_pylons_variables``
0126            If a custom namespace is specified this determines whether Pylons 
0127            variables are included in the namespace or not. Defaults to 
0128            ``True``.
0129        ``namespace``
0130            A custom dictionary of names and values to be substituted in the
0131            template.
0132        
0133        Caching options (uses Beaker caching middleware)
0134        
0135        ``cache_key``
0136            Key to cache this copy of the template under.
0137        ``cache_type``
0138            Valid options are ``dbm``, ``file``, ``memory``, or 
0139            ``ext:memcached``.
0140        ``cache_expire``
0141            Time in seconds to cache this template with this ``cache_key`` for.
0142            Or use 'never' to designate that the cache should never expire.
0143        
0144        The minimum key required to trigger caching is ``cache_expire='never'``
0145        which will cache the template forever seconds with no key.
0146        
0147        All other keyword options are passed directly to the template engine
0148        used.
0149        """
0150        if not engine_name and self.default_engine:
0151            engine_name = self.default_engine
0152        engine_config = self.engines.get(engine_name)
0153
0154        if not engine_config:
0155            raise Exception("No engine with that name configured: %s" %                                   engine_name)
0157
0158        full_path = template_name
0159
0160        if engine_name == 'pylonsmyghty':
0161            if namespace is None:
0162                namespace = {}
0163            # Reserved myghty keywords
0164            for key in ('output_encoding', 'encoding_errors',
0165                        'disable_unicode'):
0166                if key in namespace:
0167                    options[key] = namespace.pop(key)
0168
0169            if include_pylons_variables:
0170                namespace['_global_args'] = self._update_names({})
0171            else:
0172                namespace['_global_args'] = {}
0173
0174            # If they passed in a variable thats listed in the global_args,
0175            # update the global args one instead of duplicating it
0176            interp = engine_config['engine'].interpreter
0177            for key in interp.global_args.keys() +                   interp.init_params.get('allow_globals', []):
0179                if key in namespace:
0180                    namespace['_global_args'][key] = namespace.pop(key)
0181        else:
0182            if namespace is None:
0183                if not include_pylons_variables:
0184                    raise BuffetError('You must specify ``namespace`` when '
0185                                      '``include_pylons_variables`` is False')
0186                else:
0187                    namespace = self._update_names({})
0188            elif include_pylons_variables:
0189                namespace = self._update_names(namespace)
0190
0191            if not full_path.startswith(os.path.sep) and not                       engine_name.startswith('pylons') and not                       engine_name.startswith('mako') and                       engine_config['root'] is not None:
0195                full_path = os.path.join(engine_config['root'], template_name)
0196                full_path = full_path.replace(os.path.sep, '.').lstrip('.')
0197
0198        # Don't pass format into the template engine if it's None
0199        if 'format' in options and options['format'] is None:
0200            del options['format']
0201
0202        # If one of them is not None then the user did set something
0203        if cache_key is not None or cache_expire is not None or cache_type               is not None:
0205            if not cache_type:
0206                cache_type = 'dbm'
0207            if not cache_key:
0208                cache_key = 'default'
0209            if cache_expire == 'never':
0210                cache_expire = None
0211            def content():
0212                log.debug("Cached render running for %s", full_path)
0213                return engine_config['engine'].render(namespace,
0214                    template=full_path, **options)
0215            tfile = full_path
0216            if options.get('fragment', False):
0217                tfile += '_frag'
0218            if options.get('format', False):
0219                tfile += options['format']
0220            log.debug("Using render cache for %s", full_path)
0221            mycache = pylons.cache.get_cache(tfile)
0222            content = mycache.get_value(cache_key, createfunc=content,
0223                type=cache_type, expiretime=cache_expire)
0224            return content
0225
0226        log.debug("Rendering template %s with engine %s", full_path, engine_name)
0227        return engine_config['engine'].render(namespace, template=full_path,
0228            **options)
0229
0230
0231class TemplateEngineMissing(Exception):
0232    """Exception to toss when an engine is missing"""
0233    pass
0234
0235
0236class MyghtyTemplatePlugin(object):
0237    """Myghty Template Plugin
0238    
0239    This Myghty Template Plugin varies from the official BuffetMyghty in that 
0240    it will properly populate all the default Myghty variables needed and 
0241    render fragments.
0242    """
0243    extension = "myt"
0244
0245    def __init__(self, extra_vars_func=None, options=None):
0246        """Initialize Myghty template engine"""
0247        if options is None:
0248            options = {}
0249        myt_opts = {}
0250        for k, v in options.iteritems():
0251            if k.startswith('myghty.'):
0252                myt_opts[k[7:]] = v
0253        import myghty.interp
0254        self.extra_vars = extra_vars_func
0255        self.interpreter = myghty.interp.Interpreter(**myt_opts)
0256
0257    def load_template(self, template_path):
0258        """Unused method for TG plug-in API compatibility"""
0259        pass
0260
0261    def render(self, info, format="html", fragment=False, template=None,
0262               output_encoding=None, encoding_errors=None,
0263               disable_unicode=None):
0264        """Render the template indicated with info as the namespace and globals
0265        from the ``info['_global_args']`` key."""
0266        buf = StringIO()
0267        global_args = info.pop('_global_args')
0268        if self.extra_vars:
0269            global_args.update(self.extra_vars())
0270        optional_args = {}
0271        if fragment:
0272            optional_args['disable_wrapping'] = True
0273        if output_encoding:
0274            optional_args['output_encoding'] = output_encoding
0275        if encoding_errors:
0276            optional_args['encoding_errors'] = encoding_errors
0277        if disable_unicode:
0278            optional_args['disable_unicode'] = disable_unicode
0279        self.interpreter.execute(template, request_args=info,
0280                                 global_args=global_args, out_buffer=buf,
0281                                 **optional_args)
0282        return buf.getvalue()
0283
0284
0285available_engines = {}
0286
0287
0288for entry_point in           pkg_resources.iter_entry_points('python.templating.engines'):
0290    try:
0291        Engine = entry_point.load()
0292        available_engines[entry_point.name] = Engine
0293    except:
0294        import sys
0295        from pkg_resources import DistributionNotFound
0296        # Warn when there's a problem loading a Buffet plugin unless it's
0297        # pylonsmyghty reporting there's no Myghty installed
0298        if not isinstance(sys.exc_info()[1], DistributionNotFound) or                   entry_point.name != 'pylonsmyghty':
0300            import traceback
0301            import warnings
0302            tb = StringIO()
0303            traceback.print_exc(file=tb)
0304            warnings.warn("Unable to load template engine entry point: '%s': "
0305                          "%s" % (entry_point, tb.getvalue()), RuntimeWarning,
0306                          2)
0307
0308
0309def render(*args, **kargs):
0310    """Render a template and return it as a string (possibly Unicode)
0311    
0312    Optionally takes 3 keyword arguments to use caching supplied by Buffet.
0313    
0314    Examples:
0315        
0316    .. code-block:: Python
0317
0318        content = render('/my/template.mako')
0319        print content
0320        content = render('/my/template2.myt', fragment=True)
0321    
0322    .. admonition:: Note
0323        
0324        Not all template languages support the concept of a fragment. In those
0325        template languages that do support the fragment option, this usually 
0326        implies that the template will be rendered without extending or 
0327        inheriting any site skin.
0328    """
0329    fragment = kargs.pop('fragment', False)
0330    format = kargs.pop('format', None)
0331    args = list(args)
0332    template = args.pop()
0333    cache_args = dict(cache_expire=kargs.pop('cache_expire', None),
0334                       cache_type=kargs.pop('cache_type', None),
0335                       cache_key=kargs.pop('cache_key', None))
0336    log.debug("Render called with %s args and %s keyword args", args, kargs)
0337    if args:
0338        engine = args.pop()
0339        return pylons.buffet.render(engine, template, fragment=fragment,
0340                                    format=format, namespace=kargs,
0341                                    **cache_args)
0342    return pylons.buffet.render(template_name=template, fragment=fragment,
0343                                format=format, namespace=kargs, **cache_args)
0344
0345
0346def render_response(*args, **kargs):
0347    """Returns the rendered response within a Response object
0348    
0349    See ``render`` for information on rendering.
0350    
0351    Example:
0352    
0353    .. code-block:: Python
0354        
0355        def view(self):
0356            return render_response('/my/template.mako')
0357    """
0358    warnings.warn(pylons.legacy.render_response_warning,
0359                  PendingDeprecationWarning, 2)
0360
0361    response = pylons.response._current_obj()
0362    response.content = render(*args, **kargs)
0363    output_encoding = kargs.get('output_encoding')
0364    encoding_errors = kargs.get('encoding_errors')
0365    if output_encoding:
0366        response.headers['Content-Type'] = '%s; charset=%s' %               (pylons.Response.defaults.get('content_type',
0368                                          'text/html'), output_encoding)
0369    if encoding_errors:
0370        response.encoding_errors = encoding_errors
0371    return response
0372render_response.__doc__ = 'Pending Deprecation: %s.\n\n%s' %       (pylons.legacy.render_response_warning, render_response.__doc__)

Top