comparison piecrust/app.py @ 852:4850f8c21b6e

core: Start of the big refactor for PieCrust 3.0. * Everything is a `ContentSource`, including assets directories. * Most content sources are subclasses of the base file-system source. * A source is processed by a "pipeline", and there are 2 built-in pipelines, one for assets and one for pages. The asset pipeline is vaguely functional, but the page pipeline is completely broken right now. * Rewrite the baking process as just running appropriate pipelines on each content item. This should allow for better parallelization.
author Ludovic Chabant <ludovic@chabant.com>
date Wed, 17 May 2017 00:11:48 -0700
parents 7d83b9484b98
children f070a4fc033c
comparison
equal deleted inserted replaced
851:2c7e57d80bba 852:4850f8c21b6e
1 import time 1 import time
2 import os.path 2 import os.path
3 import hashlib
4 import logging 3 import logging
5 import urllib.parse 4 import urllib.parse
6 from werkzeug.utils import cached_property 5 from werkzeug.utils import cached_property
7 from piecrust import ( 6 from piecrust import (
8 RESOURCES_DIR, 7 RESOURCES_DIR,
9 CACHE_DIR, TEMPLATES_DIR, ASSETS_DIR, 8 CACHE_DIR, TEMPLATES_DIR, ASSETS_DIR,
10 THEME_DIR, PLUGINS_DIR, 9 THEME_DIR, PLUGINS_DIR,
11 CONFIG_PATH, THEME_CONFIG_PATH) 10 CONFIG_PATH, THEME_CONFIG_PATH)
12 from piecrust.appconfig import PieCrustConfiguration 11 from piecrust.appconfig import PieCrustConfiguration
13 from piecrust.cache import ExtensibleCache, NullExtensibleCache 12 from piecrust.cache import ExtensibleCache, NullExtensibleCache
14 from piecrust.configuration import ConfigurationError, merge_dicts 13 from piecrust.configuration import ConfigurationError
15 from piecrust.environment import StandardEnvironment 14 from piecrust.environment import StandardEnvironment
15 from piecrust.page import Page
16 from piecrust.plugins.base import PluginLoader 16 from piecrust.plugins.base import PluginLoader
17 from piecrust.routing import Route 17 from piecrust.routing import Route
18 from piecrust.sources.base import REALM_THEME
19 18
20 19
21 logger = logging.getLogger(__name__) 20 logger = logging.getLogger(__name__)
22 21
23 22
37 36
38 self.env = env 37 self.env = env
39 if self.env is None: 38 if self.env is None:
40 self.env = StandardEnvironment() 39 self.env = StandardEnvironment()
41 self.env.initialize(self) 40 self.env.initialize(self)
42 self.env.registerTimer('SiteConfigLoad') 41 self.env.stats.registerTimer('SiteConfigLoad')
43 self.env.registerTimer('PageLoad') 42 self.env.stats.registerTimer('PageLoad')
44 self.env.registerTimer("PageDataBuild") 43 self.env.stats.registerTimer("PageDataBuild")
45 self.env.registerTimer("BuildRenderData") 44 self.env.stats.registerTimer("BuildRenderData")
46 self.env.registerTimer("PageRender") 45 self.env.stats.registerTimer("PageRender")
47 self.env.registerTimer("PageRenderSegments") 46 self.env.stats.registerTimer("PageRenderSegments")
48 self.env.registerTimer("PageRenderLayout") 47 self.env.stats.registerTimer("PageRenderLayout")
49 self.env.registerTimer("PageSerialize") 48 self.env.stats.registerTimer("PageSerialize")
50 49
51 @cached_property 50 @cached_property
52 def config(self): 51 def config(self):
53 logger.debug("Creating site configuration...") 52 logger.debug("Creating site configuration...")
54 start_time = time.perf_counter() 53 start_time = time.perf_counter()
62 if not self.theme_site and self.theme_dir: 61 if not self.theme_site and self.theme_dir:
63 theme_path = os.path.join(self.theme_dir, THEME_CONFIG_PATH) 62 theme_path = os.path.join(self.theme_dir, THEME_CONFIG_PATH)
64 63
65 config_cache = self.cache.getCache('app') 64 config_cache = self.cache.getCache('app')
66 config = PieCrustConfiguration( 65 config = PieCrustConfiguration(
67 path=path, theme_path=theme_path, 66 path=path, theme_path=theme_path,
68 cache=config_cache, theme_config=self.theme_site) 67 cache=config_cache, theme_config=self.theme_site)
69 68
70 local_path = os.path.join( 69 local_path = os.path.join(
71 self.root_dir, 'configs', 'local.yml') 70 self.root_dir, 'configs', 'local.yml')
72 config.addVariant(local_path, raise_if_not_found=False) 71 config.addVariant(local_path, raise_if_not_found=False)
73 72
74 if self.theme_site: 73 if self.theme_site:
75 variant_path = os.path.join( 74 variant_path = os.path.join(
76 self.root_dir, 'configs', 'theme_preview.yml') 75 self.root_dir, 'configs', 'theme_preview.yml')
77 config.addVariant(variant_path, raise_if_not_found=False) 76 config.addVariant(variant_path, raise_if_not_found=False)
78 77
79 self.env.stepTimer('SiteConfigLoad', time.perf_counter() - start_time) 78 self.env.stats.stepTimer('SiteConfigLoad',
79 time.perf_counter() - start_time)
80 return config 80 return config
81 81
82 @cached_property 82 @cached_property
83 def assets_dirs(self): 83 def assets_dirs(self):
84 assets_dirs = self._get_configurable_dirs( 84 assets_dirs = self._get_configurable_dirs(
85 ASSETS_DIR, 'site/assets_dirs') 85 ASSETS_DIR, 'site/assets_dirs')
86 86
87 # Also add the theme directory, if any. 87 # Also add the theme directory, if any.
88 if self.theme_dir: 88 if self.theme_dir:
89 default_theme_dir = os.path.join(self.theme_dir, ASSETS_DIR) 89 default_theme_dir = os.path.join(self.theme_dir, ASSETS_DIR)
90 if os.path.isdir(default_theme_dir): 90 if os.path.isdir(default_theme_dir):
93 return assets_dirs 93 return assets_dirs
94 94
95 @cached_property 95 @cached_property
96 def templates_dirs(self): 96 def templates_dirs(self):
97 templates_dirs = self._get_configurable_dirs( 97 templates_dirs = self._get_configurable_dirs(
98 TEMPLATES_DIR, 'site/templates_dirs') 98 TEMPLATES_DIR, 'site/templates_dirs')
99 99
100 # Also, add the theme directory, if any. 100 # Also, add the theme directory, if any.
101 if self.theme_dir: 101 if self.theme_dir:
102 default_theme_dir = os.path.join(self.theme_dir, TEMPLATES_DIR) 102 default_theme_dir = os.path.join(self.theme_dir, TEMPLATES_DIR)
103 if os.path.isdir(default_theme_dir): 103 if os.path.isdir(default_theme_dir):
146 if cls is None: 146 if cls is None:
147 raise ConfigurationError("No such page source type: %s" % 147 raise ConfigurationError("No such page source type: %s" %
148 s['type']) 148 s['type'])
149 src = cls(self, n, s) 149 src = cls(self, n, s)
150 sources.append(src) 150 sources.append(src)
151
151 return sources 152 return sources
152 153
153 @cached_property 154 @cached_property
154 def routes(self): 155 def routes(self):
155 routes = [] 156 routes = []
156 for r in self.config.get('site/routes'): 157 for r in self.config.get('site/routes'):
157 rte = Route(self, r) 158 rte = Route(self, r)
158 routes.append(rte) 159 routes.append(rte)
159 return routes 160 return routes
160
161 @cached_property
162 def generators(self):
163 defs = {}
164 for cls in self.plugin_loader.getPageGenerators():
165 defs[cls.GENERATOR_NAME] = cls
166
167 gens = []
168 for n, g in self.config.get('site/generators').items():
169 cls = defs.get(g['type'])
170 if cls is None:
171 raise ConfigurationError("No such page generator type: %s" %
172 g['type'])
173 gen = cls(self, n, g)
174 gens.append(gen)
175 return gens
176 161
177 @cached_property 162 @cached_property
178 def publishers(self): 163 def publishers(self):
179 defs_by_name = {} 164 defs_by_name = {}
180 defs_by_scheme = {} 165 defs_by_scheme = {}
195 elif isinstance(t, str): 180 elif isinstance(t, str):
196 comps = urllib.parse.urlparse(t) 181 comps = urllib.parse.urlparse(t)
197 pub_type = comps.scheme 182 pub_type = comps.scheme
198 is_scheme = True 183 is_scheme = True
199 cls = (defs_by_scheme.get(pub_type) if is_scheme 184 cls = (defs_by_scheme.get(pub_type) if is_scheme
200 else defs_by_name.get(pub_type)) 185 else defs_by_name.get(pub_type))
201 if cls is None: 186 if cls is None:
202 raise ConfigurationError("No such publisher: %s" % pub_type) 187 raise ConfigurationError("No such publisher: %s" % pub_type)
203 tgt = cls(self, n, t) 188 tgt = cls(self, n, t)
204 tgts.append(tgt) 189 tgts.append(tgt)
205 return tgts 190 return tgts
208 for source in self.sources: 193 for source in self.sources:
209 if source.name == source_name: 194 if source.name == source_name:
210 return source 195 return source
211 return None 196 return None
212 197
213 def getGenerator(self, generator_name):
214 for gen in self.generators:
215 if gen.name == generator_name:
216 return gen
217 return None
218
219 def getSourceRoutes(self, source_name): 198 def getSourceRoutes(self, source_name):
220 for route in self.routes: 199 for route in self.routes:
221 if route.source_name == source_name: 200 if route.source_name == source_name:
222 yield route 201 yield route
223 202
224 def getSourceRoute(self, source_name, route_metadata): 203 def getSourceRoute(self, source_name, route_params):
225 for route in self.getSourceRoutes(source_name): 204 for route in self.getSourceRoutes(source_name):
226 if (route_metadata is None or 205 if (route_params is None or
227 route.matchesMetadata(route_metadata)): 206 route.matchesParameters(route_params)):
228 return route
229 return None
230
231 def getGeneratorRoute(self, generator_name):
232 for route in self.routes:
233 if route.generator_name == generator_name:
234 return route 207 return route
235 return None 208 return None
236 209
237 def getPublisher(self, target_name): 210 def getPublisher(self, target_name):
238 for pub in self.publishers: 211 for pub in self.publishers:
239 if pub.target == target_name: 212 if pub.target == target_name:
240 return pub 213 return pub
241 return None 214 return None
215
216 def getPage(self, content_item):
217 cache_key = content_item.spec
218 return self.env.page_repository.get(
219 cache_key,
220 lambda: Page(content_item))
242 221
243 def _get_dir(self, default_rel_dir): 222 def _get_dir(self, default_rel_dir):
244 abs_dir = os.path.join(self.root_dir, default_rel_dir) 223 abs_dir = os.path.join(self.root_dir, default_rel_dir)
245 if os.path.isdir(abs_dir): 224 if os.path.isdir(abs_dir):
246 return abs_dir 225 return abs_dir
267 246
268 def apply_variant_and_values(app, config_variant=None, config_values=None): 247 def apply_variant_and_values(app, config_variant=None, config_values=None):
269 if config_variant is not None: 248 if config_variant is not None:
270 logger.debug("Adding configuration variant '%s'." % config_variant) 249 logger.debug("Adding configuration variant '%s'." % config_variant)
271 variant_path = os.path.join( 250 variant_path = os.path.join(
272 app.root_dir, 'configs', '%s.yml' % config_variant) 251 app.root_dir, 'configs', '%s.yml' % config_variant)
273 app.config.addVariant(variant_path) 252 app.config.addVariant(variant_path)
274 253
275 if config_values is not None: 254 if config_values is not None:
276 for name, value in config_values: 255 for name, value in config_values:
277 logger.debug("Adding configuration override '%s': %s" % (name, value)) 256 logger.debug("Adding configuration override '%s': %s" %
257 (name, value))
278 app.config.addVariantValue(name, value) 258 app.config.addVariantValue(name, value)
279 259
280 260
281 class PieCrustFactory(object): 261 class PieCrustFactory(object):
262 """ A class that builds a PieCrust app instance.
263 """
282 def __init__( 264 def __init__(
283 self, root_dir, *, 265 self, root_dir, *,
284 cache=True, cache_key=None, 266 cache=True, cache_key=None,
285 config_variant=None, config_values=None, 267 config_variant=None, config_values=None,
286 debug=False, theme_site=False): 268 debug=False, theme_site=False):
292 self.debug = debug 274 self.debug = debug
293 self.theme_site = theme_site 275 self.theme_site = theme_site
294 276
295 def create(self): 277 def create(self):
296 app = PieCrust( 278 app = PieCrust(
297 self.root_dir, 279 self.root_dir,
298 cache=self.cache, 280 cache=self.cache,
299 cache_key=self.cache_key, 281 cache_key=self.cache_key,
300 debug=self.debug, 282 debug=self.debug,
301 theme_site=self.theme_site) 283 theme_site=self.theme_site)
302 apply_variant_and_values( 284 apply_variant_and_values(
303 app, self.config_variant, self.config_values) 285 app, self.config_variant, self.config_values)
304 return app 286 return app
305 287