python-liquid 2.3.6__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- liquid/__about__.py +1 -0
- liquid/__init__.py +161 -187
- liquid/_base_tag.py +66 -0
- liquid/{utils/chain_map.py → _chain_map.py} +18 -19
- liquid/_context.py +465 -0
- liquid/_drop.py +108 -0
- liquid/_filter.py +174 -0
- liquid/_io.py +18 -0
- liquid/_lax_parser_mixin.py +100 -0
- liquid/_lexer.py +93 -0
- liquid/_lexer_extended.py +623 -0
- liquid/_lexer_legacy.py +536 -0
- liquid/_loader.py +84 -0
- liquid/{utils/lru_cache.py → _lru_cache.py} +28 -36
- liquid/_markup.py +253 -0
- liquid/_messages.py +292 -0
- liquid/_missing.py +3 -0
- liquid/_nothing.py +28 -0
- liquid/_parser.py +460 -0
- liquid/_parser_extended.py +293 -0
- liquid/_parser_legacy.py +819 -0
- liquid/_resource_limits.py +50 -0
- liquid/_slice.py +41 -0
- liquid/_static_analysis.py +503 -0
- liquid/_strftime.py +186 -0
- liquid/_template.py +536 -0
- liquid/drops/__init__.py +20 -0
- liquid/drops/forloop.py +78 -0
- liquid/drops/iterable.py +25 -0
- liquid/drops/range.py +76 -0
- liquid/drops/self_.py +45 -0
- liquid/drops/tablerowloop.py +142 -0
- liquid/drops/undefined.py +228 -0
- liquid/environment.py +717 -551
- liquid/exceptions.py +67 -214
- liquid/expressions.py +1184 -0
- liquid/filters/__init__.py +134 -0
- liquid/filters/abs.py +7 -0
- liquid/filters/append.py +5 -0
- liquid/filters/at_least.py +9 -0
- liquid/filters/at_most.py +9 -0
- liquid/filters/babel.py +560 -0
- liquid/filters/base64.py +26 -0
- liquid/filters/capitalize.py +5 -0
- liquid/filters/ceil.py +8 -0
- liquid/filters/compact.py +25 -0
- liquid/filters/concat.py +11 -0
- liquid/filters/date.py +27 -0
- liquid/filters/default.py +25 -0
- liquid/filters/divided_by.py +21 -0
- liquid/filters/downcase.py +5 -0
- liquid/filters/escape.py +56 -0
- liquid/filters/find.py +56 -0
- liquid/filters/first.py +19 -0
- liquid/filters/floor.py +8 -0
- liquid/filters/html.py +23 -0
- liquid/filters/index.py +12 -0
- liquid/filters/join.py +13 -0
- liquid/filters/json_.py +30 -0
- liquid/filters/last.py +17 -0
- liquid/filters/map.py +5 -0
- liquid/filters/minus.py +13 -0
- liquid/filters/modulo.py +18 -0
- liquid/filters/newline_to_br.py +16 -0
- liquid/filters/plus.py +13 -0
- liquid/filters/prepend.py +5 -0
- liquid/filters/reject.py +19 -0
- liquid/filters/remove.py +36 -0
- liquid/filters/replace.py +32 -0
- liquid/filters/reverse.py +5 -0
- liquid/filters/round.py +35 -0
- liquid/filters/size.py +25 -0
- liquid/filters/slice.py +39 -0
- liquid/filters/sort.py +148 -0
- liquid/filters/split.py +34 -0
- liquid/filters/squish.py +9 -0
- liquid/filters/strip.py +13 -0
- liquid/filters/strip_html.py +19 -0
- liquid/filters/strip_newlines.py +17 -0
- liquid/filters/sum.py +21 -0
- liquid/filters/times.py +13 -0
- liquid/filters/translate.py +380 -0
- liquid/filters/truncate.py +39 -0
- liquid/filters/uniq.py +11 -0
- liquid/filters/upcase.py +5 -0
- liquid/filters/url.py +18 -0
- liquid/filters/where.py +18 -0
- liquid/loaders/__init__.py +17 -0
- liquid/loaders/caching_file_system_loader.py +64 -0
- liquid/loaders/choice_loader.py +93 -0
- liquid/loaders/dict_loader.py +56 -0
- liquid/{builtin/loaders → loaders}/file_system_loader.py +51 -55
- liquid/{builtin/loaders → loaders}/mixins.py +39 -51
- liquid/loaders/package_loader.py +110 -0
- liquid/tags/__init__.py +61 -0
- liquid/tags/assign.py +68 -0
- liquid/tags/capture.py +83 -0
- liquid/tags/case.py +199 -0
- liquid/tags/comment.py +56 -0
- liquid/tags/cycle.py +131 -0
- liquid/tags/decrement.py +55 -0
- liquid/tags/doc.py +56 -0
- liquid/tags/echo.py +65 -0
- liquid/tags/else_.py +53 -0
- liquid/tags/error.py +51 -0
- liquid/tags/extends.py +448 -0
- liquid/tags/for_.py +421 -0
- liquid/tags/if_.py +156 -0
- liquid/tags/ifchanged.py +69 -0
- liquid/tags/include.py +223 -0
- liquid/tags/increment.py +55 -0
- liquid/tags/inline_comment.py +70 -0
- liquid/tags/liquid.py +56 -0
- liquid/tags/macro.py +281 -0
- liquid/tags/output.py +58 -0
- liquid/tags/raw.py +60 -0
- liquid/tags/render.py +245 -0
- liquid/tags/tablerow.py +254 -0
- liquid/tags/template_comment.py +56 -0
- liquid/tags/translate.py +316 -0
- liquid/tags/unless.py +121 -0
- liquid/tags/with_.py +83 -0
- liquid/tokens.py +370 -0
- {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/METADATA +32 -39
- python_liquid-3.0.0.dist-info/RECORD +128 -0
- {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/WHEEL +1 -1
- {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/licenses/LICENSE +1 -1
- liquid/analyze_tags.py +0 -214
- liquid/ast.py +0 -252
- liquid/builtin/__init__.py +0 -203
- liquid/builtin/content.py +0 -41
- liquid/builtin/drops/__init__.py +0 -3
- liquid/builtin/drops/drops.py +0 -13
- liquid/builtin/expressions/__init__.py +0 -47
- liquid/builtin/expressions/_tokenize.py +0 -186
- liquid/builtin/expressions/arguments.py +0 -207
- liquid/builtin/expressions/filtered.py +0 -387
- liquid/builtin/expressions/logical.py +0 -672
- liquid/builtin/expressions/loop.py +0 -275
- liquid/builtin/expressions/path.py +0 -163
- liquid/builtin/expressions/primitive.py +0 -417
- liquid/builtin/filters/__init__.py +0 -0
- liquid/builtin/filters/array.py +0 -329
- liquid/builtin/filters/extra.py +0 -83
- liquid/builtin/filters/math.py +0 -129
- liquid/builtin/filters/misc.py +0 -137
- liquid/builtin/filters/string.py +0 -416
- liquid/builtin/illegal.py +0 -28
- liquid/builtin/loaders/__init__.py +0 -0
- liquid/builtin/loaders/caching_file_system_loader.py +0 -61
- liquid/builtin/loaders/choice_loader.py +0 -94
- liquid/builtin/loaders/dict_loader.py +0 -64
- liquid/builtin/loaders/package_loader.py +0 -132
- liquid/builtin/output.py +0 -74
- liquid/builtin/tags/__init__.py +0 -0
- liquid/builtin/tags/assign_tag.py +0 -75
- liquid/builtin/tags/capture_tag.py +0 -98
- liquid/builtin/tags/case_tag.py +0 -287
- liquid/builtin/tags/comment_tag.py +0 -77
- liquid/builtin/tags/cycle_tag.py +0 -132
- liquid/builtin/tags/decrement_tag.py +0 -59
- liquid/builtin/tags/doc_tag.py +0 -68
- liquid/builtin/tags/echo_tag.py +0 -45
- liquid/builtin/tags/for_tag.py +0 -337
- liquid/builtin/tags/if_tag.py +0 -209
- liquid/builtin/tags/ifchanged_tag.py +0 -94
- liquid/builtin/tags/include_tag.py +0 -251
- liquid/builtin/tags/increment_tag.py +0 -60
- liquid/builtin/tags/inline_comment_tag.py +0 -47
- liquid/builtin/tags/liquid_tag.py +0 -183
- liquid/builtin/tags/render_tag.py +0 -371
- liquid/builtin/tags/tablerow_tag.py +0 -320
- liquid/builtin/tags/unless_tag.py +0 -218
- liquid/context.py +0 -615
- liquid/expression.py +0 -41
- liquid/extra/__init__.py +0 -98
- liquid/extra/filters/__init__.py +0 -33
- liquid/extra/filters/_json.py +0 -33
- liquid/extra/filters/array.py +0 -60
- liquid/extra/filters/babel.py +0 -552
- liquid/extra/filters/html.py +0 -32
- liquid/extra/filters/translate.py +0 -448
- liquid/extra/tags/__init__.py +0 -17
- liquid/extra/tags/_with.py +0 -80
- liquid/extra/tags/extends_tag.py +0 -576
- liquid/extra/tags/macro_tag.py +0 -270
- liquid/extra/tags/snippet_tag.py +0 -107
- liquid/extra/tags/translate_tag.py +0 -388
- liquid/filter.py +0 -230
- liquid/future/__init__.py +0 -3
- liquid/future/environment.py +0 -30
- liquid/future/filters/__init__.py +0 -1
- liquid/future/tags/__init__.py +0 -1
- liquid/lex.py +0 -260
- liquid/limits.py +0 -66
- liquid/loader.py +0 -141
- liquid/messages.py +0 -357
- liquid/mode.py +0 -12
- liquid/output.py +0 -35
- liquid/parser.py +0 -119
- liquid/span.py +0 -35
- liquid/static_analysis.py +0 -443
- liquid/stream.py +0 -148
- liquid/stringify.py +0 -31
- liquid/tag.py +0 -47
- liquid/template.py +0 -658
- liquid/token.py +0 -145
- liquid/undefined.py +0 -218
- liquid/utils/__init__.py +0 -15
- liquid/utils/html.py +0 -57
- liquid/utils/text.py +0 -28
- python_liquid-2.3.6.dist-info/RECORD +0 -96
liquid/filters/uniq.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .._filter import FilterContext
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def uniq(context: FilterContext, left: object, prop: object = None) -> list[object]:
|
|
5
|
+
left = context.input_array(left)
|
|
6
|
+
|
|
7
|
+
if context.is_nil(prop):
|
|
8
|
+
return [obj for i, obj in enumerate(left) if left.index(obj) == i]
|
|
9
|
+
|
|
10
|
+
keys = [context.get_item(obj, prop) for obj in left]
|
|
11
|
+
return [left[i] for i, key in enumerate(keys) if keys.index(key) == i]
|
liquid/filters/upcase.py
ADDED
liquid/filters/url.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import urllib.parse
|
|
2
|
+
|
|
3
|
+
import markupsafe
|
|
4
|
+
|
|
5
|
+
from .._filter import FilterContext
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def url_encode(context: FilterContext, left: object) -> str:
|
|
9
|
+
left_ = context.to_str(left, "")
|
|
10
|
+
|
|
11
|
+
if context.env.autoescape:
|
|
12
|
+
return markupsafe.Markup(urllib.parse.quote_plus(left_))
|
|
13
|
+
|
|
14
|
+
return urllib.parse.quote_plus(left_)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def url_decode(context: FilterContext, left: object) -> str:
|
|
18
|
+
return urllib.parse.unquote_plus(context.to_str(left, ""), errors="strict")
|
liquid/filters/where.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .._filter import FilterContext
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def where(
|
|
5
|
+
context: FilterContext, left: object, prop: object, value: object = None
|
|
6
|
+
) -> list[object]:
|
|
7
|
+
left = context.input_array(left)
|
|
8
|
+
|
|
9
|
+
if context.is_nil(prop):
|
|
10
|
+
return []
|
|
11
|
+
|
|
12
|
+
def _filter(obj: object) -> bool:
|
|
13
|
+
return context.truthy(context.get_item(obj, prop))
|
|
14
|
+
|
|
15
|
+
if context.is_nil(value):
|
|
16
|
+
return list(filter(_filter, left))
|
|
17
|
+
|
|
18
|
+
return [obj for obj in left if context.get_item(obj, prop) == value]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .caching_file_system_loader import CachingFileSystemLoader
|
|
2
|
+
from .choice_loader import CachingChoiceLoader, ChoiceLoader
|
|
3
|
+
from .dict_loader import CachingDictLoader, DictLoader
|
|
4
|
+
from .file_system_loader import FileSystemLoader
|
|
5
|
+
from .mixins import CachingLoaderMixin
|
|
6
|
+
from .package_loader import PackageLoader
|
|
7
|
+
|
|
8
|
+
__all__ = (
|
|
9
|
+
"CachingChoiceLoader",
|
|
10
|
+
"CachingDictLoader",
|
|
11
|
+
"CachingFileSystemLoader",
|
|
12
|
+
"CachingLoaderMixin",
|
|
13
|
+
"ChoiceLoader",
|
|
14
|
+
"DictLoader",
|
|
15
|
+
"FileSystemLoader",
|
|
16
|
+
"PackageLoader",
|
|
17
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from .file_system_loader import FileSystemLoader
|
|
6
|
+
from .mixins import CachingLoaderMixin
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from collections.abc import Iterable
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CachingFileSystemLoader(CachingLoaderMixin, FileSystemLoader):
|
|
14
|
+
"""A file system loader that caches parsed templates in memory.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
search_path
|
|
19
|
+
One or more paths to search.
|
|
20
|
+
encoding
|
|
21
|
+
Open template files with the given encoding.
|
|
22
|
+
ext
|
|
23
|
+
A default file extension. Should include a leading period.
|
|
24
|
+
reject_symlinks
|
|
25
|
+
When `True`, reject paths to symlinks that resolve to files outside the
|
|
26
|
+
search path. Defaults to `False`.
|
|
27
|
+
auto_reload
|
|
28
|
+
If `True`, automatically reload a cached template if it has been updated.
|
|
29
|
+
namespace_key
|
|
30
|
+
The name of a global render context variable or loader keyword argument
|
|
31
|
+
that resolves to the current loader "namespace" or "scope".
|
|
32
|
+
|
|
33
|
+
If you're developing a multi-user application, a good namespace might be
|
|
34
|
+
`uid`, where `uid` is a unique identifier for a user and templates are
|
|
35
|
+
arranged in folders named for each `uid` inside the search path.
|
|
36
|
+
capacity
|
|
37
|
+
The maximum number of templates to hold in the cache before removing the
|
|
38
|
+
least recently used template.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
search_path: str | Path | Iterable[str | Path],
|
|
44
|
+
encoding: str = "utf-8",
|
|
45
|
+
ext: str | None = None,
|
|
46
|
+
*,
|
|
47
|
+
reject_symlinks: bool = False,
|
|
48
|
+
auto_reload: bool = True,
|
|
49
|
+
namespace_key: str = "",
|
|
50
|
+
capacity: int = 300,
|
|
51
|
+
):
|
|
52
|
+
super().__init__(
|
|
53
|
+
auto_reload=auto_reload,
|
|
54
|
+
namespace_key=namespace_key,
|
|
55
|
+
capacity=capacity,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
FileSystemLoader.__init__(
|
|
59
|
+
self,
|
|
60
|
+
search_path=search_path,
|
|
61
|
+
encoding=encoding,
|
|
62
|
+
ext=ext,
|
|
63
|
+
reject_symlinks=reject_symlinks,
|
|
64
|
+
)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from .._loader import Loader, TemplateSource
|
|
6
|
+
from ..exceptions import TemplateNotFoundError
|
|
7
|
+
from .mixins import CachingLoaderMixin
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .._context import RenderContext
|
|
11
|
+
from ..environment import Environment
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ChoiceLoader(Loader):
|
|
15
|
+
"""A template loader that delegates to other template loaders.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
loaders
|
|
20
|
+
A list of loaders implementing `liquid.Loader`.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, loaders: list[Loader]):
|
|
24
|
+
super().__init__()
|
|
25
|
+
self.loaders = loaders
|
|
26
|
+
|
|
27
|
+
def get_source(
|
|
28
|
+
self,
|
|
29
|
+
env: Environment,
|
|
30
|
+
name: str,
|
|
31
|
+
context: RenderContext | None = None,
|
|
32
|
+
**kwargs: object,
|
|
33
|
+
) -> TemplateSource:
|
|
34
|
+
"""Get source information for a template."""
|
|
35
|
+
for loader in self.loaders:
|
|
36
|
+
try:
|
|
37
|
+
return loader.get_source(env, name, context=context, **kwargs)
|
|
38
|
+
except TemplateNotFoundError:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
raise TemplateNotFoundError(name)
|
|
42
|
+
|
|
43
|
+
async def get_source_async(
|
|
44
|
+
self,
|
|
45
|
+
env: Environment,
|
|
46
|
+
name: str,
|
|
47
|
+
context: RenderContext | None = None,
|
|
48
|
+
**kwargs: object,
|
|
49
|
+
) -> TemplateSource:
|
|
50
|
+
"""Get source information for a template."""
|
|
51
|
+
for loader in self.loaders:
|
|
52
|
+
try:
|
|
53
|
+
return await loader.get_source_async(
|
|
54
|
+
env, name, context=context, **kwargs
|
|
55
|
+
)
|
|
56
|
+
except TemplateNotFoundError:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
raise TemplateNotFoundError(name)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class CachingChoiceLoader(CachingLoaderMixin, ChoiceLoader):
|
|
63
|
+
"""A `ChoiceLoader` that caches parsed templates in memory.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
loaders
|
|
68
|
+
A list of loaders implementing `liquid.Loader`.
|
|
69
|
+
auto_reload
|
|
70
|
+
If ``True``, automatically reload a cached template if it has been updated.
|
|
71
|
+
namespace_key
|
|
72
|
+
The name of a global render context variable or loader keyword argument
|
|
73
|
+
that resolves to the current loader "namespace" or "scope".
|
|
74
|
+
capacity
|
|
75
|
+
The maximum number of templates to hold in the cache before removing the
|
|
76
|
+
least recently used template.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
loaders: list[Loader],
|
|
82
|
+
*,
|
|
83
|
+
auto_reload: bool = True,
|
|
84
|
+
namespace_key: str = "",
|
|
85
|
+
capacity: int = 300,
|
|
86
|
+
):
|
|
87
|
+
super().__init__(
|
|
88
|
+
auto_reload=auto_reload,
|
|
89
|
+
namespace_key=namespace_key,
|
|
90
|
+
capacity=capacity,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
ChoiceLoader.__init__(self, loaders)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from .._loader import Loader, TemplateSource
|
|
6
|
+
from ..exceptions import TemplateNotFoundError
|
|
7
|
+
from .mixins import CachingLoaderMixin
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .._context import RenderContext
|
|
11
|
+
from ..environment import Environment
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DictLoader(Loader):
|
|
15
|
+
"""A template loader that reads source text from a dictionary.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
templates
|
|
20
|
+
A mapping of template names to template source code.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, templates: dict[str, str]) -> None:
|
|
24
|
+
super().__init__()
|
|
25
|
+
self.templates = templates
|
|
26
|
+
|
|
27
|
+
def get_source(
|
|
28
|
+
self,
|
|
29
|
+
env: Environment,
|
|
30
|
+
name: str,
|
|
31
|
+
context: RenderContext | None = None,
|
|
32
|
+
**kwargs: object,
|
|
33
|
+
) -> TemplateSource:
|
|
34
|
+
if name not in self.templates:
|
|
35
|
+
raise TemplateNotFoundError(name)
|
|
36
|
+
return TemplateSource(name=name, text=self.templates[name])
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class CachingDictLoader(CachingLoaderMixin, DictLoader):
|
|
40
|
+
"""A ``DictLoader`` that caches parsed templates in memory."""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
templates: dict[str, str],
|
|
45
|
+
*,
|
|
46
|
+
auto_reload: bool = True,
|
|
47
|
+
namespace_key: str = "",
|
|
48
|
+
capacity: int = 300,
|
|
49
|
+
):
|
|
50
|
+
super().__init__(
|
|
51
|
+
auto_reload=auto_reload,
|
|
52
|
+
namespace_key=namespace_key,
|
|
53
|
+
capacity=capacity,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
DictLoader.__init__(self, templates)
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
"""Built-in file system loader."""
|
|
2
|
-
|
|
3
1
|
from __future__ import annotations
|
|
4
2
|
|
|
5
3
|
import asyncio
|
|
6
4
|
import os
|
|
5
|
+
from collections.abc import Iterable
|
|
7
6
|
from functools import partial
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from pathlib import PurePath
|
|
7
|
+
from pathlib import Path, PurePath
|
|
10
8
|
from typing import TYPE_CHECKING
|
|
11
|
-
from typing import Iterable
|
|
12
|
-
from typing import Optional
|
|
13
|
-
from typing import Union
|
|
14
9
|
|
|
15
|
-
from
|
|
16
|
-
from
|
|
17
|
-
from liquid.loader import TemplateSource
|
|
10
|
+
from .._loader import Loader, TemplateSource
|
|
11
|
+
from ..exceptions import TemplateNotFoundError
|
|
18
12
|
|
|
19
13
|
if TYPE_CHECKING:
|
|
20
|
-
from liquid import Environment
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
14
|
+
from liquid import Environment, RenderContext
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FileSystemLoader(Loader):
|
|
18
|
+
"""A loader that loads templates from one or more directories on a file system.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
search_path
|
|
23
|
+
One or more paths to search.
|
|
24
|
+
encoding
|
|
25
|
+
Encoding to use when opening files.
|
|
26
|
+
ext
|
|
27
|
+
A default file extension. Should include a leading period.
|
|
28
|
+
reject_symlinks
|
|
29
|
+
When ``True``, reject paths to symlinks that resolve to files outside the
|
|
30
|
+
search path. Defaults to `False`.
|
|
31
|
+
|
|
32
|
+
Raises
|
|
33
|
+
------
|
|
34
|
+
ValueError
|
|
35
|
+
If `ext` is not a valid suffix.
|
|
36
36
|
"""
|
|
37
37
|
|
|
38
38
|
def __init__(
|
|
39
39
|
self,
|
|
40
|
-
search_path:
|
|
40
|
+
search_path: str | Path | Iterable[str | Path],
|
|
41
41
|
*,
|
|
42
42
|
encoding: str = "utf-8",
|
|
43
|
-
ext:
|
|
43
|
+
ext: str | None = None,
|
|
44
44
|
reject_symlinks: bool = False,
|
|
45
45
|
):
|
|
46
46
|
super().__init__()
|
|
@@ -56,25 +56,19 @@ class FileSystemLoader(BaseLoader):
|
|
|
56
56
|
if ext:
|
|
57
57
|
Path("x").with_suffix(ext)
|
|
58
58
|
|
|
59
|
-
def resolve_path(self,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
If the search path is a list of paths, returns the first path where
|
|
63
|
-
_template_name_ exists. If none of the search paths contain _template_name_, a
|
|
64
|
-
_TemplateNotFound_ exception is raised.
|
|
65
|
-
"""
|
|
66
|
-
template_path = PurePath(template_name)
|
|
59
|
+
def resolve_path(self, name: str) -> Path:
|
|
60
|
+
template_path = PurePath(name)
|
|
67
61
|
if template_path.anchor:
|
|
68
62
|
template_path = PurePath(*template_path.parts[1:])
|
|
69
63
|
|
|
70
64
|
if not template_path.name:
|
|
71
|
-
raise TemplateNotFoundError(
|
|
65
|
+
raise TemplateNotFoundError(name)
|
|
72
66
|
|
|
73
67
|
if self.ext and not template_path.suffix:
|
|
74
68
|
template_path = template_path.with_suffix(self.ext)
|
|
75
69
|
|
|
76
70
|
if os.path.pardir in template_path.parts or template_path.is_absolute():
|
|
77
|
-
raise TemplateNotFoundError(
|
|
71
|
+
raise TemplateNotFoundError(name)
|
|
78
72
|
|
|
79
73
|
for base in self.search_path:
|
|
80
74
|
base_resolved = base.resolve()
|
|
@@ -96,7 +90,7 @@ class FileSystemLoader(BaseLoader):
|
|
|
96
90
|
# We don't want to expose a fully qualified path in error messages.
|
|
97
91
|
return base / template_path
|
|
98
92
|
|
|
99
|
-
raise TemplateNotFoundError(
|
|
93
|
+
raise TemplateNotFoundError(name)
|
|
100
94
|
|
|
101
95
|
def _read(self, source_path: Path) -> tuple[str, float]:
|
|
102
96
|
with source_path.open(encoding=self.encoding) as fd:
|
|
@@ -105,19 +99,19 @@ class FileSystemLoader(BaseLoader):
|
|
|
105
99
|
|
|
106
100
|
def get_source(
|
|
107
101
|
self,
|
|
108
|
-
env: Environment,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
**kwargs: object, # noqa: ARG002
|
|
102
|
+
env: Environment,
|
|
103
|
+
name: str,
|
|
104
|
+
context: RenderContext | None = None,
|
|
105
|
+
**kwargs: object,
|
|
113
106
|
) -> TemplateSource:
|
|
114
107
|
"""Get source information for a template."""
|
|
115
|
-
source_path = self.resolve_path(
|
|
108
|
+
source_path = self.resolve_path(name)
|
|
116
109
|
source, mtime = self._read(source_path)
|
|
117
110
|
return TemplateSource(
|
|
118
|
-
source,
|
|
119
|
-
|
|
120
|
-
|
|
111
|
+
text=source,
|
|
112
|
+
name=source_path.name,
|
|
113
|
+
path=str(source_path),
|
|
114
|
+
up_to_date=partial(self._uptodate, source_path, mtime),
|
|
121
115
|
)
|
|
122
116
|
|
|
123
117
|
@staticmethod
|
|
@@ -132,16 +126,18 @@ class FileSystemLoader(BaseLoader):
|
|
|
132
126
|
|
|
133
127
|
async def get_source_async(
|
|
134
128
|
self,
|
|
135
|
-
env: Environment,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
**kwargs: object, # noqa: ARG002
|
|
129
|
+
env: Environment,
|
|
130
|
+
name: str,
|
|
131
|
+
context: RenderContext | None = None,
|
|
132
|
+
**kwargs: object,
|
|
140
133
|
) -> TemplateSource:
|
|
141
134
|
"""Get source information for a template."""
|
|
142
135
|
loop = asyncio.get_running_loop()
|
|
143
|
-
source_path = await loop.run_in_executor(None, self.resolve_path,
|
|
136
|
+
source_path = await loop.run_in_executor(None, self.resolve_path, name)
|
|
144
137
|
source, mtime = await loop.run_in_executor(None, self._read, source_path)
|
|
145
138
|
return TemplateSource(
|
|
146
|
-
source,
|
|
139
|
+
text=source,
|
|
140
|
+
name=source_path.name,
|
|
141
|
+
path=str(source_path),
|
|
142
|
+
up_to_date=partial(self._uptodate_async, source_path, mtime),
|
|
147
143
|
)
|
|
@@ -1,28 +1,18 @@
|
|
|
1
|
-
"""Mixin classes that can be used to add common functions to a template loader."""
|
|
2
|
-
|
|
3
1
|
from __future__ import annotations
|
|
4
2
|
|
|
5
3
|
from abc import ABC
|
|
6
4
|
from contextlib import suppress
|
|
7
5
|
from functools import partial
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
9
|
-
from typing import Awaitable
|
|
10
|
-
from typing import Callable
|
|
11
|
-
from typing import Mapping
|
|
12
|
-
from typing import Optional
|
|
13
|
-
from typing import Protocol
|
|
6
|
+
from typing import TYPE_CHECKING, Protocol
|
|
14
7
|
|
|
15
|
-
from
|
|
16
|
-
from liquid.utils import ThreadSafeLRUCache
|
|
8
|
+
from .._lru_cache import LRUCache, ThreadSafeLRUCache
|
|
17
9
|
|
|
18
10
|
if TYPE_CHECKING:
|
|
19
|
-
from
|
|
20
|
-
from liquid import Environment
|
|
21
|
-
from liquid import RenderContext
|
|
22
|
-
|
|
23
|
-
# ruff: noqa: D102
|
|
11
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
24
12
|
|
|
25
|
-
|
|
13
|
+
from .._context import RenderContext
|
|
14
|
+
from .._template import Template
|
|
15
|
+
from ..environment import Environment
|
|
26
16
|
|
|
27
17
|
|
|
28
18
|
class _CachingLoaderProtocol(Protocol):
|
|
@@ -30,21 +20,19 @@ class _CachingLoaderProtocol(Protocol):
|
|
|
30
20
|
self,
|
|
31
21
|
env: Environment,
|
|
32
22
|
name: str,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
context: Optional[RenderContext] = None,
|
|
23
|
+
globals: Mapping[str, object] | None = None,
|
|
24
|
+
context: RenderContext | None = None,
|
|
36
25
|
**kwargs: object,
|
|
37
|
-
) ->
|
|
26
|
+
) -> Template: ...
|
|
38
27
|
|
|
39
28
|
async def load_async( # pragma: no cover
|
|
40
29
|
self,
|
|
41
30
|
env: Environment,
|
|
42
31
|
name: str,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
context: Optional[RenderContext] = None,
|
|
32
|
+
globals: Mapping[str, object] | None = None,
|
|
33
|
+
context: RenderContext | None = None,
|
|
46
34
|
**kwargs: object,
|
|
47
|
-
) ->
|
|
35
|
+
) -> Template: ...
|
|
48
36
|
|
|
49
37
|
|
|
50
38
|
class CachingLoaderMixin(ABC, _CachingLoaderProtocol):
|
|
@@ -62,19 +50,20 @@ class CachingLoaderMixin(ABC, _CachingLoaderProtocol):
|
|
|
62
50
|
):
|
|
63
51
|
self.auto_reload = auto_reload
|
|
64
52
|
self.cache = (
|
|
65
|
-
ThreadSafeLRUCache[str, "
|
|
53
|
+
ThreadSafeLRUCache[str, "Template"](capacity=capacity)
|
|
66
54
|
if thread_safe
|
|
67
|
-
else LRUCache[str, "
|
|
55
|
+
else LRUCache[str, "Template"](capacity=capacity)
|
|
68
56
|
)
|
|
69
57
|
self.namespace_key = namespace_key
|
|
70
58
|
|
|
71
59
|
def _check_cache(
|
|
72
60
|
self,
|
|
73
|
-
|
|
61
|
+
*,
|
|
62
|
+
env: Environment,
|
|
74
63
|
cache_key: str,
|
|
75
|
-
globals:
|
|
76
|
-
load_func: Callable[[],
|
|
77
|
-
) ->
|
|
64
|
+
globals: Mapping[str, object] | None,
|
|
65
|
+
load_func: Callable[[], Template],
|
|
66
|
+
) -> Template:
|
|
78
67
|
try:
|
|
79
68
|
cached_template = self.cache[cache_key]
|
|
80
69
|
except KeyError:
|
|
@@ -93,11 +82,12 @@ class CachingLoaderMixin(ABC, _CachingLoaderProtocol):
|
|
|
93
82
|
|
|
94
83
|
async def _check_cache_async(
|
|
95
84
|
self,
|
|
96
|
-
|
|
85
|
+
*,
|
|
86
|
+
env: Environment,
|
|
97
87
|
cache_key: str,
|
|
98
|
-
globals:
|
|
99
|
-
load_func: Callable[[], Awaitable[
|
|
100
|
-
) ->
|
|
88
|
+
globals: Mapping[str, object] | None,
|
|
89
|
+
load_func: Callable[[], Awaitable[Template]],
|
|
90
|
+
) -> Template:
|
|
101
91
|
try:
|
|
102
92
|
cached_template = self.cache[cache_key]
|
|
103
93
|
except KeyError:
|
|
@@ -118,17 +108,16 @@ class CachingLoaderMixin(ABC, _CachingLoaderProtocol):
|
|
|
118
108
|
self,
|
|
119
109
|
env: Environment,
|
|
120
110
|
name: str,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
context: Optional[RenderContext] = None,
|
|
111
|
+
globals: Mapping[str, object] | None = None,
|
|
112
|
+
context: RenderContext | None = None,
|
|
124
113
|
**kwargs: object,
|
|
125
|
-
) ->
|
|
114
|
+
) -> Template:
|
|
126
115
|
cache_key = self.cache_key(name, context, kwargs)
|
|
127
116
|
return self._check_cache(
|
|
128
|
-
env,
|
|
129
|
-
cache_key,
|
|
130
|
-
globals,
|
|
131
|
-
partial(
|
|
117
|
+
env=env,
|
|
118
|
+
cache_key=cache_key,
|
|
119
|
+
globals=globals,
|
|
120
|
+
load_func=partial(
|
|
132
121
|
super().load, # type: ignore
|
|
133
122
|
env,
|
|
134
123
|
name,
|
|
@@ -142,17 +131,16 @@ class CachingLoaderMixin(ABC, _CachingLoaderProtocol):
|
|
|
142
131
|
self,
|
|
143
132
|
env: Environment,
|
|
144
133
|
name: str,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
context: Optional[RenderContext] = None,
|
|
134
|
+
globals: Mapping[str, object] | None = None,
|
|
135
|
+
context: RenderContext | None = None,
|
|
148
136
|
**kwargs: object,
|
|
149
|
-
) ->
|
|
137
|
+
) -> Template:
|
|
150
138
|
cache_key = self.cache_key(name, context, kwargs)
|
|
151
139
|
return await self._check_cache_async(
|
|
152
|
-
env,
|
|
153
|
-
cache_key,
|
|
154
|
-
globals,
|
|
155
|
-
partial(
|
|
140
|
+
env=env,
|
|
141
|
+
cache_key=cache_key,
|
|
142
|
+
globals=globals,
|
|
143
|
+
load_func=partial(
|
|
156
144
|
super().load_async, # type: ignore
|
|
157
145
|
env,
|
|
158
146
|
name,
|
|
@@ -165,7 +153,7 @@ class CachingLoaderMixin(ABC, _CachingLoaderProtocol):
|
|
|
165
153
|
def cache_key(
|
|
166
154
|
self,
|
|
167
155
|
name: str,
|
|
168
|
-
context:
|
|
156
|
+
context: RenderContext | None,
|
|
169
157
|
args: Mapping[str, object],
|
|
170
158
|
) -> str:
|
|
171
159
|
if not self.namespace_key:
|