comparison piecrust/rendering.py @ 411:e7b865f8f335

bake: Enable multiprocess baking. Baking is now done by running a worker per CPU, and sending jobs to them. This changes several things across the codebase: * Ability to not cache things related to pages other than the 'main' page (i.e. the page at the bottom of the execution stack). * Decouple the baking process from the bake records, so only the main process keeps track (and modifies) the bake record. * Remove the need for 'batch page getters' and loading a page directly from the page factories. There are various smaller changes too included here, including support for scope performance timers that are saved with the bake record and can be printed out to the console. Yes I got carried away. For testing, the in-memory 'mock' file-system doesn't work anymore, since we're spawning processes, so this is replaced by a 'tmpfs' file-system which is saved in temporary files on disk and deleted after tests have run.
author Ludovic Chabant <ludovic@chabant.com>
date Fri, 12 Jun 2015 17:09:19 -0700
parents 4b1019bb2533
children 0e9a94b7fdfa
comparison
equal deleted inserted replaced
410:d1a472464e57 411:e7b865f8f335
1 import re 1 import re
2 import os.path 2 import os.path
3 import logging 3 import logging
4 from werkzeug.utils import cached_property 4 from werkzeug.utils import cached_property
5 from piecrust.data.builder import (DataBuildingContext, build_page_data, 5 from piecrust.data.builder import (
6 build_layout_data) 6 DataBuildingContext, build_page_data, build_layout_data)
7 from piecrust.data.filters import ( 7 from piecrust.data.filters import (
8 PaginationFilter, HasFilterClause, IsFilterClause, AndBooleanClause, 8 PaginationFilter, HasFilterClause, IsFilterClause, AndBooleanClause,
9 page_value_accessor) 9 page_value_accessor)
10 from piecrust.sources.base import PageSource 10 from piecrust.sources.base import PageSource
11 from piecrust.templating.base import TemplateNotFoundError, TemplatingError 11 from piecrust.templating.base import TemplateNotFoundError, TemplatingError
90 return self.page.source_metadata 90 return self.page.source_metadata
91 91
92 @cached_property 92 @cached_property
93 def uri(self): 93 def uri(self):
94 return self.page.getUri(self.page_num) 94 return self.page.getUri(self.page_num)
95
96 @property
97 def pagination_has_more(self):
98 if self.used_pagination is None:
99 return False
100 return self.used_pagination.has_more
95 101
96 @property 102 @property
97 def current_pass_info(self): 103 def current_pass_info(self):
98 return self.render_passes.get(self._current_pass) 104 return self.render_passes.get(self._current_pass)
99 105
155 page_data.update(ctx.custom_data) 161 page_data.update(ctx.custom_data)
156 162
157 # Render content segments. 163 # Render content segments.
158 ctx.setCurrentPass(PASS_FORMATTING) 164 ctx.setCurrentPass(PASS_FORMATTING)
159 repo = ctx.app.env.rendered_segments_repository 165 repo = ctx.app.env.rendered_segments_repository
166 save_to_fs = True
167 if ctx.app.env.fs_cache_only_for_main_page and not eis.is_main_page:
168 save_to_fs = False
160 if repo and not ctx.force_render: 169 if repo and not ctx.force_render:
161 cache_key = ctx.uri
162 page_time = page.path_mtime
163 contents = repo.get( 170 contents = repo.get(
164 cache_key, 171 ctx.uri,
165 lambda: _do_render_page_segments(page, page_data), 172 lambda: _do_render_page_segments(page, page_data),
166 fs_cache_time=page_time) 173 fs_cache_time=page.path_mtime,
174 save_to_fs=save_to_fs)
167 else: 175 else:
168 contents = _do_render_page_segments(page, page_data) 176 contents = _do_render_page_segments(page, page_data)
177 if repo:
178 repo.put(ctx.uri, contents, save_to_fs)
169 179
170 # Render layout. 180 # Render layout.
171 ctx.setCurrentPass(PASS_RENDERING) 181 ctx.setCurrentPass(PASS_RENDERING)
172 layout_name = page.config.get('layout') 182 layout_name = page.config.get('layout')
173 if layout_name is None: 183 if layout_name is None:
224 for seg_name, seg in page.raw_content.items(): 234 for seg_name, seg in page.raw_content.items():
225 seg_text = '' 235 seg_text = ''
226 for seg_part in seg.parts: 236 for seg_part in seg.parts:
227 part_format = seg_part.fmt or format_name 237 part_format = seg_part.fmt or format_name
228 try: 238 try:
229 part_text = engine.renderString( 239 with app.env.timerScope(engine.__class__.__name__):
230 seg_part.content, page_data, 240 part_text = engine.renderString(
231 filename=page.path) 241 seg_part.content, page_data,
242 filename=page.path)
232 except TemplatingError as err: 243 except TemplatingError as err:
233 err.lineno += seg_part.line 244 err.lineno += seg_part.line
234 raise err 245 raise err
235 246
236 part_text = format_text(app, part_format, part_text) 247 part_text = format_text(app, part_format, part_text)
289 300
290 format_count = 0 301 format_count = 0
291 format_name = format_name or app.config.get('site/default_format') 302 format_name = format_name or app.config.get('site/default_format')
292 for fmt in app.plugin_loader.getFormatters(): 303 for fmt in app.plugin_loader.getFormatters():
293 if fmt.FORMAT_NAMES is None or format_name in fmt.FORMAT_NAMES: 304 if fmt.FORMAT_NAMES is None or format_name in fmt.FORMAT_NAMES:
294 txt = fmt.render(format_name, txt) 305 with app.env.timerScope(fmt.__class__.__name__):
306 txt = fmt.render(format_name, txt)
295 format_count += 1 307 format_count += 1
296 if fmt.OUTPUT_FORMAT is not None: 308 if fmt.OUTPUT_FORMAT is not None:
297 format_name = fmt.OUTPUT_FORMAT 309 format_name = fmt.OUTPUT_FORMAT
298 if exact_format and format_count == 0: 310 if exact_format and format_count == 0:
299 raise Exception("No such format: %s" % format_name) 311 raise Exception("No such format: %s" % format_name)