python-liquid 2.3.5__tar.gz → 2.3.7__tar.gz

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.
Files changed (97) hide show
  1. {python_liquid-2.3.5 → python_liquid-2.3.7}/PKG-INFO +4 -27
  2. {python_liquid-2.3.5 → python_liquid-2.3.7}/README.md +3 -26
  3. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/__init__.py +1 -1
  4. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/expressions/logical.py +3 -0
  5. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/filters/array.py +23 -33
  6. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/loaders/file_system_loader.py +16 -15
  7. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/loaders/mixins.py +2 -2
  8. {python_liquid-2.3.5 → python_liquid-2.3.7}/.gitignore +0 -0
  9. {python_liquid-2.3.5 → python_liquid-2.3.7}/LICENSE +0 -0
  10. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/analyze_tags.py +0 -0
  11. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/ast.py +0 -0
  12. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/__init__.py +0 -0
  13. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/content.py +0 -0
  14. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/drops/__init__.py +0 -0
  15. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/drops/drops.py +0 -0
  16. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/expressions/__init__.py +0 -0
  17. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/expressions/_tokenize.py +0 -0
  18. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/expressions/arguments.py +0 -0
  19. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/expressions/filtered.py +0 -0
  20. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/expressions/loop.py +0 -0
  21. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/expressions/path.py +0 -0
  22. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/expressions/primitive.py +0 -0
  23. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/filters/__init__.py +0 -0
  24. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/filters/extra.py +0 -0
  25. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/filters/math.py +0 -0
  26. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/filters/misc.py +0 -0
  27. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/filters/string.py +0 -0
  28. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/illegal.py +0 -0
  29. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/loaders/__init__.py +0 -0
  30. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/loaders/caching_file_system_loader.py +0 -0
  31. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/loaders/choice_loader.py +0 -0
  32. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/loaders/dict_loader.py +0 -0
  33. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/loaders/package_loader.py +0 -0
  34. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/output.py +0 -0
  35. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/__init__.py +0 -0
  36. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/assign_tag.py +0 -0
  37. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/capture_tag.py +0 -0
  38. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/case_tag.py +0 -0
  39. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/comment_tag.py +0 -0
  40. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/cycle_tag.py +0 -0
  41. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/decrement_tag.py +0 -0
  42. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/doc_tag.py +0 -0
  43. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/echo_tag.py +0 -0
  44. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/for_tag.py +0 -0
  45. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/if_tag.py +0 -0
  46. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/ifchanged_tag.py +0 -0
  47. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/include_tag.py +0 -0
  48. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/increment_tag.py +0 -0
  49. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/inline_comment_tag.py +0 -0
  50. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/liquid_tag.py +0 -0
  51. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/render_tag.py +0 -0
  52. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/tablerow_tag.py +0 -0
  53. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/builtin/tags/unless_tag.py +0 -0
  54. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/context.py +0 -0
  55. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/environment.py +0 -0
  56. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/exceptions.py +0 -0
  57. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/expression.py +0 -0
  58. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/extra/__init__.py +0 -0
  59. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/extra/filters/__init__.py +0 -0
  60. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/extra/filters/_json.py +0 -0
  61. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/extra/filters/array.py +0 -0
  62. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/extra/filters/babel.py +0 -0
  63. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/extra/filters/html.py +0 -0
  64. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/extra/filters/translate.py +0 -0
  65. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/extra/tags/__init__.py +0 -0
  66. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/extra/tags/_with.py +0 -0
  67. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/extra/tags/extends_tag.py +0 -0
  68. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/extra/tags/macro_tag.py +0 -0
  69. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/extra/tags/snippet_tag.py +0 -0
  70. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/extra/tags/translate_tag.py +0 -0
  71. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/filter.py +0 -0
  72. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/future/__init__.py +0 -0
  73. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/future/environment.py +0 -0
  74. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/future/filters/__init__.py +0 -0
  75. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/future/tags/__init__.py +0 -0
  76. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/lex.py +0 -0
  77. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/limits.py +0 -0
  78. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/loader.py +0 -0
  79. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/messages.py +0 -0
  80. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/mode.py +0 -0
  81. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/output.py +0 -0
  82. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/parser.py +0 -0
  83. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/py.typed +0 -0
  84. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/span.py +0 -0
  85. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/static_analysis.py +0 -0
  86. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/stream.py +0 -0
  87. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/stringify.py +0 -0
  88. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/tag.py +0 -0
  89. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/template.py +0 -0
  90. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/token.py +0 -0
  91. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/undefined.py +0 -0
  92. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/utils/__init__.py +0 -0
  93. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/utils/chain_map.py +0 -0
  94. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/utils/html.py +0 -0
  95. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/utils/lru_cache.py +0 -0
  96. {python_liquid-2.3.5 → python_liquid-2.3.7}/liquid/utils/text.py +0 -0
  97. {python_liquid-2.3.5 → python_liquid-2.3.7}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-liquid
3
- Version: 2.3.5
3
+ Version: 2.3.7
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/
@@ -38,32 +38,9 @@ Python Liquid is a Python engine for <a href="https://shopify.github.io/liquid/"
38
38
  <br>
39
39
  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.
40
40
  </p>
41
- <p align="center">
42
- <a href="https://pypi.org/project/python-liquid/">
43
- <img src="https://img.shields.io/pypi/v/python-liquid.svg?style=flat-square" alt="PyPi - Version">
44
- </a>
45
- <a href="https://anaconda.org/conda-forge/python-liquid">
46
- <img src="https://img.shields.io/conda/vn/conda-forge/python-liquid?style=flat-square" alt="conda-forge">
47
- </a>
48
- <a href="https://pypi.org/project/python-liquid/">
49
- <img src="https://img.shields.io/pypi/pyversions/python-liquid.svg?style=flat-square" alt="Python versions">
50
- </a>
51
- <br>
52
- <a href="https://github.com/jg-rp/liquid/actions/workflows/tests.yaml">
53
- <img src="https://img.shields.io/github/actions/workflow/status/jg-rp/liquid/tests.yaml?branch=main&label=tests&style=flat-square" alt="Tests">
54
- </a>
55
- <a href="https://github.com/jg-rp/liquid/actions/workflows/coverage.yaml">
56
- <img src="https://img.shields.io/github/actions/workflow/status/jg-rp/liquid/coverage.yaml?branch=main&label=coverage&style=flat-square" alt="Coverage">
57
- </a>
58
- <br>
59
- <a href="https://pypi.org/project/python-liquid/">
60
- <img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dm/python-liquid?style=flat-square">
61
- </a>
62
- <br>
63
- <a href="https://github.com/jg-rp/liquid/blob/main/LICENSE">
64
- <img src="https://img.shields.io/pypi/l/python-liquid.svg?style=flat-square" alt="License">
65
- </a>
66
- </p>
41
+
42
+ > [!WARNING]
43
+ > This is the v2 branch of Python Liquid.
67
44
 
68
45
  ---
69
46
 
@@ -5,32 +5,9 @@ Python Liquid is a Python engine for <a href="https://shopify.github.io/liquid/"
5
5
  <br>
6
6
  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.
7
7
  </p>
8
- <p align="center">
9
- <a href="https://pypi.org/project/python-liquid/">
10
- <img src="https://img.shields.io/pypi/v/python-liquid.svg?style=flat-square" alt="PyPi - Version">
11
- </a>
12
- <a href="https://anaconda.org/conda-forge/python-liquid">
13
- <img src="https://img.shields.io/conda/vn/conda-forge/python-liquid?style=flat-square" alt="conda-forge">
14
- </a>
15
- <a href="https://pypi.org/project/python-liquid/">
16
- <img src="https://img.shields.io/pypi/pyversions/python-liquid.svg?style=flat-square" alt="Python versions">
17
- </a>
18
- <br>
19
- <a href="https://github.com/jg-rp/liquid/actions/workflows/tests.yaml">
20
- <img src="https://img.shields.io/github/actions/workflow/status/jg-rp/liquid/tests.yaml?branch=main&label=tests&style=flat-square" alt="Tests">
21
- </a>
22
- <a href="https://github.com/jg-rp/liquid/actions/workflows/coverage.yaml">
23
- <img src="https://img.shields.io/github/actions/workflow/status/jg-rp/liquid/coverage.yaml?branch=main&label=coverage&style=flat-square" alt="Coverage">
24
- </a>
25
- <br>
26
- <a href="https://pypi.org/project/python-liquid/">
27
- <img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dm/python-liquid?style=flat-square">
28
- </a>
29
- <br>
30
- <a href="https://github.com/jg-rp/liquid/blob/main/LICENSE">
31
- <img src="https://img.shields.io/pypi/l/python-liquid.svg?style=flat-square" alt="License">
32
- </a>
33
- </p>
8
+
9
+ > [!WARNING]
10
+ > This is the v2 branch of Python Liquid.
34
11
 
35
12
  ---
36
13
 
@@ -56,7 +56,7 @@ from .tag import Tag
56
56
 
57
57
  from . import future
58
58
 
59
- __version__ = "2.3.5"
59
+ __version__ = "2.3.7"
60
60
 
61
61
  __all__ = (
62
62
  "AwareBoundTemplate",
@@ -618,6 +618,9 @@ def _lt(token: Token, left: object, right: object) -> bool:
618
618
  if isinstance(left, str) and isinstance(right, str):
619
619
  return left < right
620
620
 
621
+ if isinstance(left, bool) or isinstance(right, bool):
622
+ return False
623
+
621
624
  if isinstance(left, (int, float, Decimal)) and isinstance(
622
625
  right, (int, float, Decimal)
623
626
  ):
@@ -73,7 +73,7 @@ def first(left: Any, *, environment: Environment) -> object:
73
73
  return None
74
74
 
75
75
  if isinstance(left, dict):
76
- left = list(islice(left.items(), 1))
76
+ return next(islice(left.items(), 1), None) # pyright: ignore[reportUnknownVariableType, reportUnknownArgumentType]
77
77
 
78
78
  try:
79
79
  return getitem(left, 0)
@@ -101,17 +101,18 @@ def last(obj: Sequence[Any], *, environment: Environment) -> object:
101
101
 
102
102
 
103
103
  @sequence_filter
104
- def concat(sequence: ArrayT, second_array: ArrayT) -> ArrayT:
104
+ def concat(sequence: ArrayT, second_array: object) -> ArrayT:
105
105
  """Return the concatenation of _sequence_ and _second_array_."""
106
106
  if not isinstance(second_array, (list, tuple)):
107
107
  raise FilterArgumentError(
108
- f"concat expected an array, found {type(second_array).__name__}"
108
+ f"concat expected an array, found {type(second_array).__name__}",
109
+ token=None,
109
110
  )
110
111
 
111
112
  if is_undefined(sequence):
112
- return second_array
113
+ return second_array # pyright: ignore[reportUnknownVariableType]
113
114
 
114
- return list(chain(sequence, second_array))
115
+ return list(chain(sequence, second_array)) # pyright: ignore[reportUnknownArgumentType]
115
116
 
116
117
 
117
118
  @sequence_filter
@@ -166,7 +167,7 @@ def where(sequence: ArrayT, attr: object, value: object = None) -> list[object]:
166
167
  if value is not None and not is_undefined(value):
167
168
  return [itm for itm in sequence if _getitem(itm, attr) == value]
168
169
 
169
- return [itm for itm in sequence if _getitem(itm, attr) not in (False, None)]
170
+ return [itm for itm in sequence if _truthy(_getitem(itm, attr))]
170
171
 
171
172
 
172
173
  @sequence_filter
@@ -178,7 +179,7 @@ def reject(sequence: ArrayT, attr: object, value: object = None) -> list[object]
178
179
  if value is not None and not is_undefined(value):
179
180
  return [itm for itm in sequence if _getitem(itm, attr) != value]
180
181
 
181
- return [itm for itm in sequence if _getitem(itm, attr) in (False, None)]
182
+ return [itm for itm in sequence if not _truthy(_getitem(itm, attr))]
182
183
 
183
184
 
184
185
  @sequence_filter
@@ -187,9 +188,7 @@ def find(sequence: ArrayT, attr: object, value: object = None) -> object:
187
188
  if value is not None and not is_undefined(value):
188
189
  return next((itm for itm in sequence if _getitem(itm, attr) == value), None)
189
190
 
190
- return next(
191
- (itm for itm in sequence if _getitem(itm, attr) not in (False, None)), None
192
- )
191
+ return next((itm for itm in sequence if _truthy(_getitem(itm, attr))), None)
193
192
 
194
193
 
195
194
  @sequence_filter
@@ -224,30 +223,11 @@ def has(sequence: ArrayT, attr: object, value: object = None) -> bool:
224
223
  @sequence_filter
225
224
  def uniq(sequence: ArrayT, key: object = None) -> list[object]:
226
225
  """Return a copy of _sequence_ with duplicate elements removed."""
227
- # Note that we're not using a dict or set for deduplication because we need
228
- # to handle sequences containing unhashable objects, like dictionaries.
229
-
230
- # This is probably quite slow.
231
- if key is not None:
232
- keys = []
233
- result = []
234
- for obj in sequence:
235
- try:
236
- item = obj[key]
237
- except KeyError:
238
- item = MISSING
239
- except TypeError as err:
240
- raise FilterArgumentError(
241
- f"can't read property '{key}' of {obj}", token=None
242
- ) from err
243
-
244
- if item not in keys:
245
- keys.append(item)
246
- result.append(obj)
226
+ if _nil(key):
227
+ return [obj for i, obj in enumerate(sequence) if sequence.index(obj) == i]
247
228
 
248
- return result
249
-
250
- return [obj for i, obj in enumerate(sequence) if sequence.index(obj) == i]
229
+ keys = [_getitem(obj, key) for obj in sequence]
230
+ return [sequence[i] for i, key in enumerate(keys) if keys.index(key) == i]
251
231
 
252
232
 
253
233
  @sequence_filter
@@ -327,3 +307,13 @@ class _Null:
327
307
 
328
308
 
329
309
  _NULL = _Null()
310
+
311
+
312
+ def _nil(obj: object) -> bool:
313
+ # Liquid nullish-ness
314
+ return obj is None or is_undefined(obj) or obj is _NULL
315
+
316
+
317
+ def _truthy(obj: object) -> bool:
318
+ # Liquid truthiness. 0 is truthy.
319
+ return obj is not False and obj is not None and not is_undefined(obj)
@@ -6,6 +6,7 @@ import asyncio
6
6
  import os
7
7
  from functools import partial
8
8
  from pathlib import Path
9
+ from pathlib import PurePath
9
10
  from typing import TYPE_CHECKING
10
11
  from typing import Iterable
11
12
  from typing import Optional
@@ -62,7 +63,9 @@ class FileSystemLoader(BaseLoader):
62
63
  _template_name_ exists. If none of the search paths contain _template_name_, a
63
64
  _TemplateNotFound_ exception is raised.
64
65
  """
65
- template_path = Path(template_name)
66
+ template_path = PurePath(template_name)
67
+ if template_path.anchor:
68
+ template_path = PurePath(*template_path.parts[1:])
66
69
 
67
70
  if not template_path.name:
68
71
  raise TemplateNotFoundError(template_name)
@@ -74,26 +77,24 @@ class FileSystemLoader(BaseLoader):
74
77
  raise TemplateNotFoundError(template_name)
75
78
 
76
79
  for base in self.search_path:
77
- source_path = base.joinpath(template_path)
80
+ base_resolved = base.resolve()
81
+ source_path = (base_resolved / template_path).resolve()
78
82
 
79
- try:
80
- if not source_path.exists() or not source_path.is_file():
81
- continue
82
- except OSError:
83
- # "File name too long", for example
83
+ if not source_path.is_relative_to(base_resolved):
84
84
  continue
85
85
 
86
86
  if self.reject_symlinks:
87
- try:
88
- resolved = source_path.resolve(strict=False)
89
- base_resolved = base.resolve(strict=False)
90
- except OSError:
91
- continue
87
+ current = base_resolved
88
+ for part in template_path.parts:
89
+ current /= part
90
+ if current.is_symlink():
91
+ continue
92
92
 
93
- if not resolved.is_relative_to(base_resolved):
94
- continue
93
+ if not source_path.is_file() or source_path.is_dir():
94
+ continue
95
95
 
96
- return source_path
96
+ # We don't want to expose a fully qualified path in error messages.
97
+ return base / template_path
97
98
 
98
99
  raise TemplateNotFoundError(template_name)
99
100
 
@@ -150,12 +150,12 @@ class CachingLoaderMixin(ABC, _CachingLoaderProtocol):
150
150
  cache_key = self.cache_key(name, context, kwargs)
151
151
  return await self._check_cache_async(
152
152
  env,
153
- name,
153
+ cache_key,
154
154
  globals,
155
155
  partial(
156
156
  super().load_async, # type: ignore
157
157
  env,
158
- cache_key,
158
+ name,
159
159
  globals=globals,
160
160
  context=context,
161
161
  **kwargs,
File without changes
File without changes