Speeding up pystache

This unfortunately won’t do much good as a PR because the project seems to be abandoned but you can get really big speedups in pystache by caching templates and (particularly) parsed templates. Especially if you are using a lot of partials.

It looks something like this:

# Subclass pystache.Renderer to provide our custom caching versions of pystache classes for performance reasons.
class CachedRenderer(pystache.Renderer):
    def _make_loader(self) -> pystache.loader.Loader:
        return CachedLoader(file_encoding=self.file_encoding, extension=self.file_extension, to_unicode=self.str, search_dirs=self.search_dirs)

    def _make_render_engine(self) -> pystache.renderengine.RenderEngine:
        resolve_context = self._make_resolve_context()
        resolve_partial = self._make_resolve_partial()
        engine = CachedRenderEngine(literal=self._to_unicode_hard, escape=self._escape_to_unicode, resolve_context=resolve_context, resolve_partial=resolve_partial, to_str=self.str_coerce)
        return engine

# A custom loader that acts exactly as the default loader but only loads a given file once to speed up repeated use of partials.
# This will stop us loading record.mustache from disk 16,000 times on /cards/ for example.
class CachedLoader(pystache.loader.Loader):
    def __init__(self, file_encoding: Optional[str] = None, extension: Optional[Union[str, bool]] = None, to_unicode: Optional[StringConverterFunction] = None, search_dirs: Optional[List[str]] = None) -> None:
        super().__init__(file_encoding, extension, to_unicode, search_dirs)
        self.templates: Dict[str, str] = {}

    def read(self, path: str, encoding: Optional[str] = None) -> str:
        if self.templates.get(path) is None:
            # look in redis using modified date on filesystem of path
            self.templates[path] = super().read(path, encoding)
            # write to redis
        return self.templates[path]

# If you have already parsed a template, don't parse it again.
class CachedRenderEngine(pystache.renderengine.RenderEngine):
    # pylint: disable=too-many-arguments
    def __init__(self, literal: StringConverterFunction = None, escape: StringConverterFunction = None, resolve_context: Optional[Callable[[ContextStack, str], str]] = None, resolve_partial: Optional[StringConverterFunction] = None, to_str: Optional[Callable[[object], str]] = None) -> None:
        super().__init__(literal, escape, resolve_context, resolve_partial, to_str)
        self.parsed_templates: Dict[str, pystache.parsed.ParsedTemplate] = {}

    def render(self, template: str, context_stack: ContextStack, delimiters: Optional[Tuple[str, str]] = None) -> str:
        if self.parsed_templates.get(template) is None:
            # look in redis
            self.parsed_templates[template] = pystache.parser.parse(template, delimiters)
            # store in redis
        return self.parsed_templates[template].render(self, context_stack)

Leave a Reply

Your email address will not be published.