Mercurial > piecrust2
comparison piecrust/rendering.py @ 854:08e02c2a2a1a
core: Keep refactoring, this time to prepare for generator sources.
- Make a few APIs simpler.
- Content pipelines create their own jobs, so that generator sources can
keep aborting in `getContents`, but rely on their pipeline to generate
pages for baking.
| author | Ludovic Chabant <ludovic@chabant.com> |
|---|---|
| date | Sun, 04 Jun 2017 23:34:28 -0700 |
| parents | f070a4fc033c |
| children | 58e28ba02fb7 |
comparison
equal
deleted
inserted
replaced
| 853:f070a4fc033c | 854:08e02c2a2a1a |
|---|---|
| 3 import copy | 3 import copy |
| 4 import logging | 4 import logging |
| 5 from piecrust.data.builder import ( | 5 from piecrust.data.builder import ( |
| 6 DataBuildingContext, build_page_data, add_layout_data) | 6 DataBuildingContext, build_page_data, add_layout_data) |
| 7 from piecrust.fastpickle import _pickle_object, _unpickle_object | 7 from piecrust.fastpickle import _pickle_object, _unpickle_object |
| 8 from piecrust.sources.base import ContentSource | |
| 9 from piecrust.templating.base import TemplateNotFoundError, TemplatingError | 8 from piecrust.templating.base import TemplateNotFoundError, TemplatingError |
| 10 | 9 |
| 11 | 10 |
| 12 logger = logging.getLogger(__name__) | 11 logger = logging.getLogger(__name__) |
| 13 | 12 |
| 113 pass_info.pagination_has_more = paginator.has_more | 112 pass_info.pagination_has_more = paginator.has_more |
| 114 self.addUsedSource(paginator._source) | 113 self.addUsedSource(paginator._source) |
| 115 | 114 |
| 116 def addUsedSource(self, source): | 115 def addUsedSource(self, source): |
| 117 self._raiseIfNoCurrentPass() | 116 self._raiseIfNoCurrentPass() |
| 118 if isinstance(source, ContentSource): | 117 pass_info = self.current_pass_info |
| 119 pass_info = self.current_pass_info | 118 pass_info.used_source_names.add(source.name) |
| 120 pass_info.used_source_names.add(source.name) | |
| 121 | 119 |
| 122 def _raiseIfNoCurrentPass(self): | 120 def _raiseIfNoCurrentPass(self): |
| 123 if self._current_pass == PASS_NONE: | 121 if self._current_pass == PASS_NONE: |
| 124 raise Exception("No rendering pass is currently active.") | 122 raise Exception("No rendering pass is currently active.") |
| 125 | 123 |
| 157 self._ctx_stack = [] | 155 self._ctx_stack = [] |
| 158 | 156 |
| 159 | 157 |
| 160 def render_page(ctx): | 158 def render_page(ctx): |
| 161 env = ctx.app.env | 159 env = ctx.app.env |
| 160 stats = env.stats | |
| 162 | 161 |
| 163 stack = env.render_ctx_stack | 162 stack = env.render_ctx_stack |
| 164 stack.pushCtx(ctx) | 163 stack.pushCtx(ctx) |
| 165 | 164 |
| 166 page = ctx.page | 165 page = ctx.page |
| 167 page_uri = page.getUri(ctx.sub_num) | 166 page_uri = page.getUri(ctx.sub_num) |
| 168 | 167 |
| 169 try: | 168 try: |
| 170 # Build the data for both segment and layout rendering. | 169 # Build the data for both segment and layout rendering. |
| 171 with env.timerScope("BuildRenderData"): | 170 with stats.timerScope("BuildRenderData"): |
| 172 page_data = _build_render_data(ctx) | 171 page_data = _build_render_data(ctx) |
| 173 | 172 |
| 174 # Render content segments. | 173 # Render content segments. |
| 175 ctx.setCurrentPass(PASS_FORMATTING) | 174 ctx.setCurrentPass(PASS_FORMATTING) |
| 176 repo = env.rendered_segments_repository | 175 repo = env.rendered_segments_repository |
| 177 save_to_fs = True | 176 save_to_fs = True |
| 178 if env.fs_cache_only_for_main_page and not stack.is_main_ctx: | 177 if env.fs_cache_only_for_main_page and not stack.is_main_ctx: |
| 179 save_to_fs = False | 178 save_to_fs = False |
| 180 with env.timerScope("PageRenderSegments"): | 179 with stats.timerScope("PageRenderSegments"): |
| 181 if repo is not None and not ctx.force_render: | 180 if repo is not None and not ctx.force_render: |
| 182 render_result = repo.get( | 181 render_result = repo.get( |
| 183 page_uri, | 182 page_uri, |
| 184 lambda: _do_render_page_segments(ctx, page_data), | 183 lambda: _do_render_page_segments(ctx, page_data), |
| 185 fs_cache_time=page.content_mtime, | 184 fs_cache_time=page.content_mtime, |
| 195 if layout_name is None: | 194 if layout_name is None: |
| 196 layout_name = page.source.config.get( | 195 layout_name = page.source.config.get( |
| 197 'default_layout', 'default') | 196 'default_layout', 'default') |
| 198 null_names = ['', 'none', 'nil'] | 197 null_names = ['', 'none', 'nil'] |
| 199 if layout_name not in null_names: | 198 if layout_name not in null_names: |
| 200 with ctx.app.env.timerScope("BuildRenderData"): | 199 with stats.timerScope("BuildRenderData"): |
| 201 add_layout_data(page_data, render_result['segments']) | 200 add_layout_data(page_data, render_result['segments']) |
| 202 | 201 |
| 203 with ctx.app.env.timerScope("PageRenderLayout"): | 202 with stats.timerScope("PageRenderLayout"): |
| 204 layout_result = _do_render_layout( | 203 layout_result = _do_render_layout( |
| 205 layout_name, page, page_data) | 204 layout_name, page, page_data) |
| 206 else: | 205 else: |
| 207 layout_result = { | 206 layout_result = { |
| 208 'content': render_result['segments']['content'], | 207 'content': render_result['segments']['content'], |
| 220 | 219 |
| 221 except Exception as ex: | 220 except Exception as ex: |
| 222 if ctx.app.debug: | 221 if ctx.app.debug: |
| 223 raise | 222 raise |
| 224 logger.exception(ex) | 223 logger.exception(ex) |
| 225 page_rel_path = os.path.relpath(ctx.page.path, ctx.app.root_dir) | 224 raise Exception("Error rendering page: %s" % |
| 226 raise Exception("Error rendering page: %s" % page_rel_path) from ex | 225 ctx.page.content_spec) from ex |
| 227 | 226 |
| 228 finally: | 227 finally: |
| 229 ctx.setCurrentPass(PASS_NONE) | 228 ctx.setCurrentPass(PASS_NONE) |
| 230 stack.popCtx() | 229 stack.popCtx() |
| 231 | 230 |
| 232 | 231 |
| 233 def render_page_segments(ctx): | 232 def render_page_segments(ctx): |
| 234 env = ctx.app.env | 233 env = ctx.app.env |
| 234 stats = env.stats | |
| 235 | 235 |
| 236 stack = env.render_ctx_stack | 236 stack = env.render_ctx_stack |
| 237 stack.pushCtx(ctx) | 237 stack.pushCtx(ctx) |
| 238 | 238 |
| 239 page = ctx.page | 239 page = ctx.page |
| 240 page_uri = page.getUri(ctx.sub_num) | 240 page_uri = page.getUri(ctx.sub_num) |
| 241 | 241 |
| 242 try: | 242 try: |
| 243 ctx.setCurrentPass(PASS_FORMATTING) | 243 ctx.setCurrentPass(PASS_FORMATTING) |
| 244 repo = ctx.app.env.rendered_segments_repository | 244 repo = env.rendered_segments_repository |
| 245 | |
| 245 save_to_fs = True | 246 save_to_fs = True |
| 246 if ctx.app.env.fs_cache_only_for_main_page and not stack.is_main_ctx: | 247 if env.fs_cache_only_for_main_page and not stack.is_main_ctx: |
| 247 save_to_fs = False | 248 save_to_fs = False |
| 248 with ctx.app.env.timerScope("PageRenderSegments"): | 249 |
| 250 with stats.timerScope("PageRenderSegments"): | |
| 249 if repo is not None and not ctx.force_render: | 251 if repo is not None and not ctx.force_render: |
| 250 render_result = repo.get( | 252 render_result = repo.get( |
| 251 page_uri, | 253 page_uri, |
| 252 lambda: _do_render_page_segments_from_ctx(ctx), | 254 lambda: _do_render_page_segments_from_ctx(ctx), |
| 253 fs_cache_time=page.content_mtime, | 255 fs_cache_time=page.content_mtime, |
| 265 _unpickle_object(render_result['pass_info'])) | 267 _unpickle_object(render_result['pass_info'])) |
| 266 return rs | 268 return rs |
| 267 | 269 |
| 268 | 270 |
| 269 def _build_render_data(ctx): | 271 def _build_render_data(ctx): |
| 270 with ctx.app.env.timerScope("PageDataBuild"): | 272 with ctx.app.env.stats.timerScope("PageDataBuild"): |
| 271 data_ctx = DataBuildingContext(ctx.page, ctx.sub_num) | 273 data_ctx = DataBuildingContext(ctx.page, ctx.sub_num) |
| 272 data_ctx.pagination_source = ctx.pagination_source | 274 data_ctx.pagination_source = ctx.pagination_source |
| 273 data_ctx.pagination_filter = ctx.pagination_filter | 275 data_ctx.pagination_filter = ctx.pagination_filter |
| 274 page_data = build_page_data(data_ctx) | 276 page_data = build_page_data(data_ctx) |
| 275 if ctx.custom_data: | 277 if ctx.custom_data: |
| 295 for seg_name, seg in page.segments.items(): | 297 for seg_name, seg in page.segments.items(): |
| 296 seg_text = '' | 298 seg_text = '' |
| 297 for seg_part in seg.parts: | 299 for seg_part in seg.parts: |
| 298 part_format = seg_part.fmt or format_name | 300 part_format = seg_part.fmt or format_name |
| 299 try: | 301 try: |
| 300 with app.env.timerScope( | 302 with app.env.stats.timerScope( |
| 301 engine.__class__.__name__ + '_segment'): | 303 engine.__class__.__name__ + '_segment'): |
| 302 part_text = engine.renderSegmentPart( | 304 part_text = engine.renderSegmentPart( |
| 303 page.path, seg_part, page_data) | 305 page.content_spec, seg_part, page_data) |
| 304 except TemplatingError as err: | 306 except TemplatingError as err: |
| 305 err.lineno += seg_part.line | 307 err.lineno += seg_part.line |
| 306 raise err | 308 raise err |
| 307 | 309 |
| 308 part_text = format_text(app, part_format, part_text) | 310 part_text = format_text(app, part_format, part_text) |
| 322 'pass_info': _pickle_object(pass_info)} | 324 'pass_info': _pickle_object(pass_info)} |
| 323 return res | 325 return res |
| 324 | 326 |
| 325 | 327 |
| 326 def _do_render_layout(layout_name, page, layout_data): | 328 def _do_render_layout(layout_name, page, layout_data): |
| 327 cpi = page.app.env.exec_info_stack.current_page_info | 329 cur_ctx = page.app.env.render_ctx_stack.current_ctx |
| 328 assert cpi is not None | 330 assert cur_ctx is not None |
| 329 assert cpi.page == page | 331 assert cur_ctx.page == page |
| 330 | 332 |
| 331 names = layout_name.split(',') | 333 names = layout_name.split(',') |
| 332 default_exts = page.app.env.default_layout_extensions | |
| 333 full_names = [] | 334 full_names = [] |
| 334 for name in names: | 335 for name in names: |
| 335 if '.' not in name: | 336 if '.' not in name: |
| 336 for ext in default_exts: | 337 full_names.append(name + '.html') |
| 337 full_names.append(name + ext) | |
| 338 else: | 338 else: |
| 339 full_names.append(name) | 339 full_names.append(name) |
| 340 | 340 |
| 341 _, engine_name = os.path.splitext(full_names[0]) | 341 _, engine_name = os.path.splitext(full_names[0]) |
| 342 engine_name = engine_name.lstrip('.') | 342 engine_name = engine_name.lstrip('.') |
| 343 engine = get_template_engine(page.app, engine_name) | 343 engine = get_template_engine(page.app, engine_name) |
| 344 | 344 |
| 345 try: | 345 try: |
| 346 with page.app.env.timerScope(engine.__class__.__name__ + '_layout'): | 346 with page.app.env.stats.timerScope( |
| 347 engine.__class__.__name__ + '_layout'): | |
| 347 output = engine.renderFile(full_names, layout_data) | 348 output = engine.renderFile(full_names, layout_data) |
| 348 except TemplateNotFoundError as ex: | 349 except TemplateNotFoundError as ex: |
| 349 logger.exception(ex) | 350 logger.exception(ex) |
| 350 msg = "Can't find template for page: %s\n" % page.path | 351 msg = "Can't find template for page: %s\n" % page.path |
| 351 msg += "Looked for: %s" % ', '.join(full_names) | 352 msg += "Looked for: %s" % ', '.join(full_names) |
| 352 raise Exception(msg) from ex | 353 raise Exception(msg) from ex |
| 353 | 354 |
| 354 pass_info = cpi.render_ctx.render_passes[PASS_RENDERING] | 355 pass_info = cur_ctx.render_passes[PASS_RENDERING] |
| 355 res = {'content': output, 'pass_info': _pickle_object(pass_info)} | 356 res = {'content': output, 'pass_info': _pickle_object(pass_info)} |
| 356 return res | 357 return res |
| 357 | 358 |
| 358 | 359 |
| 359 def get_template_engine(app, engine_name): | 360 def get_template_engine(app, engine_name): |
| 374 format_name = format_name or app.config.get('site/default_format') | 375 format_name = format_name or app.config.get('site/default_format') |
| 375 for fmt in app.plugin_loader.getFormatters(): | 376 for fmt in app.plugin_loader.getFormatters(): |
| 376 if not fmt.enabled: | 377 if not fmt.enabled: |
| 377 continue | 378 continue |
| 378 if fmt.FORMAT_NAMES is None or format_name in fmt.FORMAT_NAMES: | 379 if fmt.FORMAT_NAMES is None or format_name in fmt.FORMAT_NAMES: |
| 379 with app.env.timerScope(fmt.__class__.__name__): | 380 with app.env.stats.timerScope(fmt.__class__.__name__): |
| 380 txt = fmt.render(format_name, txt) | 381 txt = fmt.render(format_name, txt) |
| 381 format_count += 1 | 382 format_count += 1 |
| 382 if fmt.OUTPUT_FORMAT is not None: | 383 if fmt.OUTPUT_FORMAT is not None: |
| 383 format_name = fmt.OUTPUT_FORMAT | 384 format_name = fmt.OUTPUT_FORMAT |
| 384 if exact_format and format_count == 0: | 385 if exact_format and format_count == 0: |
