Mercurial > piecrust2
view piecrust/templating/inukshukengine.py @ 1188:a7c43131d871
bake: Fix file write flushing problem with Python 3.8+
Writing the cache files fails in Python 3.8 because it looks like flushing
behaviour has changed. We need to explicitly flush. And even then, in very
rare occurrences, it looks like it can still run into racing conditions,
so we do a very hacky and ugly "retry" loop when fetching cached data :(
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Tue, 15 Jun 2021 22:36:23 -0700 |
parents | 6370ab74b2d5 |
children |
line wrap: on
line source
import io import os.path import time import logging from inukshuk.parser import ParserError from piecrust.templating.base import ( TemplateEngine, TemplatingError, TemplateNotFoundError) logger = logging.getLogger(__name__) _profile = False class InukshukTemplateEngine(TemplateEngine): ENGINE_NAMES = ['inukshuk', 'inuk'] EXTENSIONS = ['html', 'inuk'] def __init__(self): self.engine = None self.pc_cache = {} self._seg_loader = None self._buf = io.StringIO() self._buf.truncate(2048) if _profile: self._renderTemplate = self._renderTemplateProf else: self._renderTemplate = self._renderTemplateNoProf def populateCache(self): self._ensureLoaded() used_names = set() def _filter_names(name): if name in used_names: return False used_names.add(name) return True self.engine.cacheAllTemplates(cache_condition=_filter_names) def renderSegment(self, path, segment, data): if not _string_needs_render(segment.content): return segment.content, False self._ensureLoaded() tpl_name = os.path.relpath(path, self.app.root_dir) try: self._seg_loader.templates[tpl_name] = segment.content tpl = self.engine.getTemplate(tpl_name, memmodule=True) return self._renderTemplate(tpl, data), True except ParserError as pe: raise TemplatingError(pe.message, path, pe.line_num) def renderFile(self, paths, data): self._ensureLoaded() tpl = None rendered_path = None for p in paths: try: tpl = self.engine.getTemplate(p) rendered_path = p break except Exception: pass if tpl is None: raise TemplateNotFoundError() try: return self._renderTemplate(tpl, data) except ParserError as pe: raise TemplatingError(pe.message, rendered_path, pe.line_num) def _renderTemplateNoProf(self, tpl, data): return tpl.render(data) def _renderTemplateIntoNoProf(self, tpl, data): buf = self._buf buf.seek(0) tpl.renderInto(data, buf) buf.flush() size = buf.tell() buf.seek(0) return buf.read(size) def _renderTemplateProf(self, tpl, data): stats = self.app.env.stats # Code copied from Inukshuk, but with an `out_write` method that # wraps a timer scope. out = [] def out_write(s): start = time.perf_counter() out.append(s) stats.stepTimerSince('Inukshuk_outWrite', start) tpl._renderWithContext(None, data, out_write) return ''.join(out) def _ensureLoaded(self): if self.engine is not None: return from inukshuk.engine import Engine from inukshuk.loader import ( StringsLoader, FileSystemLoader, CompositeLoader) from ._inukshukext import PieCrustExtension self._seg_loader = StringsLoader() loader = CompositeLoader([ self._seg_loader, FileSystemLoader(self.app.templates_dirs)]) self.engine = Engine(loader) self.engine.autoescape = True self.engine.extensions.append(PieCrustExtension(self.app)) self.engine.compile_templates = True self.engine.compile_cache_dir = os.path.join( self.app.cache_dir, 'inuk') if _profile: # If we're profiling, monkeypatch all the appropriate methods # from the Inukshuk API. stats = self.app.env.stats import inukshuk.rendering afe = inukshuk.rendering._attr_first_access def wafe(ctx, data, prop_name): with stats.timerScope('Inukshuk_query'): return afe(ctx, data, prop_name) inukshuk.rendering._attr_first_access = wafe afer = inukshuk.rendering._attr_first_access_root def wafer(ctx, ctx_locals, data, ctx_globals, prop_name): with stats.timerScope('Inukshuk_query'): return afer(ctx, ctx_locals, data, ctx_globals, prop_name) inukshuk.rendering._attr_first_access_root = wafer i = inukshuk.rendering.RenderContext.invoke def wi(ctx, data, out, data_func, *args, **kwargs): with stats.timerScope('Inukshuk_invoke'): return i(ctx, data, out, data_func, *args, **kwargs) inukshuk.rendering.RenderContext.invoke = wi import inukshuk.template cc = inukshuk.template.Template._compileContent def wcc(tpl, force_compiled=False): with stats.timerScope('Inukshuk_templateCompileContent'): return cc(tpl, force_compiled) inukshuk.template.Template._compileContent = wcc dr = inukshuk.template.Template._doRender def wdr(tpl, ctx, data, out): with stats.timerScope('Inukshuk_templateDoRender'): return dr(tpl, ctx, data, out) inukshuk.template.Template._doRender = wdr stats.registerTimer('Inukshuk_query') stats.registerTimer('Inukshuk_invoke') stats.registerTimer('Inukshuk_templateDoRender') stats.registerTimer('Inukshuk_templateCompileContent') stats.registerTimer('Inukshuk_outWrite') try: os.makedirs(self.engine.compile_cache_dir) except OSError: pass def _string_needs_render(txt): index = txt.find('{') while index >= 0: ch = txt[index + 1] if ch == '{' or ch == '%': return True index = txt.find('{', index + 1) return False