Mercurial > piecrust2
view piecrust/app.py @ 675:3df808b133f8
internal: Improve how theme configuration is validated and merged.
* Add default theme config up-front so it benefits from the usual validation.
* Add an explicit `use_default_theme_content` setting.
* Add/fix unit tests.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Sun, 06 Mar 2016 23:49:41 -0800 |
parents | 81d9c3a3a0b5 |
children | 894d286b348f |
line wrap: on
line source
import time import os.path import hashlib import logging from werkzeug.utils import cached_property from piecrust import ( RESOURCES_DIR, CACHE_DIR, TEMPLATES_DIR, ASSETS_DIR, THEME_DIR, CONFIG_PATH, THEME_CONFIG_PATH) from piecrust.appconfig import PieCrustConfiguration from piecrust.cache import ExtensibleCache, NullExtensibleCache from piecrust.plugins.base import PluginLoader from piecrust.environment import StandardEnvironment from piecrust.configuration import ConfigurationError, merge_dicts from piecrust.routing import Route from piecrust.sources.base import REALM_THEME from piecrust.taxonomies import Taxonomy logger = logging.getLogger(__name__) class PieCrust(object): def __init__(self, root_dir, cache=True, debug=False, theme_site=False, env=None, cache_key=None): self.root_dir = root_dir self.debug = debug self.theme_site = theme_site self.plugin_loader = PluginLoader(self) self.cache_key = cache_key or 'default' if cache: self.cache = ExtensibleCache(self.cache_dir) else: self.cache = NullExtensibleCache() self.env = env if self.env is None: self.env = StandardEnvironment() self.env.initialize(self) self.env.registerTimer('SiteConfigLoad') self.env.registerTimer('PageLoad') self.env.registerTimer("PageDataBuild") self.env.registerTimer("BuildRenderData") self.env.registerTimer("PageRender") self.env.registerTimer("PageRenderSegments") self.env.registerTimer("PageRenderLayout") self.env.registerTimer("PageSerialize") @cached_property def config(self): logger.debug("Creating site configuration...") start_time = time.perf_counter() paths = [] if not self.theme_site: if self.theme_dir: paths.append(os.path.join(self.theme_dir, THEME_CONFIG_PATH)) paths.append(os.path.join(self.root_dir, CONFIG_PATH)) else: paths.append(os.path.join(self.root_dir, THEME_CONFIG_PATH)) preview_path = os.path.join( self.root_dir, 'configs', 'theme_preview.yml') if os.path.isfile(preview_path): paths.append(preview_path) config_cache = self.cache.getCache('app') config = PieCrustConfiguration( paths, config_cache, theme_config=self.theme_site) if not self.theme_site and self.theme_dir: config.addFixup(_fixup_theme_config) self.env.stepTimer('SiteConfigLoad', time.perf_counter() - start_time) return config @cached_property def assets_dirs(self): assets_dirs = self._get_configurable_dirs( ASSETS_DIR, 'site/assets_dirs') # Also add the theme directory, if any. if self.theme_dir: default_theme_dir = os.path.join(self.theme_dir, ASSETS_DIR) if os.path.isdir(default_theme_dir): assets_dirs.append(default_theme_dir) return assets_dirs @cached_property def templates_dirs(self): templates_dirs = self._get_configurable_dirs( TEMPLATES_DIR, 'site/templates_dirs') # Also, add the theme directory, if any. if self.theme_dir: default_theme_dir = os.path.join(self.theme_dir, TEMPLATES_DIR) if os.path.isdir(default_theme_dir): templates_dirs.append(default_theme_dir) return templates_dirs @cached_property def theme_dir(self): if self.theme_site: return None td = self._get_dir(THEME_DIR) if td is not None: return td return os.path.join(RESOURCES_DIR, 'theme') @cached_property def cache_dir(self): return os.path.join(self.root_dir, CACHE_DIR, self.cache_key) @cached_property def sources(self): defs = {} for cls in self.plugin_loader.getSources(): defs[cls.SOURCE_NAME] = cls sources = [] for n, s in self.config.get('site/sources').items(): cls = defs.get(s['type']) if cls is None: raise ConfigurationError("No such page source type: %s" % s['type']) src = cls(self, n, s) sources.append(src) return sources @cached_property def routes(self): routes = [] for r in self.config.get('site/routes'): rte = Route(self, r) routes.append(rte) return routes @cached_property def taxonomies(self): taxonomies = [] for tn, tc in self.config.get('site/taxonomies').items(): tax = Taxonomy(self, tn, tc) taxonomies.append(tax) return taxonomies def getSource(self, source_name): for source in self.sources: if source.name == source_name: return source return None def getRoutes(self, source_name, *, skip_taxonomies=False): for route in self.routes: if route.source_name == source_name: if not skip_taxonomies or route.taxonomy_name is None: yield route def getRoute(self, source_name, route_metadata, *, skip_taxonomies=False): for route in self.getRoutes(source_name, skip_taxonomies=skip_taxonomies): if (route_metadata is None or route.matchesMetadata(route_metadata)): return route return None def getTaxonomyRoute(self, tax_name, source_name): for route in self.routes: if (route.taxonomy_name == tax_name and route.source_name == source_name): return route return None def getTaxonomy(self, tax_name): for tax in self.taxonomies: if tax.name == tax_name: return tax return None def _get_dir(self, default_rel_dir): abs_dir = os.path.join(self.root_dir, default_rel_dir) if os.path.isdir(abs_dir): return abs_dir return None def _get_configurable_dirs(self, default_rel_dir, conf_name): dirs = [] # Add custom directories from the configuration. conf_dirs = self.config.get(conf_name) if conf_dirs is not None: if isinstance(conf_dirs, str): dirs.append(os.path.join(self.root_dir, conf_dirs)) else: dirs += [os.path.join(self.root_dir, p) for p in conf_dirs] # Add the default directory if it exists. default_dir = os.path.join(self.root_dir, default_rel_dir) if os.path.isdir(default_dir): dirs.append(default_dir) return dirs def _fixup_theme_config(index, config): if index != 0: # We only want to affect the theme config, which is first. return # See if we want to generate the default theme content model. sitec = config.setdefault('site', {}) gen_default_model = sitec.setdefault('use_default_theme_content', True) if gen_default_model: # Create a default `theme_pages` source. srcc = sitec.setdefault('sources', {}) if not isinstance(srcc, dict): raise Exception("Theme configuration has invalid `site/sources`. " "Must be a dictionary.") default_theme_sources = { 'theme_pages': { 'type': 'default', 'ignore_missing_dir': True, 'fs_endpoint': 'pages', 'data_endpoint': 'site.pages', 'default_layout': 'default', 'item_name': 'page' } } sitec['sources'] = merge_dicts(default_theme_sources, srcc) sitec.setdefault('theme_tag_page', 'theme_pages:_tag.%ext%') sitec.setdefault('theme_category_page', 'theme_pages:_category.%ext%') # Create a default route for `theme_pages`. rtc = sitec.setdefault('routes', []) if not isinstance(rtc, list): raise Exception("Theme configuration has invalid `site/routes`. " "Must be a list.") rtc.append({ 'url': '/%path:slug%', 'source': 'theme_pages', 'func': 'pcurl(slug)'}) # Make all sources belong to the "theme" realm. srcc = sitec.get('sources') if srcc and isinstance(srcc, dict): for sn, sc in srcc.items(): sc['realm'] = REALM_THEME def apply_variant_and_values(app, config_variant=None, config_values=None): if config_variant is not None: logger.debug("Adding configuration variant '%s'." % config_variant) variant_path = os.path.join( app.root_dir, 'configs', '%s.yml' % config_variant) app.config.addVariant(variant_path) if config_values is not None: for name, value in config_values: logger.debug("Adding configuration override '%s': %s" % (name, value)) app.config.addVariantValue(name, value) class PieCrustFactory(object): def __init__( self, root_dir, *, cache=True, cache_key=None, config_variant=None, config_values=None, debug=False, theme_site=False): self.root_dir = root_dir self.cache = cache self.cache_key = cache_key self.config_variant = config_variant self.config_values = config_values self.debug = debug self.theme_site = theme_site def create(self): app = PieCrust( self.root_dir, cache=self.cache, cache_key=self.cache_key, debug=self.debug, theme_site=self.theme_site) apply_variant_and_values( app, self.config_variant, self.config_values) return app