Mercurial > piecrust2
diff piecrust/rendering.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 | dca51cd8147a |
children | f070a4fc033c |
line wrap: on
line diff
--- a/piecrust/rendering.py Sat Apr 29 21:42:22 2017 -0700 +++ b/piecrust/rendering.py Wed May 17 00:11:48 2017 -0700 @@ -2,13 +2,10 @@ import os.path import copy import logging -from werkzeug.utils import cached_property from piecrust.data.builder import ( - DataBuildingContext, build_page_data, build_layout_data) -from piecrust.data.filters import ( - PaginationFilter, SettingFilterClause, page_value_accessor) + DataBuildingContext, build_page_data, add_layout_data) from piecrust.fastpickle import _pickle_object, _unpickle_object -from piecrust.sources.base import PageSource +from piecrust.sources.base import ContentSource from piecrust.templating.base import TemplateNotFoundError, TemplatingError @@ -19,7 +16,7 @@ re.MULTILINE) -class PageRenderingError(Exception): +class RenderingError(Exception): pass @@ -27,19 +24,6 @@ pass -class QualifiedPage(object): - def __init__(self, page, route, route_metadata): - self.page = page - self.route = route - self.route_metadata = route_metadata - - def getUri(self, sub_num=1): - return self.route.getUri(self.route_metadata, sub_num=sub_num) - - def __getattr__(self, name): - return getattr(self.page, name) - - class RenderedSegments(object): def __init__(self, segments, render_pass_info): self.segments = segments @@ -53,17 +37,15 @@ class RenderedPage(object): - def __init__(self, page, uri, num=1): - self.page = page - self.uri = uri - self.num = num + def __init__(self, qualified_page): + self.qualified_page = qualified_page self.data = None self.content = None self.render_info = [None, None] @property def app(self): - return self.page.app + return self.qualified_page.app def copyRenderInfo(self): return copy.deepcopy(self.render_info) @@ -94,13 +76,10 @@ return self._custom_info.get(key, default) -class PageRenderingContext(object): - def __init__(self, qualified_page, page_num=1, - force_render=False, is_from_request=False): - self.page = qualified_page - self.page_num = page_num +class RenderingContext(object): + def __init__(self, qualified_page, force_render=False): + self.qualified_page = qualified_page self.force_render = force_render - self.is_from_request = is_from_request self.pagination_source = None self.pagination_filter = None self.custom_data = {} @@ -109,15 +88,7 @@ @property def app(self): - return self.page.app - - @property - def source_metadata(self): - return self.page.source_metadata - - @cached_property - def uri(self): - return self.page.getUri(self.page_num) + return self.qualified_page.app @property def current_pass_info(self): @@ -142,7 +113,7 @@ def addUsedSource(self, source): self._raiseIfNoCurrentPass() - if isinstance(source, PageSource): + if isinstance(source, ContentSource): pass_info = self.current_pass_info pass_info.used_source_names.add(source.name) @@ -151,103 +122,149 @@ raise Exception("No rendering pass is currently active.") +class RenderingContextStack(object): + def __init__(self): + self._ctx_stack = [] + + @property + def current_ctx(self): + if len(self._ctx_stack) == 0: + return None + return self._ctx_stack[-1] + + @property + def is_main_ctx(self): + return len(self._ctx_stack) == 1 + + def hasPage(self, page): + for ei in self._ctx_stack: + if ei.qualified_page.page == page: + return True + return False + + def pushCtx(self, render_ctx): + for ctx in self._ctx_stack: + if ctx.qualified_page.page == render_ctx.qualified_page.page: + raise Exception("Loop detected during rendering!") + self._ctx_stack.append(render_ctx) + + def popCtx(self): + del self._ctx_stack[-1] + + def clear(self): + self._ctx_stack = [] + + def render_page(ctx): - eis = ctx.app.env.exec_info_stack - eis.pushPage(ctx.page, ctx) + env = ctx.app.env + + stack = env.render_ctx_stack + stack.pushCtx(ctx) + + qpage = ctx.qualified_page + try: # Build the data for both segment and layout rendering. - with ctx.app.env.timerScope("BuildRenderData"): + with env.timerScope("BuildRenderData"): page_data = _build_render_data(ctx) # Render content segments. ctx.setCurrentPass(PASS_FORMATTING) - repo = ctx.app.env.rendered_segments_repository + repo = env.rendered_segments_repository save_to_fs = True - if ctx.app.env.fs_cache_only_for_main_page and not eis.is_main_page: + if env.fs_cache_only_for_main_page and not stack.is_main_ctx: save_to_fs = False - with ctx.app.env.timerScope("PageRenderSegments"): - if repo and not ctx.force_render: + with env.timerScope("PageRenderSegments"): + if repo is not None and not ctx.force_render: render_result = repo.get( - ctx.uri, - lambda: _do_render_page_segments(ctx.page, page_data), - fs_cache_time=ctx.page.path_mtime, - save_to_fs=save_to_fs) + qpage.uri, + lambda: _do_render_page_segments(ctx, page_data), + fs_cache_time=qpage.page.content_mtime, + save_to_fs=save_to_fs) else: - render_result = _do_render_page_segments(ctx.page, page_data) + render_result = _do_render_page_segments(ctx, page_data) if repo: - repo.put(ctx.uri, render_result, save_to_fs) + repo.put(qpage.uri, render_result, save_to_fs) # Render layout. - page = ctx.page ctx.setCurrentPass(PASS_RENDERING) - layout_name = page.config.get('layout') + layout_name = qpage.page.config.get('layout') if layout_name is None: - layout_name = page.source.config.get('default_layout', 'default') + layout_name = qpage.page.source.config.get( + 'default_layout', 'default') null_names = ['', 'none', 'nil'] if layout_name not in null_names: with ctx.app.env.timerScope("BuildRenderData"): - build_layout_data(page, page_data, render_result['segments']) + add_layout_data(page_data, render_result['segments']) with ctx.app.env.timerScope("PageRenderLayout"): - layout_result = _do_render_layout(layout_name, page, page_data) + layout_result = _do_render_layout( + layout_name, qpage, page_data) else: layout_result = { - 'content': render_result['segments']['content'], - 'pass_info': None} + 'content': render_result['segments']['content'], + 'pass_info': None} - rp = RenderedPage(page, ctx.uri, ctx.page_num) + rp = RenderedPage(qpage) rp.data = page_data rp.content = layout_result['content'] rp.render_info[PASS_FORMATTING] = _unpickle_object( - render_result['pass_info']) + render_result['pass_info']) if layout_result['pass_info'] is not None: rp.render_info[PASS_RENDERING] = _unpickle_object( - layout_result['pass_info']) + layout_result['pass_info']) return rp + except Exception as ex: if ctx.app.debug: raise logger.exception(ex) page_rel_path = os.path.relpath(ctx.page.path, ctx.app.root_dir) raise Exception("Error rendering page: %s" % page_rel_path) from ex + finally: ctx.setCurrentPass(PASS_NONE) - eis.popPage() + stack.popCtx() def render_page_segments(ctx): - eis = ctx.app.env.exec_info_stack - eis.pushPage(ctx.page, ctx) + env = ctx.app.env + + stack = env.render_ctx_stack + stack.pushCtx(ctx) + + qpage = ctx.qualified_page + try: ctx.setCurrentPass(PASS_FORMATTING) repo = ctx.app.env.rendered_segments_repository save_to_fs = True - if ctx.app.env.fs_cache_only_for_main_page and not eis.is_main_page: + if ctx.app.env.fs_cache_only_for_main_page and not stack.is_main_ctx: save_to_fs = False with ctx.app.env.timerScope("PageRenderSegments"): - if repo and not ctx.force_render: + if repo is not None and not ctx.force_render: render_result = repo.get( - ctx.uri, + qpage.uri, lambda: _do_render_page_segments_from_ctx(ctx), - fs_cache_time=ctx.page.path_mtime, + fs_cache_time=qpage.page.content_mtime, save_to_fs=save_to_fs) else: render_result = _do_render_page_segments_from_ctx(ctx) if repo: - repo.put(ctx.uri, render_result, save_to_fs) + repo.put(qpage.uri, render_result, save_to_fs) finally: ctx.setCurrentPass(PASS_NONE) - eis.popPage() + stack.popCtx() rs = RenderedSegments( - render_result['segments'], - _unpickle_object(render_result['pass_info'])) + render_result['segments'], + _unpickle_object(render_result['pass_info'])) return rs def _build_render_data(ctx): with ctx.app.env.timerScope("PageDataBuild"): - data_ctx = DataBuildingContext(ctx.page, page_num=ctx.page_num) + data_ctx = DataBuildingContext(ctx.qualified_page) data_ctx.pagination_source = ctx.pagination_source data_ctx.pagination_filter = ctx.pagination_filter page_data = build_page_data(data_ctx) @@ -258,16 +275,13 @@ def _do_render_page_segments_from_ctx(ctx): page_data = _build_render_data(ctx) - return _do_render_page_segments(ctx.page, page_data) + return _do_render_page_segments(ctx, page_data) -def _do_render_page_segments(page, page_data): +def _do_render_page_segments(ctx, page_data): + page = ctx.qualified_page.page app = page.app - cpi = app.env.exec_info_stack.current_page_info - assert cpi is not None - assert cpi.page == page - engine_name = page.config.get('template_engine') format_name = page.config.get('format') @@ -282,7 +296,7 @@ with app.env.timerScope( engine.__class__.__name__ + '_segment'): part_text = engine.renderSegmentPart( - page.path, seg_part, page_data) + page.path, seg_part, page_data) except TemplatingError as err: err.lineno += seg_part.line raise err @@ -298,10 +312,10 @@ content_abstract = seg_text[:offset] formatted_segments['content.abstract'] = content_abstract - pass_info = cpi.render_ctx.render_passes[PASS_FORMATTING] + pass_info = ctx.render_passes[PASS_FORMATTING] res = { - 'segments': formatted_segments, - 'pass_info': _pickle_object(pass_info)} + 'segments': formatted_segments, + 'pass_info': _pickle_object(pass_info)} return res