comparison piecrust/serving.py @ 371:c2ca72fb7f0b 2.0.0a8

caching: Use separate caches for config variants and other contexts. * The `_cache` directory is now organized in multiple "sub-caches" for different contexts. * A new context is created when config variants or overrides are applied. * `serve` context uses a different context that the other commends, to prevent the `bake` command's output from messing up the preview server (e.g. with how asset URLs are generated differently between the two). * Fix a few places where the cache directory was referenced directly.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 03 May 2015 23:59:46 -0700
parents 4b1019bb2533
children
comparison
equal deleted inserted replaced
370:a1bbe66cba03 371:c2ca72fb7f0b
64 return self.server._run_request(environ, start_response) 64 return self.server._run_request(environ, start_response)
65 65
66 66
67 class Server(object): 67 class Server(object):
68 def __init__(self, root_dir, 68 def __init__(self, root_dir,
69 debug=False, use_reloader=False, static_preview=True): 69 debug=False, sub_cache_dir=None,
70 use_reloader=False, static_preview=True):
70 self.root_dir = root_dir 71 self.root_dir = root_dir
71 self.debug = debug 72 self.debug = debug
73 self.sub_cache_dir = sub_cache_dir
72 self.use_reloader = use_reloader 74 self.use_reloader = use_reloader
73 self.static_preview = static_preview 75 self.static_preview = static_preview
74 self._out_dir = None 76 self._out_dir = None
75 self._page_record = None 77 self._page_record = None
76 self._proc_loop = None 78 self._proc_loop = None
78 80
79 def getWsgiApp(self): 81 def getWsgiApp(self):
80 # Bake all the assets so we know what we have, and so we can serve 82 # Bake all the assets so we know what we have, and so we can serve
81 # them to the client. We need a temp app for this. 83 # them to the client. We need a temp app for this.
82 app = PieCrust(root_dir=self.root_dir, debug=self.debug) 84 app = PieCrust(root_dir=self.root_dir, debug=self.debug)
83 self._out_dir = os.path.join(app.cache_dir, 'server') 85 app._useSubCacheDir(self.sub_cache_dir)
86 self._out_dir = os.path.join(app.sub_cache_dir, 'server')
84 self._page_record = ServeRecord() 87 self._page_record = ServeRecord()
85 88
86 if (not self.use_reloader or 89 if (not self.use_reloader or
87 os.environ.get('WERKZEUG_RUN_MAIN') == 'true'): 90 os.environ.get('WERKZEUG_RUN_MAIN') == 'true'):
88 # We don't want to run the processing loop here if this isn't 91 # We don't want to run the processing loop here if this isn't
127 if response is not None: 130 if response is not None:
128 return response(environ, start_response) 131 return response(environ, start_response)
129 132
130 # Create the app for this request. 133 # Create the app for this request.
131 app = PieCrust(root_dir=self.root_dir, debug=self.debug) 134 app = PieCrust(root_dir=self.root_dir, debug=self.debug)
135 app._useSubCacheDir(self.sub_cache_dir)
132 app.config.set('site/root', '/') 136 app.config.set('site/root', '/')
133 app.config.set('server/is_serving', True) 137 app.config.set('server/is_serving', True)
134 if (app.config.get('site/enable_debug_info') and 138 if (app.config.get('site/enable_debug_info') and
135 '!debug' in request.args): 139 '!debug' in request.args):
136 app.config.set('site/show_debug_info', True) 140 app.config.set('site/show_debug_info', True)
224 228
225 routes = find_routes(app.routes, req_path) 229 routes = find_routes(app.routes, req_path)
226 if len(routes) == 0: 230 if len(routes) == 0:
227 raise RouteNotFoundError("Can't find route for: %s" % req_path) 231 raise RouteNotFoundError("Can't find route for: %s" % req_path)
228 232
229 taxonomy = None 233 rendered_page = None
230 tax_terms = None 234 first_not_found = None
231 for route, route_metadata in routes: 235 for route, route_metadata in routes:
232 source = app.getSource(route.source_name) 236 try:
233 if route.taxonomy_name is None: 237 logger.debug("Trying to render match from source '%s'." %
234 factory = source.findPageFactory(route_metadata, MODE_PARSING) 238 route.source_name)
235 if factory is not None: 239 rendered_page = self._try_render_page(
240 app, route, route_metadata, page_num, req_path)
241 if rendered_page is not None:
236 break 242 break
237 else: 243 except NotFound as nfe:
238 taxonomy = app.getTaxonomy(route.taxonomy_name) 244 if first_not_found is None:
239 route_terms = route_metadata.get(taxonomy.term_name) 245 first_not_found = nfe
240 if route_terms is not None:
241 tax_page_ref = taxonomy.getPageRef(source.name)
242 factory = tax_page_ref.getFactory()
243 tax_terms = route.unslugifyTaxonomyTerm(route_terms)
244 factory.metadata[taxonomy.term_name] = tax_terms
245 break
246 else: 246 else:
247 raise SourceNotFoundError( 247 raise SourceNotFoundError(
248 "Can't find path for: %s (looked in: %s)" % 248 "Can't find path for: %s (looked in: %s)" %
249 (req_path, [r.source_name for r, _ in routes])) 249 (req_path, [r.source_name for r, _ in routes]))
250 250
251 # Build the page. 251 # If we haven't found any good match, raise whatever exception we
252 page = factory.buildPage() 252 # first got. Otherwise, raise a generic exception.
253 # We force the rendering of the page because it could not have 253 if rendered_page is None:
254 # changed, but include pages that did change. 254 first_not_found = first_not_found or NotFound(
255 qp = QualifiedPage(page, route, route_metadata) 255 "This page couldn't be found.")
256 render_ctx = PageRenderingContext(qp, 256 raise first_not_found
257 page_num=page_num, 257
258 force_render=True) 258 # Start doing stuff.
259 if taxonomy is not None: 259 page = rendered_page.page
260 render_ctx.setTaxonomyFilter(taxonomy, tax_terms)
261
262 # See if this page is known to use sources. If that's the case,
263 # just don't use cached rendered segments for that page (but still
264 # use them for pages that are included in it).
265 entry = self._page_record.getEntry(req_path, page_num)
266 if (taxonomy is not None or entry is None or
267 entry.used_source_names):
268 cache_key = '%s:%s' % (req_path, page_num)
269 app.env.rendered_segments_repository.invalidate(cache_key)
270
271 # Render the page.
272 rendered_page = render_page(render_ctx)
273 rp_content = rendered_page.content 260 rp_content = rendered_page.content
274
275 if taxonomy is not None:
276 paginator = rendered_page.data.get('pagination')
277 if (paginator and paginator.is_loaded and
278 len(paginator.items) == 0):
279 message = ("This URL matched a route for taxonomy '%s' but "
280 "no pages have been found to have it. This page "
281 "won't be generated by a bake." % taxonomy.name)
282 raise NotFound(message)
283
284 if entry is None:
285 entry = ServeRecordPageEntry(req_path, page_num)
286 self._page_record.addEntry(entry)
287 for p, pinfo in render_ctx.render_passes.items():
288 entry.used_source_names |= pinfo.used_source_names
289 261
290 # Profiling. 262 # Profiling.
291 if app.config.get('site/show_debug_info'): 263 if app.config.get('site/show_debug_info'):
292 now_time = time.clock() 264 now_time = time.clock()
293 timing_info = ('%8.1f ms' % 265 timing_info = ('%8.1f ms' %
338 logger.exception("Error compressing response, " 310 logger.exception("Error compressing response, "
339 "falling back to uncompressed.") 311 "falling back to uncompressed.")
340 response.set_data(rp_content) 312 response.set_data(rp_content)
341 313
342 return response 314 return response
315
316 def _try_render_page(self, app, route, route_metadata, page_num, req_path):
317 # Match the route to an actual factory.
318 taxonomy_info = None
319 source = app.getSource(route.source_name)
320 if route.taxonomy_name is None:
321 factory = source.findPageFactory(route_metadata, MODE_PARSING)
322 if factory is None:
323 return None
324 else:
325 taxonomy = app.getTaxonomy(route.taxonomy_name)
326 route_terms = route_metadata.get(taxonomy.term_name)
327 if route_terms is None:
328 return None
329
330 tax_page_ref = taxonomy.getPageRef(source.name)
331 factory = tax_page_ref.getFactory()
332 tax_terms = route.unslugifyTaxonomyTerm(route_terms)
333 route_metadata[taxonomy.term_name] = tax_terms
334 taxonomy_info = (taxonomy, tax_terms)
335
336 # Build the page.
337 page = factory.buildPage()
338 # We force the rendering of the page because it could not have
339 # changed, but include pages that did change.
340 qp = QualifiedPage(page, route, route_metadata)
341 render_ctx = PageRenderingContext(qp,
342 page_num=page_num,
343 force_render=True)
344 if taxonomy_info is not None:
345 taxonomy, tax_terms = taxonomy_info
346 render_ctx.setTaxonomyFilter(taxonomy, tax_terms)
347
348 # See if this page is known to use sources. If that's the case,
349 # just don't use cached rendered segments for that page (but still
350 # use them for pages that are included in it).
351 uri = qp.getUri()
352 assert uri == req_path
353 entry = self._page_record.getEntry(uri, page_num)
354 if (taxonomy_info is not None or entry is None or
355 entry.used_source_names):
356 cache_key = '%s:%s' % (uri, page_num)
357 app.env.rendered_segments_repository.invalidate(cache_key)
358
359 # Render the page.
360 rendered_page = render_page(render_ctx)
361
362 # Check if this page is a taxonomy page that actually doesn't match
363 # anything.
364 if taxonomy_info is not None:
365 paginator = rendered_page.data.get('pagination')
366 if (paginator and paginator.is_loaded and
367 len(paginator.items) == 0):
368 taxonomy = taxonomy_info[0]
369 message = ("This URL matched a route for taxonomy '%s' but "
370 "no pages have been found to have it. This page "
371 "won't be generated by a bake." % taxonomy.name)
372 raise NotFound(message)
373
374 # Remember stuff for next time.
375 if entry is None:
376 entry = ServeRecordPageEntry(req_path, page_num)
377 self._page_record.addEntry(entry)
378 for p, pinfo in render_ctx.render_passes.items():
379 entry.used_source_names |= pinfo.used_source_names
380
381 # Ok all good.
382 return rendered_page
343 383
344 def _make_wrapped_file_response(self, environ, request, path): 384 def _make_wrapped_file_response(self, environ, request, path):
345 logger.debug("Serving %s" % path) 385 logger.debug("Serving %s" % path)
346 386
347 # Check if we can return a 304 status code. 387 # Check if we can return a 304 status code.