python-liquid 1.10.2__py3-none-any.whl → 1.12.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/__init__.py CHANGED
@@ -12,11 +12,15 @@ from .mode import Mode
12
12
  from .token import Token
13
13
  from .expression import Expression
14
14
 
15
+ from .loaders import CachingChoiceLoader
15
16
  from .loaders import CachingFileSystemLoader
16
17
  from .loaders import ChoiceLoader
17
18
  from .loaders import DictLoader
18
19
  from .loaders import FileExtensionLoader
19
20
  from .loaders import FileSystemLoader
21
+ from .loaders import PackageLoader
22
+ from .loaders import make_choice_loader
23
+ from .loaders import make_file_system_loader
20
24
 
21
25
  from .context import Context
22
26
  from .context import DebugUndefined
@@ -42,11 +46,12 @@ from .static_analysis import ContextualTemplateAnalysis
42
46
 
43
47
  from . import future
44
48
 
45
- __version__ = "1.10.2"
49
+ __version__ = "1.12.0"
46
50
 
47
51
  __all__ = (
48
52
  "AwareBoundTemplate",
49
53
  "BoundTemplate",
54
+ "CachingChoiceLoader",
50
55
  "CachingFileSystemLoader",
51
56
  "ChoiceLoader",
52
57
  "Context",
@@ -64,8 +69,11 @@ __all__ = (
64
69
  "FutureBoundTemplate",
65
70
  "FutureContext",
66
71
  "is_undefined",
72
+ "make_choice_loader",
73
+ "make_file_system_loader",
67
74
  "Markup",
68
75
  "Mode",
76
+ "PackageLoader",
69
77
  "soft_str",
70
78
  "StrictDefaultUndefined",
71
79
  "StrictUndefined",
@@ -1,9 +1,15 @@
1
+ from pathlib import Path
2
+ from typing import Iterable
3
+ from typing import List
4
+ from typing import Union
5
+
1
6
  from .base_loader import BaseLoader
2
7
  from .base_loader import DictLoader
3
8
  from .base_loader import TemplateNamespace
4
9
  from .base_loader import TemplateSource
5
10
  from .base_loader import UpToDate
6
11
 
12
+ from .choice_loader import CachingChoiceLoader
7
13
  from .choice_loader import ChoiceLoader
8
14
 
9
15
  from .file_system_loader import FileExtensionLoader
@@ -11,14 +17,117 @@ from .file_system_loader import FileSystemLoader
11
17
 
12
18
  from .caching_file_system_loader import CachingFileSystemLoader
13
19
 
20
+ from .package_loader import PackageLoader
21
+
14
22
  __all__ = (
15
23
  "BaseLoader",
24
+ "CachingChoiceLoader",
16
25
  "CachingFileSystemLoader",
17
26
  "ChoiceLoader",
18
27
  "DictLoader",
19
28
  "FileExtensionLoader",
20
29
  "FileSystemLoader",
30
+ "make_choice_loader",
31
+ "make_file_system_loader",
32
+ "PackageLoader",
21
33
  "TemplateNamespace",
22
34
  "TemplateSource",
23
35
  "UpToDate",
24
36
  )
37
+
38
+
39
+ def make_file_system_loader(
40
+ search_path: Union[str, Path, Iterable[Union[str, Path]]],
41
+ *,
42
+ encoding: str = "utf-8",
43
+ ext: str = ".liquid",
44
+ auto_reload: bool = True,
45
+ namespace_key: str = "",
46
+ cache_size: int = 300,
47
+ ) -> BaseLoader:
48
+ """A _file system_ template loader factory.
49
+
50
+ Returns one of `CachingFileSystemLoader`, `FileExtensionLoader` or
51
+ `FileSystemLoader` depending in the given arguments.
52
+
53
+ A `CachingFileSystemLoader` is returned if _cache_size_ is greater than 0.
54
+ Otherwise a `FileExtensionLoader` is returned if _ext_ is not empty.
55
+ If _ext_ is empty, a `FileSystemLoader` is returned.
56
+
57
+ _auto_reload_ and _namespace_key_ are ignored if _cache_key_ is less than 1.
58
+
59
+ Args:
60
+ search_path: One or more paths to search.
61
+ encoding: Open template files with the given encoding.
62
+ ext: A default file extension. Should include a leading period.
63
+ auto_reload: If `True`, automatically reload a cached template if it has been
64
+ updated.
65
+ namespace_key: The name of a global render context variable or loader keyword
66
+ argument that resolves to the current loader "namespace" or "scope".
67
+
68
+ If you're developing a multi-user application, a good namespace might be
69
+ `uid`, where `uid` is a unique identifier for a user and templates are
70
+ arranged in folders named for each `uid` inside the search path.
71
+ cache_size: The maximum number of templates to hold in the cache before removing
72
+ the least recently used template.
73
+
74
+ _New in version 1.12.0_
75
+ """
76
+ if cache_size > 0:
77
+ return CachingFileSystemLoader(
78
+ search_path=search_path,
79
+ encoding=encoding,
80
+ ext=ext,
81
+ auto_reload=auto_reload,
82
+ namespace_key=namespace_key,
83
+ cache_size=cache_size,
84
+ )
85
+
86
+ if ext:
87
+ return FileExtensionLoader(
88
+ search_path=search_path,
89
+ encoding=encoding,
90
+ ext=ext,
91
+ )
92
+
93
+ return FileSystemLoader(search_path=search_path, encoding=encoding)
94
+
95
+
96
+ def make_choice_loader(
97
+ loaders: List[BaseLoader],
98
+ *,
99
+ auto_reload: bool = True,
100
+ namespace_key: str = "",
101
+ cache_size: int = 300,
102
+ ) -> BaseLoader:
103
+ """A _choice loader_ factory.
104
+
105
+ Returns one of `CachingChoiceLoader` or `ChoiceLoader` depending on the
106
+ given arguments.
107
+
108
+ A `CachingChoiceLoader` is returned if _cache_size_ > 0, otherwise a
109
+ `ChoiceLoader` is returned.
110
+
111
+ _auto_reload_ and _namespace_key_ are ignored if _cache_key_ is less than 1.
112
+
113
+ Args:
114
+ loaders: A list of loaders implementing `liquid.loaders.BaseLoader`.
115
+ auto_reload: If `True`, automatically reload a cached template if it
116
+ has been updated.
117
+ namespace_key: The name of a global render context variable or loader
118
+ keyword argument that resolves to the current loader "namespace" or
119
+ "scope".
120
+ cache_size: The maximum number of templates to hold in the cache before
121
+ removing the least recently used template.
122
+
123
+ _New in version 1.12.0_
124
+ """
125
+ if cache_size > 0:
126
+ return CachingChoiceLoader(
127
+ loaders=loaders,
128
+ auto_reload=auto_reload,
129
+ namespace_key=namespace_key,
130
+ cache_size=cache_size,
131
+ )
132
+
133
+ return ChoiceLoader(loaders=loaders)
@@ -129,11 +129,12 @@ class BaseLoader(ABC): # noqa: B024
129
129
  name: str,
130
130
  globals: TemplateNamespace = None, # noqa: A002
131
131
  ) -> BoundTemplate:
132
- """Load and parse a template.
132
+ """Find and parse template source code.
133
133
 
134
- Used internally by `Environment` to load a template source. Delegates to
135
- `get_source`. A custom loaders would typically implement `get_source` rather
136
- than overriding `load`.
134
+ This is used internally by `liquid.Environment` to load template
135
+ source text. `load()` delegates to `BaseLoader.get_source()`. Custom
136
+ loaders would typically implement `get_source()` rather than overriding
137
+ `load()`.
137
138
  """
138
139
  try:
139
140
  source, filename, uptodate, matter = self.get_source(env, name)
@@ -156,7 +157,7 @@ class BaseLoader(ABC): # noqa: B024
156
157
  name: str,
157
158
  globals: TemplateNamespace = None, # noqa: A002
158
159
  ) -> BoundTemplate:
159
- """An async version of `load`."""
160
+ """An async version of `load()`."""
160
161
  try:
161
162
  template_source = await self.get_source_async(env, name)
162
163
  source, filename, uptodate, matter = template_source
@@ -1,32 +1,24 @@
1
1
  """A file system loader that caches parsed templates in memory."""
2
2
  from __future__ import annotations
3
3
 
4
- from functools import partial
5
4
  from typing import TYPE_CHECKING
6
- from typing import Awaitable
7
- from typing import Callable
8
5
  from typing import Iterable
9
- from typing import Mapping
10
6
  from typing import Union
11
7
 
12
- from liquid.utils import LRUCache
13
-
14
8
  from .file_system_loader import FileExtensionLoader
9
+ from .mixins import CachingLoaderMixin
15
10
 
16
11
  if TYPE_CHECKING:
17
12
  from pathlib import Path
18
13
 
19
- from liquid import BoundTemplate
20
14
  from liquid import Context
21
- from liquid import Environment
22
15
 
23
- from .base_loader import TemplateNamespace
24
16
  from .base_loader import TemplateSource
25
17
 
26
- # ruff: noqa: D102 D101
18
+ # ruff: noqa: D102
27
19
 
28
20
 
29
- class CachingFileSystemLoader(FileExtensionLoader):
21
+ class CachingFileSystemLoader(CachingLoaderMixin, FileExtensionLoader):
30
22
  """A file system loader that caches parsed templates in memory.
31
23
 
32
24
  Args:
@@ -45,8 +37,6 @@ class CachingFileSystemLoader(FileExtensionLoader):
45
37
  the least recently used template.
46
38
  """
47
39
 
48
- caching_loader = True
49
-
50
40
  def __init__(
51
41
  self,
52
42
  search_path: Union[str, Path, Iterable[Union[str, Path]]],
@@ -58,160 +48,17 @@ class CachingFileSystemLoader(FileExtensionLoader):
58
48
  cache_size: int = 300,
59
49
  ):
60
50
  super().__init__(
51
+ auto_reload=auto_reload,
52
+ namespace_key=namespace_key,
53
+ cache_size=cache_size,
54
+ )
55
+
56
+ FileExtensionLoader.__init__(
57
+ self,
61
58
  search_path=search_path,
62
59
  encoding=encoding,
63
60
  ext=ext,
64
61
  )
65
- self.auto_reload = auto_reload
66
- self.cache = LRUCache(capacity=cache_size)
67
- self.namespace_key = namespace_key
68
-
69
- def load(
70
- self,
71
- env: Environment,
72
- name: str,
73
- globals: TemplateNamespace = None, # noqa: A002
74
- ) -> BoundTemplate:
75
- return self.check_cache(
76
- env,
77
- name,
78
- globals,
79
- partial(super().load, env, name, globals),
80
- )
81
-
82
- async def load_async(
83
- self,
84
- env: Environment,
85
- name: str,
86
- globals: TemplateNamespace = None, # noqa: A002
87
- ) -> BoundTemplate:
88
- return await self.check_cache_async(
89
- env,
90
- name,
91
- globals,
92
- partial(super().load_async, env, name, globals),
93
- )
94
-
95
- def load_with_args(
96
- self,
97
- env: Environment,
98
- name: str,
99
- globals: TemplateNamespace = None, # noqa: A002
100
- **kwargs: object,
101
- ) -> BoundTemplate:
102
- cache_key = self.cache_key(name, kwargs)
103
- return self.check_cache(
104
- env,
105
- cache_key,
106
- globals,
107
- partial(super().load_with_args, env, cache_key, globals, **kwargs),
108
- )
109
-
110
- async def load_with_args_async(
111
- self,
112
- env: Environment,
113
- name: str,
114
- globals: TemplateNamespace = None, # noqa: A002
115
- **kwargs: object,
116
- ) -> BoundTemplate:
117
- cache_key = self.cache_key(name, kwargs)
118
- return await self.check_cache_async(
119
- env,
120
- cache_key,
121
- globals,
122
- partial(super().load_with_args_async, env, cache_key, globals, **kwargs),
123
- )
124
-
125
- def load_with_context(
126
- self, context: Context, name: str, **kwargs: str
127
- ) -> BoundTemplate:
128
- cache_key = self.cache_key_with_context(name, context, **kwargs)
129
- return self.check_cache(
130
- context.env,
131
- cache_key,
132
- context.globals,
133
- partial(super().load_with_context, context=context, name=name, **kwargs),
134
- )
135
-
136
- async def load_with_context_async(
137
- self, context: Context, name: str, **kwargs: str
138
- ) -> BoundTemplate:
139
- cache_key = self.cache_key_with_context(name, context, **kwargs)
140
- return await self.check_cache_async(
141
- context.env,
142
- cache_key,
143
- context.globals,
144
- partial(super().load_with_context_async, context, name, **kwargs),
145
- )
146
-
147
- def check_cache(
148
- self,
149
- env: Environment, # noqa: ARG002
150
- cache_key: str,
151
- globals: TemplateNamespace, # noqa: A002
152
- load_func: Callable[[], BoundTemplate],
153
- ) -> BoundTemplate:
154
- try:
155
- cached_template: BoundTemplate = self.cache[cache_key]
156
- except KeyError:
157
- template = load_func()
158
- self.cache[cache_key] = template
159
- return template
160
-
161
- if self.auto_reload and not cached_template.is_up_to_date:
162
- template = load_func()
163
- self.cache[cache_key] = template
164
- return template
165
-
166
- if globals:
167
- cached_template.globals.update(globals)
168
- return cached_template
169
-
170
- async def check_cache_async(
171
- self,
172
- env: Environment, # noqa: ARG002
173
- cache_key: str,
174
- globals: TemplateNamespace, # noqa: A002
175
- load_func: Callable[[], Awaitable[BoundTemplate]],
176
- ) -> BoundTemplate:
177
- try:
178
- cached_template: BoundTemplate = self.cache[cache_key]
179
- except KeyError:
180
- template = await load_func()
181
- self.cache[cache_key] = template
182
- return template
183
-
184
- if self.auto_reload and not await cached_template.is_up_to_date_async():
185
- template = await load_func()
186
- self.cache[cache_key] = template
187
- return template
188
-
189
- if globals:
190
- cached_template.globals.update(globals)
191
- return cached_template
192
-
193
- def cache_key(self, name: str, args: Mapping[str, object]) -> str:
194
- if not self.namespace_key:
195
- return name
196
-
197
- try:
198
- return f"{args[self.namespace_key]}/{name}"
199
- except KeyError:
200
- return name
201
-
202
- def cache_key_with_context(
203
- self,
204
- name: str,
205
- context: Context,
206
- **kwargs: str, # noqa: ARG002
207
- ) -> str:
208
- if not self.namespace_key:
209
- return name
210
-
211
- try:
212
- return f"{context.globals[self.namespace_key]}/{name}"
213
- except KeyError:
214
- return name
215
62
 
216
63
  def get_source_with_context(
217
64
  self, context: Context, template_name: str, **kwargs: str
@@ -7,11 +7,14 @@ from typing import List
7
7
  from liquid.exceptions import TemplateNotFound
8
8
 
9
9
  from .base_loader import BaseLoader
10
- from .base_loader import TemplateSource
10
+ from .mixins import CachingLoaderMixin
11
11
 
12
12
  if TYPE_CHECKING:
13
+ from liquid import Context
13
14
  from liquid import Environment
14
15
 
16
+ from .base_loader import TemplateSource
17
+
15
18
 
16
19
  class ChoiceLoader(BaseLoader):
17
20
  """A template loader that will try each of a list of loaders in turn.
@@ -24,9 +27,8 @@ class ChoiceLoader(BaseLoader):
24
27
  super().__init__()
25
28
  self.loaders = loaders
26
29
 
27
- def get_source( # noqa: D102
28
- self, env: Environment, template_name: str
29
- ) -> TemplateSource:
30
+ def get_source(self, env: Environment, template_name: str) -> TemplateSource:
31
+ """Get source code for a template from one of the configured loaders."""
30
32
  for loader in self.loaders:
31
33
  try:
32
34
  return loader.get_source(env, template_name)
@@ -35,11 +37,12 @@ class ChoiceLoader(BaseLoader):
35
37
 
36
38
  raise TemplateNotFound(template_name)
37
39
 
38
- async def get_source_async( # noqa: D102
40
+ async def get_source_async(
39
41
  self,
40
42
  env: Environment,
41
43
  template_name: str,
42
44
  ) -> TemplateSource:
45
+ """An async version of `get_source`."""
43
46
  for loader in self.loaders:
44
47
  try:
45
48
  return await loader.get_source_async(env, template_name)
@@ -47,3 +50,94 @@ class ChoiceLoader(BaseLoader):
47
50
  pass
48
51
 
49
52
  raise TemplateNotFound(template_name)
53
+
54
+ def get_source_with_args(
55
+ self,
56
+ env: Environment,
57
+ template_name: str,
58
+ **kwargs: object,
59
+ ) -> TemplateSource:
60
+ """Get source code for a template from one of the configured loaders."""
61
+ for loader in self.loaders:
62
+ try:
63
+ return loader.get_source_with_args(env, template_name, **kwargs)
64
+ except TemplateNotFound:
65
+ pass
66
+
67
+ # TODO: include arguments in TemplateNotFound exception.
68
+ raise TemplateNotFound(template_name)
69
+
70
+ async def get_source_with_args_async(
71
+ self,
72
+ env: Environment,
73
+ template_name: str,
74
+ **kwargs: object,
75
+ ) -> TemplateSource:
76
+ """An async version of `get_source_with_args`."""
77
+ for loader in self.loaders:
78
+ try:
79
+ return await loader.get_source_with_args_async(
80
+ env, template_name, **kwargs
81
+ )
82
+ except TemplateNotFound:
83
+ pass
84
+
85
+ raise TemplateNotFound(template_name)
86
+
87
+ def get_source_with_context(
88
+ self, context: Context, template_name: str, **kwargs: str
89
+ ) -> TemplateSource:
90
+ """Get source code for a template from one of the configured loaders."""
91
+ for loader in self.loaders:
92
+ try:
93
+ return loader.get_source_with_context(context, template_name, **kwargs)
94
+ except TemplateNotFound:
95
+ pass
96
+
97
+ raise TemplateNotFound(template_name)
98
+
99
+ async def get_source_with_context_async(
100
+ self, context: Context, template_name: str, **kwargs: str
101
+ ) -> TemplateSource:
102
+ """Get source code for a template from one of the configured loaders."""
103
+ for loader in self.loaders:
104
+ try:
105
+ return await loader.get_source_with_context_async(
106
+ context, template_name, **kwargs
107
+ )
108
+ except TemplateNotFound:
109
+ pass
110
+
111
+ raise TemplateNotFound(template_name)
112
+
113
+
114
+ class CachingChoiceLoader(CachingLoaderMixin, ChoiceLoader):
115
+ """A `ChoiceLoader` that caches parsed templates in memory.
116
+
117
+ Args:
118
+ loaders: A list of loaders implementing `liquid.loaders.BaseLoader`.
119
+ auto_reload: If `True`, automatically reload a cached template if it has been
120
+ updated.
121
+ namespace_key: The name of a global render context variable or loader keyword
122
+ argument that resolves to the current loader "namespace" or "scope".
123
+ cache_size: The maximum number of templates to hold in the cache before removing
124
+ the least recently used template.
125
+
126
+ _New in version 1.11.0._
127
+ """
128
+
129
+ def __init__(
130
+ self,
131
+ loaders: List[BaseLoader],
132
+ *,
133
+ auto_reload: bool = True,
134
+ namespace_key: str = "",
135
+ cache_size: int = 300,
136
+ ):
137
+ super().__init__(
138
+ auto_reload=auto_reload,
139
+ namespace_key=namespace_key,
140
+ cache_size=cache_size,
141
+ )
142
+
143
+ ChoiceLoader.__init__(self, loaders)
@@ -0,0 +1,240 @@
1
+ """Mixin classes that can be used to add common functions to a template loader."""
2
+ from __future__ import annotations
3
+
4
+ from abc import ABC
5
+ from functools import partial
6
+ from typing import TYPE_CHECKING
7
+ from typing import Awaitable
8
+ from typing import Callable
9
+ from typing import Mapping
10
+
11
+ from typing_extensions import Protocol
12
+
13
+ from liquid.utils import LRUCache
14
+
15
+ if TYPE_CHECKING:
16
+ from liquid import BoundTemplate
17
+ from liquid import Context
18
+ from liquid import Environment
19
+
20
+ from .base_loader import TemplateNamespace
21
+
22
+ # ruff: noqa: D102
23
+
24
+ # ignoring "safe-super" type errors due to https://github.com/python/mypy/issues/14757
25
+
26
+
27
+ class _CachingLoaderProtocol(Protocol):
28
+ def load(
29
+ self,
30
+ env: Environment,
31
+ name: str,
32
+ globals: TemplateNamespace = None, # noqa: A002
33
+ ) -> BoundTemplate:
34
+ ...
35
+
36
+ async def load_async(
37
+ self,
38
+ env: Environment,
39
+ name: str,
40
+ globals: TemplateNamespace = None, # noqa: A002
41
+ ) -> BoundTemplate:
42
+ ...
43
+
44
+ def load_with_args(
45
+ self,
46
+ env: Environment,
47
+ name: str,
48
+ globals: TemplateNamespace = None, # noqa: A002
49
+ **kwargs: object,
50
+ ) -> BoundTemplate:
51
+ ...
52
+
53
+ async def load_with_args_async(
54
+ self,
55
+ env: Environment,
56
+ name: str,
57
+ globals: TemplateNamespace = None, # noqa: A002
58
+ **kwargs: object,
59
+ ) -> BoundTemplate:
60
+ ...
61
+
62
+ def load_with_context(
63
+ self,
64
+ context: Context,
65
+ name: str,
66
+ **kwargs: str,
67
+ ) -> BoundTemplate:
68
+ ...
69
+
70
+ async def load_with_context_async(
71
+ self,
72
+ context: Context,
73
+ name: str,
74
+ **kwargs: str,
75
+ ) -> BoundTemplate:
76
+ ...
77
+
78
+
79
+ class CachingLoaderMixin(ABC, _CachingLoaderProtocol):
80
+ """A mixin class that adds caching to a template loader."""
81
+
82
+ caching_loader = True
83
+
84
+ def __init__(
85
+ self,
86
+ *,
87
+ auto_reload: bool = True,
88
+ namespace_key: str = "",
89
+ cache_size: int = 300,
90
+ ):
91
+ self.auto_reload = auto_reload
92
+ self.cache = LRUCache(capacity=cache_size)
93
+ self.namespace_key = namespace_key
94
+
95
+ def _check_cache(
96
+ self,
97
+ env: Environment, # noqa: ARG002
98
+ cache_key: str,
99
+ globals: TemplateNamespace, # noqa: A002
100
+ load_func: Callable[[], BoundTemplate],
101
+ ) -> BoundTemplate:
102
+ try:
103
+ cached_template: BoundTemplate = self.cache[cache_key]
104
+ except KeyError:
105
+ template = load_func()
106
+ self.cache[cache_key] = template
107
+ return template
108
+
109
+ if self.auto_reload and not cached_template.is_up_to_date:
110
+ template = load_func()
111
+ self.cache[cache_key] = template
112
+ return template
113
+
114
+ if globals:
115
+ cached_template.globals.update(globals)
116
+ return cached_template
117
+
118
+ async def _check_cache_async(
119
+ self,
120
+ env: Environment, # noqa: ARG002
121
+ cache_key: str,
122
+ globals: TemplateNamespace, # noqa: A002
123
+ load_func: Callable[[], Awaitable[BoundTemplate]],
124
+ ) -> BoundTemplate:
125
+ try:
126
+ cached_template: BoundTemplate = self.cache[cache_key]
127
+ except KeyError:
128
+ template = await load_func()
129
+ self.cache[cache_key] = template
130
+ return template
131
+
132
+ if self.auto_reload and not await cached_template.is_up_to_date_async():
133
+ template = await load_func()
134
+ self.cache[cache_key] = template
135
+ return template
136
+
137
+ if globals:
138
+ cached_template.globals.update(globals)
139
+ return cached_template
140
+
141
+ def load(
142
+ self,
143
+ env: Environment,
144
+ name: str,
145
+ globals: TemplateNamespace = None, # noqa: A002
146
+ ) -> BoundTemplate:
147
+ return self._check_cache(
148
+ env,
149
+ name,
150
+ globals,
151
+ partial(super().load, env, name, globals), # type: ignore
152
+ )
153
+
154
+ async def load_async(
155
+ self,
156
+ env: Environment,
157
+ name: str,
158
+ globals: TemplateNamespace = None, # noqa: A002
159
+ ) -> BoundTemplate:
160
+ return await self._check_cache_async(
161
+ env,
162
+ name,
163
+ globals,
164
+ partial(super().load_async, env, name, globals), # type: ignore
165
+ )
166
+
167
+ def load_with_args(
168
+ self,
169
+ env: Environment,
170
+ name: str,
171
+ globals: TemplateNamespace = None, # noqa: A002
172
+ **kwargs: object,
173
+ ) -> BoundTemplate:
174
+ cache_key = self.cache_key(name, kwargs)
175
+ return self._check_cache(
176
+ env,
177
+ cache_key,
178
+ globals,
179
+ partial(super().load_with_args, env, cache_key, globals, **kwargs), # type: ignore
180
+ )
181
+
182
+ async def load_with_args_async(
183
+ self,
184
+ env: Environment,
185
+ name: str,
186
+ globals: TemplateNamespace = None, # noqa: A002
187
+ **kwargs: object,
188
+ ) -> BoundTemplate:
189
+ cache_key = self.cache_key(name, kwargs)
190
+ return await self._check_cache_async(
191
+ env,
192
+ cache_key,
193
+ globals,
194
+ partial(super().load_with_args_async, env, cache_key, globals, **kwargs), # type: ignore
195
+ )
196
+
197
+ def load_with_context(
198
+ self, context: Context, name: str, **kwargs: str
199
+ ) -> BoundTemplate:
200
+ cache_key = self.cache_key_with_context(name, context, **kwargs)
201
+ return self._check_cache(
202
+ context.env,
203
+ cache_key,
204
+ context.globals,
205
+ partial(super().load_with_context, context=context, name=name, **kwargs), # type: ignore
206
+ )
207
+
208
+ async def load_with_context_async(
209
+ self, context: Context, name: str, **kwargs: str
210
+ ) -> BoundTemplate:
211
+ cache_key = self.cache_key_with_context(name, context, **kwargs)
212
+ return await self._check_cache_async(
213
+ context.env,
214
+ cache_key,
215
+ context.globals,
216
+ partial(super().load_with_context_async, context, name, **kwargs), # type: ignore
217
+ )
218
+
219
+ def cache_key(self, name: str, args: Mapping[str, object]) -> str:
220
+ if not self.namespace_key:
221
+ return name
222
+
223
+ try:
224
+ return f"{args[self.namespace_key]}/{name}"
225
+ except KeyError:
226
+ return name
227
+
228
+ def cache_key_with_context(
229
+ self,
230
+ name: str,
231
+ context: Context,
232
+ **kwargs: str, # noqa: ARG002
233
+ ) -> str:
234
+ if not self.namespace_key:
235
+ return name
236
+
237
+ try:
238
+ return f"{context.globals[self.namespace_key]}/{name}"
239
+ except KeyError:
240
+ return name
@@ -0,0 +1,108 @@
1
+ """A template loader that reads templates from Python packages."""
2
+ from __future__ import annotations
3
+
4
+ import asyncio
5
+ import os
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING
8
+ from typing import Iterable
9
+ from typing import Union
10
+
11
+ from importlib_resources import files
12
+
13
+ from liquid.exceptions import TemplateNotFound
14
+
15
+ from .base_loader import BaseLoader
16
+ from .base_loader import TemplateSource
17
+
18
+ if TYPE_CHECKING:
19
+ from types import ModuleType
20
+
21
+ from importlib_resources.abc import Traversable
22
+
23
+ from liquid import Environment
24
+
25
+
26
+ class PackageLoader(BaseLoader):
27
+ """A template loader that reads templates from Python packages.
28
+
29
+ Args:
30
+ package: Import name of a package containing Liquid templates.
31
+ package_path: One or more directories in the package containing Liquid
32
+ templates.
33
+ encoding: Encoding of template files.
34
+ ext: A default file extension to use if one is not provided. Should
35
+ include a leading period.
36
+
37
+ _New in version 1.11.0._
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ package: Union[str, ModuleType],
43
+ *,
44
+ package_path: Union[str, Iterable[str]] = "templates",
45
+ encoding: str = "utf-8",
46
+ ext: str = ".liquid",
47
+ ) -> None:
48
+ if isinstance(package_path, str):
49
+ self.paths = [files(package).joinpath(package_path)]
50
+ else:
51
+ _package = files(package)
52
+ self.paths = [_package.joinpath(path) for path in package_path]
53
+
54
+ self.encoding = encoding
55
+ self.ext = ext
56
+
57
+ def _resolve_path(self, template_name: str) -> Traversable:
58
+ template_path = Path(template_name)
59
+
60
+ # Don't build a path that escapes package/package_path.
61
+ # Does ".." appear in template_name?
62
+ if os.path.pardir in template_path.parts:
63
+ raise TemplateNotFound(template_name)
64
+
65
+ # Add suffix self.ext if template name does not have a suffix.
66
+ if not template_path.suffix:
67
+ template_path = template_path.with_suffix(self.ext)
68
+
69
+ for path in self.paths:
70
+ source_path = path.joinpath(template_path)
71
+ if source_path.is_file():
72
+ # MyPy seems to think source_path has `Any` type :(
73
+ return source_path # type: ignore
74
+
75
+ raise TemplateNotFound(template_name)
76
+
77
+ def get_source( # noqa: D102
78
+ self,
79
+ _: Environment,
80
+ template_name: str,
81
+ ) -> TemplateSource:
82
+ source_path = self._resolve_path(template_name)
83
+ return TemplateSource(
84
+ source=source_path.read_text(self.encoding),
85
+ filename=str(source_path),
86
+ uptodate=None,
87
+ )
88
+
89
+ async def get_source_async( # noqa: D102
90
+ self, _: Environment, template_name: str
91
+ ) -> TemplateSource:
92
+ loop = asyncio.get_running_loop()
93
+
94
+ source_path = await loop.run_in_executor(
95
+ None,
96
+ self._resolve_path,
97
+ template_name,
98
+ )
99
+
100
+ source_text = await loop.run_in_executor(
101
+ None,
102
+ source_path.read_text,
103
+ self.encoding,
104
+ )
105
+
106
+ return TemplateSource(
107
+ source=source_text, filename=str(source_path), uptodate=None
108
+ )
liquid/exceptions.py CHANGED
@@ -235,3 +235,7 @@ class Markup(str):
235
235
  raise Error(
236
236
  "autoescape requires Markupsafe to be installed"
237
237
  ) # pragma: no cover
238
+
239
+
240
+ class CacheCapacityValueError(ValueError):
241
+ """An exception raised when the LRU cache is given a zero or negative capacity."""
liquid/expression.py CHANGED
@@ -16,6 +16,7 @@ from typing import Iterable
16
16
  from typing import Iterator
17
17
  from typing import List
18
18
  from typing import Mapping
19
+ from typing import NoReturn
19
20
  from typing import Optional
20
21
  from typing import Tuple
21
22
  from typing import TypeVar
@@ -29,6 +30,7 @@ from liquid.exceptions import FilterValueError
29
30
  from liquid.exceptions import LiquidTypeError
30
31
  from liquid.exceptions import NoSuchFilterFunc
31
32
  from liquid.limits import to_int
33
+ from liquid.undefined import Undefined
32
34
 
33
35
  # ruff: noqa: D102 D101
34
36
 
@@ -1079,7 +1081,7 @@ def eval_number_expression(left: Number, operator: str, right: Number) -> bool:
1079
1081
 
1080
1082
 
1081
1083
  def _is_py_falsy_number(obj: object) -> bool:
1082
- # Liquid 0, 0.0, 0b0, 0X0, 0o0 and Decimal("0") are not falsy.
1084
+ # Liquid 0, 0.0, and Decimal("0") are not falsy.
1083
1085
  return not isinstance(obj, bool) and isinstance(obj, (int, float, Decimal))
1084
1086
 
1085
1087
 
@@ -1090,67 +1092,86 @@ def is_truthy(obj: Any) -> bool:
1090
1092
  return _is_py_falsy_number(obj) or obj not in (False, None)
1091
1093
 
1092
1094
 
1093
- def compare_bool(left: Any, operator: str, right: Any) -> bool:
1094
- """Compare an object to a boolean value."""
1095
- if (isinstance(left, bool) and _is_py_falsy_number(right)) or (
1096
- isinstance(right, bool) and _is_py_falsy_number(left)
1097
- ):
1098
- if operator in ("==", "<", ">", "<=", ">="):
1099
- return False
1100
- if operator in ("!=", "<>"):
1101
- return True
1102
- raise LiquidTypeError(
1103
- f"unknown operator: {type(left)} {operator} {type(right)}"
1104
- )
1105
-
1106
- if operator == "==":
1107
- return bool(left == right)
1108
- if operator in ("!=", "<>"):
1109
- return bool(left != right)
1110
- if operator in ("<", ">", "<=", ">="):
1111
- return False
1112
-
1113
- raise LiquidTypeError(f"unknown operator: {type(left)} {operator} {type(right)}")
1114
-
1115
-
1116
- def compare(left: Any, operator: str, right: Any) -> bool: # noqa: PLR0911, PLR0912
1117
- """Return the result of a comparison operation between two objects."""
1118
- if operator == "and":
1095
+ def compare(left: object, op: str, right: object) -> bool: # noqa: PLR0911, PLR0912
1096
+ """Compare _left_ with _right_ according to Liquid semantics."""
1097
+ if op == "and":
1119
1098
  return is_truthy(left) and is_truthy(right)
1120
- if operator == "or":
1099
+ if op == "or":
1121
1100
  return is_truthy(left) or is_truthy(right)
1122
1101
 
1123
1102
  if hasattr(left, "__liquid__"):
1124
1103
  left = left.__liquid__()
1104
+
1125
1105
  if hasattr(right, "__liquid__"):
1126
1106
  right = right.__liquid__()
1127
1107
 
1128
- if isinstance(right, (Empty, Blank)):
1129
- left, right = right, left
1130
-
1131
- if isinstance(left, bool) or isinstance(right, bool):
1132
- return compare_bool(left, operator, right)
1108
+ def _type_error(_left: object, _right: object) -> NoReturn:
1109
+ if type(_left) != type(_right):
1110
+ raise LiquidTypeError(f"invalid operator for types '{_left} {op} {_right}'")
1133
1111
 
1134
- if operator == "==":
1135
- return bool(left == right)
1136
- if operator in ("!=", "<>"):
1137
- return bool(left != right)
1112
+ raise LiquidTypeError(f"unknown operator: {type(_left)} {op} {type(_right)}")
1138
1113
 
1139
- if operator == "contains":
1114
+ if op == "==":
1115
+ return _eq(left, right)
1116
+ if op == "!=":
1117
+ return not _eq(left, right)
1118
+ if op == "<>":
1119
+ return not _eq(left, right)
1120
+ if op == "<":
1121
+ try:
1122
+ return _lt(left, right)
1123
+ except TypeError:
1124
+ _type_error(left, right)
1125
+ if op == ">":
1126
+ try:
1127
+ return _lt(right, left)
1128
+ except TypeError:
1129
+ _type_error(right, left)
1130
+ if op == ">=":
1131
+ try:
1132
+ return _lt(right, left) or _eq(left, right)
1133
+ except TypeError:
1134
+ _type_error(right, left)
1135
+ if op == "<=":
1136
+ try:
1137
+ return _lt(left, right) or _eq(left, right)
1138
+ except TypeError:
1139
+ _type_error(left, right)
1140
+ if op == "contains":
1140
1141
  if isinstance(left, str):
1141
1142
  return str(right) in left
1142
1143
  if isinstance(left, (list, dict)):
1143
1144
  return right in left
1145
+ if isinstance(left, Undefined):
1146
+ return False
1144
1147
 
1145
- if None in (left, right):
1146
- return False
1148
+ return _type_error(left, right)
1147
1149
 
1148
- if type(left) in (int, float) and type(right) in (int, float):
1149
- return eval_number_expression(left, operator, right)
1150
1150
 
1151
- if type(left) != type(right):
1152
- raise LiquidTypeError(
1153
- f"invalid operator for types '{str(left)} {operator} {str(right)}'"
1154
- )
1151
+ def _eq(left: object, right: object) -> bool:
1152
+ if isinstance(right, (Empty, Blank)):
1153
+ left, right = right, left
1154
+
1155
+ # Remember 1 == True and 0 == False in Python
1156
+ if isinstance(right, bool):
1157
+ left, right = right, left
1158
+
1159
+ if isinstance(left, bool):
1160
+ return isinstance(right, bool) and left == right
1161
+
1162
+ return left == right
1163
+
1164
+
1165
+ def _lt(left: object, right: object) -> bool:
1166
+ if isinstance(left, str) and isinstance(right, str):
1167
+ return left < right
1168
+
1169
+ if isinstance(left, bool) or isinstance(right, bool):
1170
+ return False
1171
+
1172
+ if isinstance(left, (int, float, Decimal)) and isinstance(
1173
+ right, (int, float, Decimal)
1174
+ ):
1175
+ return left < right
1155
1176
 
1156
- raise LiquidTypeError(f"unknown operator: {type(left)} {operator} {type(right)}")
1177
+ raise TypeError
liquid/golden/if_tag.py CHANGED
@@ -219,4 +219,52 @@ cases = [
219
219
  expect="false",
220
220
  error=True,
221
221
  ),
222
+ Case(
223
+ description="string is less than string",
224
+ template="{% if 'abc' < 'acb' %}true{% else %}false{% endif %}",
225
+ globals={},
226
+ expect="true",
227
+ ),
228
+ Case(
229
+ description="string is not less than string",
230
+ template="{% if 'bbb' < 'aaa' %}true{% else %}false{% endif %}",
231
+ globals={},
232
+ expect="false",
233
+ ),
234
+ Case(
235
+ description="string is less than or equal to string",
236
+ template="{% if 'abc' <= 'acb' %}true{% else %}false{% endif %}",
237
+ globals={},
238
+ expect="true",
239
+ ),
240
+ Case(
241
+ description="string is not less than or equal to string",
242
+ template="{% if 'bbb' <= 'aaa' %}true{% else %}false{% endif %}",
243
+ globals={},
244
+ expect="false",
245
+ ),
246
+ Case(
247
+ description="string is greater than string",
248
+ template="{% if 'abc' > 'acb' %}true{% else %}false{% endif %}",
249
+ globals={},
250
+ expect="false",
251
+ ),
252
+ Case(
253
+ description="string is not greater than string",
254
+ template="{% if 'bbb' > 'aaa' %}true{% else %}false{% endif %}",
255
+ globals={},
256
+ expect="true",
257
+ ),
258
+ Case(
259
+ description="string is greater than or equal to string",
260
+ template="{% if 'abc' >= 'acb' %}true{% else %}false{% endif %}",
261
+ globals={},
262
+ expect="false",
263
+ ),
264
+ Case(
265
+ description="string is not greater than or equal to string",
266
+ template="{% if 'bbb' >= 'aaa' %}true{% else %}false{% endif %}",
267
+ globals={},
268
+ expect="true",
269
+ ),
222
270
  ]
liquid/loaders.py CHANGED
@@ -1,21 +1,29 @@
1
1
  """Built-in loaders."""
2
2
  from .builtin.loaders import BaseLoader
3
+ from .builtin.loaders import CachingChoiceLoader
3
4
  from .builtin.loaders import CachingFileSystemLoader
4
5
  from .builtin.loaders import ChoiceLoader
5
6
  from .builtin.loaders import DictLoader
6
7
  from .builtin.loaders import FileExtensionLoader
7
8
  from .builtin.loaders import FileSystemLoader
9
+ from .builtin.loaders import PackageLoader
8
10
  from .builtin.loaders import TemplateNamespace
9
11
  from .builtin.loaders import TemplateSource
10
12
  from .builtin.loaders import UpToDate
13
+ from .builtin.loaders import make_choice_loader
14
+ from .builtin.loaders import make_file_system_loader
11
15
 
12
16
  __all__ = (
13
17
  "BaseLoader",
18
+ "CachingChoiceLoader",
14
19
  "CachingFileSystemLoader",
15
20
  "ChoiceLoader",
16
21
  "DictLoader",
17
22
  "FileExtensionLoader",
18
23
  "FileSystemLoader",
24
+ "make_choice_loader",
25
+ "make_file_system_loader",
26
+ "PackageLoader",
19
27
  "TemplateNamespace",
20
28
  "TemplateSource",
21
29
  "UpToDate",
liquid/utils/cache.py CHANGED
@@ -36,6 +36,8 @@ from collections import abc
36
36
  from collections import deque
37
37
  from threading import Lock
38
38
 
39
+ from liquid.exceptions import CacheCapacityValueError
40
+
39
41
 
40
42
  class LRUCache(abc.MutableMapping):
41
43
  """A simple LRU Cache implementation."""
@@ -45,6 +47,9 @@ class LRUCache(abc.MutableMapping):
45
47
  # won't do any harm.
46
48
 
47
49
  def __init__(self, capacity: int):
50
+ if capacity < 1:
51
+ raise CacheCapacityValueError("cache size must be greater than 1")
52
+
48
53
  self.capacity = capacity
49
54
  self._mapping = {}
50
55
  self._queue = deque()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-liquid
3
- Version: 1.10.2
3
+ Version: 1.12.0
4
4
  Summary: A Python engine for the Liquid template language.
5
5
  Project-URL: Change Log, https://github.com/jg-rp/liquid/blob/main/CHANGES.md
6
6
  Project-URL: Documentation, https://jg-rp.github.io/liquid/
@@ -19,9 +19,11 @@ Classifier: Programming Language :: Python :: 3.8
19
19
  Classifier: Programming Language :: Python :: 3.9
20
20
  Classifier: Programming Language :: Python :: 3.10
21
21
  Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
22
23
  Classifier: Programming Language :: Python :: Implementation :: CPython
23
24
  Classifier: Programming Language :: Python :: Implementation :: PyPy
24
25
  Requires-Python: >=3.7
26
+ Requires-Dist: importlib-resources>=5.10.0
25
27
  Requires-Dist: python-dateutil>=2.8.1
26
28
  Requires-Dist: typing-extensions>=4.2.0
27
29
  Provides-Extra: autoescape
@@ -32,8 +34,8 @@ Description-Content-Type: text/markdown
32
34
 
33
35
  <p align="center">
34
36
  A Python engine for <a href="https://shopify.github.io/liquid/">Liquid</a>, the safe customer-facing template language for flexible web apps.
37
+ <br>We follow <a href="https://github.com/Shopify/liquid">Shopify/Liquid</a> closely and test against the <a href="https://github.com/jg-rp/golden-liquid">Golden Liquid</a> test suite.
35
38
  </p>
36
-
37
39
  <p align="center">
38
40
  <a href="https://github.com/jg-rp/liquid/blob/main/LICENSE">
39
41
  <img src="https://img.shields.io/pypi/l/python-liquid.svg?style=flat-square" alt="License">
@@ -59,6 +61,10 @@ A Python engine for <a href="https://shopify.github.io/liquid/">Liquid</a>, the
59
61
  <a href="https://github.com/jg-rp/liquid/actions/workflows/coverage.yaml">
60
62
  <img src="https://img.shields.io/github/actions/workflow/status/jg-rp/liquid/coverage.yaml?branch=main&label=coverage&style=flat-square" alt="Coverage">
61
63
  </a>
64
+ <br>
65
+ <a href="https://pypi.org/project/python-liquid/">
66
+ <img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dm/python-liquid?style=flat-square">
67
+ </a>
62
68
  </p>
63
69
 
64
70
  ---
@@ -1,15 +1,15 @@
1
- liquid/__init__.py,sha256=UaDLh_elcxm5c7mDFqlyuG1l06SMw7f0s-4MVDtdhY0,1907
1
+ liquid/__init__.py,sha256=0nYsF01jFXyXrxYCLnglEp0H4H7fHG7_tyh91X4td5o,2173
2
2
  liquid/analyze_tags.py,sha256=PKMBk4lFWNwkfuMY9ulou6QeSq676IgQmC88JqfeO0k,7503
3
3
  liquid/ast.py,sha256=zawW4ryxo_0ExGKpMYUifbtD7F5SlmlMrDJvCVTfWCM,8313
4
4
  liquid/chain_map.py,sha256=nxkw3wwF6ddlGarIuL7Ii2elm4dU80LySgdQx1oift0,1517
5
5
  liquid/context.py,sha256=cHn0IYhtOM3xlujn1M0fY0KO9WMS8sMNj9AWjTxicZg,26261
6
6
  liquid/environment.py,sha256=9PhcexMoKW30pmFzfo8so7pZhweO_WCk6SRQp0w-jdo,30780
7
- liquid/exceptions.py,sha256=7Rd7FOj6CntEv1EtV8xKH2caQgBbETq9Bimok51lvqg,6559
8
- liquid/expression.py,sha256=MU2QtquzqRMzAUiLZtW1xpTPvbckr8eGlTFaMtte59k,34912
7
+ liquid/exceptions.py,sha256=gbqQcwJmChp6FCGQdxTWo0mstQtAqmp3-MkUT-evlFI,6691
8
+ liquid/expression.py,sha256=NwI8ZIbfqY8HejYZJiNTRCdu_KvEkPtDD9_PIepsmsk,35252
9
9
  liquid/filter.py,sha256=AOBC4cU4eLLiiUvLfb9L0zZvR38ln0BSKqlIy41AUd4,6315
10
10
  liquid/lex.py,sha256=pizKyPdIqRNT6TN5NolRhBI3oBpTyoYwJS_DCyyQhg8,15137
11
11
  liquid/limits.py,sha256=N3InvvDMg9lTVc6vUhKftV206Oz8D3Pmg_a_jZtgyro,1675
12
- liquid/loaders.py,sha256=xMBHI5PEgGkP-326BrTcjnKZKhq3KNldtmJyjAXzVpI,639
12
+ liquid/loaders.py,sha256=aXh-hiDyYV6gaBEWRkUORawkhJ4hbBwXP8Sbv9c4j7c,937
13
13
  liquid/mode.py,sha256=nYm-oYRkcZk1j-pmYPXMBqfQMMfYYB13SHI2OWHHFr0,207
14
14
  liquid/output.py,sha256=QEL_dg4Opb63W0pv1P-4IpUX36uAtuJf9sKxZbVLGxk,773
15
15
  liquid/parse.py,sha256=50BT569pjW1sYAofiJ2SVzZgzKlma9lKPStF7EPfE1w,27131
@@ -33,11 +33,13 @@ liquid/builtin/filters/extra.py,sha256=7_8mUD6DlNLBti-TVCeqzI2NpCmua_OJrwYa5re6c
33
33
  liquid/builtin/filters/math.py,sha256=ZEzeX5Rw9gTTtIEnR951MXl_1Fv9j2ntfM9EDzmSYlU,3804
34
34
  liquid/builtin/filters/misc.py,sha256=z4DXSqidAqZ0F3W5Kr__mebAghDmzYzGr4BzE5-tsFY,3436
35
35
  liquid/builtin/filters/string.py,sha256=GJMl2nFcygAz73snVsud4lX0Gk0VhwGbhldeX558SSo,9964
36
- liquid/builtin/loaders/__init__.py,sha256=L9EcC-gJua6aO5Mbw9RXJmxJly7dAayLnMUX9_kHPhM,613
37
- liquid/builtin/loaders/base_loader.py,sha256=2549s2rP8IsLY25-L5AJklkSW_EEAno2Vm-CdCyi2Qk,9447
38
- liquid/builtin/loaders/caching_file_system_loader.py,sha256=50lCTRxaIdF7TocyCBdOp1VMPm1c3aH70gUNwQSj6X4,7261
39
- liquid/builtin/loaders/choice_loader.py,sha256=LJcC1xOZZdDAv4mkkh4FXWHvlxrepihJUrZTg5ZNfxU,1353
36
+ liquid/builtin/loaders/__init__.py,sha256=9B7KAc-HoNd-pUNwJ-Syzr_8JsBD2S7feYwulUWtrtk,4309
37
+ liquid/builtin/loaders/base_loader.py,sha256=ks_tfzx1t0Dg2gW0Ewr-jHn4KKd5oUWXdREh_n2zHEY,9509
38
+ liquid/builtin/loaders/caching_file_system_loader.py,sha256=3_rGFWv2A3caYmGiaXqjJF-kqEFnU5oZj9R95y1Qc3w,2681
39
+ liquid/builtin/loaders/choice_loader.py,sha256=89rXKA6LR66BqA5ZQEOM9JwRIjPdLUtb3_DyJel8C4I,4566
40
40
  liquid/builtin/loaders/file_system_loader.py,sha256=z6nSZih0fDn5aUCWaPlBMqKgAPaGj2NxyZOPHCfTVrg,4396
41
+ liquid/builtin/loaders/mixins.py,sha256=lIlKcjhekb8aoSCuGjJR7mquNP9VjxKUaMu86ONcOSg,6668
42
+ liquid/builtin/loaders/package_loader.py,sha256=xqkl1neXhEPlfjGUlcRzHi3OUpFH4Ht596WOKt04MSw,3234
41
43
  liquid/builtin/tags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
44
  liquid/builtin/tags/assign_tag.py,sha256=0TujfsRE8N8hSjuVpmNQ0HQmmMUbFY89K_mnK2ogdfw,2554
43
45
  liquid/builtin/tags/capture_tag.py,sha256=m9Mo7bxarA-Xq_nsrc70aUZJXRVr3WBvoKmCXepy6wk,3136
@@ -122,7 +124,7 @@ liquid/golden/first_filter.py,sha256=BzyMgvUVZYqDp-qGIXDlncbocXmO4zpnz2gxKCejOfU
122
124
  liquid/golden/floor_filter.py,sha256=hL7aonQiADdMhvIYRi5NY90MV4GjTSPEVk3ju3xudGQ,1474
123
125
  liquid/golden/for_tag.py,sha256=FGUwzLQef_ibBwSszddA-DIMNAesSfyO6CC8TuqYbHE,20459
124
126
  liquid/golden/identifiers.py,sha256=XyGUFaMHdvW_q13JrloLaY8AwhSFGIpM78YHeqkeOUc,7364
125
- liquid/golden/if_tag.py,sha256=amdJsivN-EmXXnrfEazNXTuikWcepYAh-Db2e4KYVoc,6629
127
+ liquid/golden/if_tag.py,sha256=YmdlIHVGRYZq0lbqFqZtnt8gieZfbr5jh9S-m6TiGkU,8177
126
128
  liquid/golden/ifchanged_tag.py,sha256=I048sTPf6p4XHy-RQs6rTVrFkZR1YW9juQxBw7l3i2g,1375
127
129
  liquid/golden/illegal.py,sha256=AyJIrNKf435NXT51KBqXlYTYP1t9WUia4e5Zg6iqY8Y,777
128
130
  liquid/golden/include_tag.py,sha256=ukzCYwIm9SFA4ZOU7QML2JYB_WB_sF1JnWpNii5aIhk,6842
@@ -174,11 +176,11 @@ liquid/golden/url_encode_filter.py,sha256=pT6u0Ib44AgIKeHUdNGgpRZNjZzFwRwkvhjY8w
174
176
  liquid/golden/where_filter.py,sha256=8myDb6AUCMU3qgBihU8-O-MehzeLEXUsXe7q9XGVckg,3858
175
177
  liquid/golden/whitespace_control.py,sha256=uAcIIuE1DFWRQhP3fQmI0S3LumMGlIfvWQMIqbxbWMw,7809
176
178
  liquid/utils/__init__.py,sha256=X8Dc_jIJKRCccurGqHGqin8IJswNUta-cV9XoRANmEQ,230
177
- liquid/utils/cache.py,sha256=VOYltMBLU0FKOEX71EPrG7ZSIBeRkIZL93pfI3R5kQE,6297
179
+ liquid/utils/cache.py,sha256=9fgOrkocCSSQNHNjXmeh0EssK6ZfFmF6pB-0asogSLc,6457
178
180
  liquid/utils/cache.pyi,sha256=4Vgtz5vk9IDPnD43AOnby1kWEJJgGH3fsHgQ-B7qa4M,543
179
181
  liquid/utils/html.py,sha256=47ACnJLEMSRkDgqDLBujmT091pk0EjoapmlZxkzz3D8,1755
180
182
  liquid/utils/text.py,sha256=1SwDECNMaqnnZ05je_AZZgxqzZd6U-mvq5jNU3W1-Qk,841
181
- python_liquid-1.10.2.dist-info/METADATA,sha256=VNMKyq_n78_TVXvP838aP9VzU_zPPOyDWpT2nOVVu0Y,7478
182
- python_liquid-1.10.2.dist-info/WHEEL,sha256=rKV0FgtKSxe-Q3jTBlmgn27qdhTm0_K1YKwPwAPskZ0,87
183
- python_liquid-1.10.2.dist-info/licenses/LICENSE,sha256=yAFURzud5ERNHt1rZIPnTLJ92ep7q8y5yG9g5DUMR_E,1075
184
- python_liquid-1.10.2.dist-info/RECORD,,
183
+ python_liquid-1.12.0.dist-info/METADATA,sha256=z32jYN1wGgKeuwpN8lzbgd1Ck3furve1KTkBD7FARPE,7925
184
+ python_liquid-1.12.0.dist-info/WHEEL,sha256=TJPnKdtrSue7xZ_AVGkp9YXcvDrobsjBds1du3Nx6dc,87
185
+ python_liquid-1.12.0.dist-info/licenses/LICENSE,sha256=yAFURzud5ERNHt1rZIPnTLJ92ep7q8y5yG9g5DUMR_E,1075
186
+ python_liquid-1.12.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.20.0
2
+ Generator: hatchling 1.21.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any