Source code for pyramid_frontend.theme

from __future__ import absolute_import, print_function, division

import os.path
import inspect
import pkg_resources

from pyramid.decorator import reify
from pyramid.settings import aslist, asbool
from pyramid.path import DottedNameResolver

from .templating.lookup import SuperTemplateLookup
from .templating.renderer import (mako_renderer_factory,
                                  mako_renderer_factory_nofilters)

static_dir = pkg_resources.resource_filename('pyramid_frontend', 'static')


default_sentinel = object()


[docs]class Theme(object): """ Represents a collection of templates, static files, image filters, and configuration corresponding to a particular visual theme (or "skin") used by the application. New themes are created by subclassing from this class. When passed to ``config.add_theme()``, The subclass will be instantiated with the application's ``settings`` dict and prepared for use. """ template_dir = 'templates' static_dir = 'static' assets = {} image_filters = [] includes = [] cache_impl = None cache_args = None def __init__(self, settings): self.settings = settings self._compiled_asset_cache = {} def __repr__(self): return '<%s: %s>' % (self.__class__.__name__, self.key) @classmethod
[docs] def qualify_path(cls, path): theme_file = os.path.abspath(inspect.getfile(cls)) return os.path.join(os.path.dirname(theme_file), path)
@classmethod
[docs] def traverse_attributes(cls, name, qualify_paths=False): assert len(cls.__bases__) == 1, \ "multiple inheritance not allowed for themes" while cls != Theme: el = getattr(cls, name) if qualify_paths: el = cls.qualify_path(el) yield cls.key, el cls = cls.__bases__[0]
@reify
[docs] def template_dirs(self): dirs = [] for key, dir in self.__class__.traverse_attributes( 'template_dir', qualify_paths=True): dirs.append(dir) return dirs
@reify
[docs] def lookup(self): return self._make_lookup()
@reify
[docs] def lookup_nofilters(self): return self._make_lookup(clear_default_filters=True)
def _make_lookup(self, clear_default_filters=False): default_filters = (['decode.utf8'] if clear_default_filters else ['escape']) template_imports = [ 'from webhelpers2.html import escape', ] template_imports.extend(aslist( self.settings.get('pyramid_frontend.template_imports', ''), flatten=False)) debug = asbool(self.settings.get('pyramid_frontend.debug')) base_module_dir = \ self.settings.get('pyramid_frontend.module_directory') module_dir = base_module_dir and os.path.join(base_module_dir, self.key) return SuperTemplateLookup(directories=self.template_dirs, input_encoding='utf-8', output_encoding='utf-8', imports=template_imports, default_filters=default_filters, filesystem_checks=debug, module_directory=module_dir, cache_impl=self.cache_impl, cache_args=self.cache_args) @reify
[docs] def stacked_image_filters(self): filters = {} collected = self.__class__.traverse_attributes('image_filters') for key, class_list in reversed(list(collected)): class_dict = {chain.suffix: chain for chain in class_list} filters.update(class_dict) return filters.values()
@reify
[docs] def stacked_assets(self): asset_specs = {} collected = self.__class__.traverse_attributes('assets') for key, class_dict in reversed(list(collected)): asset_specs.update(class_dict) return asset_specs
@reify
[docs] def stacked_includes(self): includes = [] collected = self.__class__.traverse_attributes('includes') for key, these in reversed(list(collected)): includes.extend(these) return includes
@reify
[docs] def keyed_static_dirs(self): cls = self.__class__ stack = list(cls.traverse_attributes('static_dir', qualify_paths=True)) stack.append(('pfe', static_dir)) return stack
[docs] def compiled_asset_path(self, key): if key in self._compiled_asset_cache: return self._compiled_asset_cache[key] else: map_path = os.path.join( self.settings['pyramid_frontend.compiled_asset_dir'], self.key, '%s.map' % key) with open(map_path) as f: self._compiled_asset_cache[key] = compiled_path = f.read() return compiled_path
[docs] def static_url_to_filesystem_path(self, url): """ Given a URL of the structure /_<theme key>/<path>, locate the static dir which corresponds to the theme key and re-qualify the <path> to that directory. """ assert url.startswith('/_') theme_key, path = url[2:].split('/', 1) theme_dirs = dict(self.keyed_static_dirs) base_dir = theme_dirs[theme_key] return os.path.join(base_dir, path)
[docs] def opt(self, key, default=default_sentinel): if default is default_sentinel: return getattr(self, key) else: return getattr(self, key, default)
[docs] def static(self, path): for key, static_dir in self.keyed_static_dirs: if os.path.exists(os.path.join(static_dir, path)): return '/_%s/%s' % (key, path) raise IOError('path %r does not exist in any static dirs' % path)
[docs] def compile(self, minify=True): output_dir = os.path.join( self.settings['pyramid_frontend.compiled_asset_dir'], self.key) for key, asset in self.stacked_assets.items(): asset.compile(key=key, theme=self, output_dir=output_dir, minify=minify)
[docs]def add_theme(config, cls): """ A Pyramid config directive to initialiaze and register a theme for use. """ resolved_cls = config.maybe_dotted(cls) settings = config.registry.settings theme = resolved_cls(settings) # Call includes package = inspect.getmodule(resolved_cls) resolver = DottedNameResolver(package=package) for include in theme.stacked_includes: config.include(resolver.maybe_resolve(include)) # Register static dirs. static_dirs = settings.setdefault('pyramid_frontend.static_registry', set()) for key, dir in theme.keyed_static_dirs: if (key, dir) not in static_dirs: static_dirs.add((key, dir)) config.add_static_view('_%s' % key, path=dir) # Update global image filter registry as well, and ensure there are no # conflicts. for chain in theme.stacked_image_filters: config.add_image_filter(chain, with_theme=theme) def register(theme): themes = settings.setdefault('pyramid_frontend.theme_registry', {}) themes[theme.key] = theme intr = config.introspectable(category_name='themes', discriminator=theme.key, title=theme.key, type_name=None) intr['theme'] = theme config.action(('theme', theme.key), register, args=(theme,), introspectables=(intr,))
[docs]def set_theme_strategy(config, strategy_func): """ A Pyramid config directive to set a customized theme-selection strategy for each request. """ def register(): registry = config.registry registry.pfe_theme_strategy = strategy_func config.action(('theme_strategy',), register)
[docs]def default_theme_strategy(request): """ The default theme selection strategy: just checks the ``pyramid_frontend.theme`` settings key. """ settings = request.registry.settings return settings['pyramid_frontend.theme']
[docs]def theme(request): """ The theme instance that should be used for this request. This property is both lazily-evaluated and reified. """ registry = request.registry strategy = getattr(registry, 'pfe_theme_strategy', default_theme_strategy) key = strategy(request) settings = registry.settings themes = settings.setdefault('pyramid_frontend.theme_registry', {}) return themes[key]
[docs]def includeme(config): config.include('.images') config.include('.assets') config.add_directive('add_theme', add_theme) config.add_directive('set_theme_strategy', set_theme_strategy) config.add_request_method(theme, 'theme', reify=True) config.add_renderer(name='.html', factory=mako_renderer_factory) config.add_renderer(name='.txt', factory=mako_renderer_factory_nofilters)