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: |