Passed
Push — master ( c65a32...c8f71e )
by Jochen
02:08
created

SiteTemplateOverridesLoader.get_source()   A

Complexity

Conditions 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 7
nop 3
dl 0
loc 10
rs 10
c 0
b 0
f 0
1
"""
2
byceps.util.templating
3
~~~~~~~~~~~~~~~~~~~~~~
4
5
Templating utilities
6
7
:Copyright: 2006-2019 Jochen Kupperschmidt
8
:License: Modified BSD, see LICENSE for details.
9
"""
10
11
from flask import g
12
13
from typing import Any, Dict, Optional, Set
14
15
from jinja2 import (
16
    BaseLoader,
17
    Environment,
18
    FileSystemLoader,
19
    FunctionLoader,
20
    Template,
21
    TemplateNotFound,
22
)
23
from jinja2.sandbox import ImmutableSandboxedEnvironment
24
25
26
def load_template(source: str, *, template_globals: Dict[str, Any] = None):
27
    """Load a template from source, using the sandboxed environment."""
28
    env = create_sandboxed_environment()
29
30
    if template_globals is not None:
31
        env.globals.update(template_globals)
32
33
    return env.from_string(source)
34
35
36
def create_sandboxed_environment(
37
    *, loader: Optional[BaseLoader] = None, autoescape: bool = True
38
) -> Environment:
39
    """Create a sandboxed environment."""
40
    if loader is None:
41
        # A loader that never finds a template.
42
        loader = FunctionLoader(lambda name: None)
43
44
    return ImmutableSandboxedEnvironment(loader=loader, autoescape=autoescape)
45
46
47
def get_variable_value(template: Template, name: str) -> Optional[Any]:
48
    """Return the named variable's value from the template, or `None` if
49
    the variable is not defined.
50
    """
51
    try:
52
        return getattr(template.module, name)
53
    except AttributeError:
54
        return None
55
56
57
class SiteTemplateOverridesLoader(BaseLoader):
58
    """Look for site-specific template overrides."""
59
60
    def __init__(self) -> None:
61
        self._loaders_by_site_id = {}
62
63
    def get_source(self, environment: Environment, template: Template) -> str:
64
        loader = self._get_loader()
65
66
        try:
67
            return loader.get_source(environment, template)
68
        except TemplateNotFound:
69
            pass
70
71
        # Site does not override template.
72
        raise TemplateNotFound(template)
73
74
    def _get_loader(self) -> BaseLoader:
75
        """Look up site-specific loader.
76
77
        Create if not available.
78
        """
79
        site_id = g.site_id
80
81
        loader = self._loaders_by_site_id.get(site_id)
82
83
        if loader is None:
84
            loader = self._create_loader(site_id)
85
            self._loaders_by_site_id[site_id] = loader
86
87
        return loader
88
89
    def _create_loader(self, site_id: str) -> BaseLoader:
90
        """Create file system loader for site-specific search path."""
91
        search_path = f'sites/{site_id}/template_overrides'
92
        return FileSystemLoader(search_path)
93