python-liquid 2.3.0__py3-none-any.whl → 2.3.2__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
@@ -56,7 +56,7 @@ from .tag import Tag
56
56
 
57
57
  from . import future
58
58
 
59
- __version__ = "2.3.0"
59
+ __version__ = "2.3.2"
60
60
 
61
61
  __all__ = (
62
62
  "AwareBoundTemplate",
@@ -13,6 +13,7 @@ from typing import Union
13
13
  from liquid import Mode
14
14
  from liquid.exceptions import LiquidSyntaxError
15
15
  from liquid.exceptions import LiquidTypeError
16
+ from liquid.exceptions import LiquidValueError
16
17
  from liquid.expression import Expression
17
18
  from liquid.limits import to_int
18
19
  from liquid.token import TOKEN_ASSIGN
@@ -95,7 +96,12 @@ class LoopExpression(Expression):
95
96
  if isinstance(obj, Mapping):
96
97
  return iter(obj.items()), len(obj)
97
98
  if isinstance(obj, range):
98
- return iter(obj), len(obj)
99
+ try:
100
+ return iter(obj), len(obj)
101
+ except OverflowError as err:
102
+ raise LiquidValueError(
103
+ "range literal length limit reached", token=None
104
+ ) from err
99
105
  if isinstance(obj, str) and not context.env.string_sequences:
100
106
  return (iter([]), 0) if not obj else (iter([obj]), 1)
101
107
  if isinstance(obj, Sequence):
@@ -39,12 +39,6 @@ if TYPE_CHECKING:
39
39
  from liquid import TokenStream
40
40
 
41
41
 
42
- # MAX_RANGE = (2**53) - 1
43
- # MIN_RANGE = -(2**53) + 1
44
- MAX_RANGE = 1024
45
- MIN_RANGE = -1024
46
-
47
-
48
42
  class Nil(Expression):
49
43
  __slots__ = ()
50
44
 
@@ -255,9 +249,7 @@ class RangeLiteral(Expression):
255
249
  if start > stop:
256
250
  return range(0)
257
251
 
258
- return range(
259
- clamp(start, MIN_RANGE, MAX_RANGE), clamp(stop + 1, MIN_RANGE, MAX_RANGE)
260
- )
252
+ return range(start, stop + 1)
261
253
 
262
254
  def evaluate(self, context: RenderContext) -> range:
263
255
  return self._make_range(
@@ -423,8 +415,3 @@ def parse_string_or_path(
423
415
  def is_empty(obj: object) -> bool:
424
416
  """Return True if _obj_ is considered empty."""
425
417
  return isinstance(obj, (list, dict, str)) and not obj
426
-
427
-
428
- def clamp(n: int, min_value: int, max_value: int) -> int:
429
- """Clamp _n_ between _min_value_ and _max_value_."""
430
- return max(min_value, min(n, max_value))
@@ -59,13 +59,17 @@ def join(
59
59
  return separator.join(_str_if_not(item) for item in left)
60
60
 
61
61
 
62
+ @with_environment
62
63
  @liquid_filter
63
- def first(left: Any) -> object:
64
+ def first(left: Any, *, environment: Environment) -> object:
64
65
  """Return the first item in collection _left_.
65
66
 
66
67
  If _left_ is not a collection or it is empty, `None` is returned.
67
68
  """
68
69
  if isinstance(left, str):
70
+ # Honor the opt-in Ruby-compat flag, matching the `.first` property path.
71
+ if environment.string_first_and_last:
72
+ return left[0] if left else None
69
73
  return None
70
74
 
71
75
  if isinstance(left, dict):
@@ -77,13 +81,17 @@ def first(left: Any) -> object:
77
81
  return None
78
82
 
79
83
 
84
+ @with_environment
80
85
  @liquid_filter
81
- def last(obj: Sequence[Any]) -> object:
86
+ def last(obj: Sequence[Any], *, environment: Environment) -> object:
82
87
  """Return the last item in collection _left_.
83
88
 
84
89
  If _left_ is not a collection or it is empty, `None` is returned.
85
90
  """
86
91
  if isinstance(obj, str):
92
+ # Honor the opt-in Ruby-compat flag, matching the `.last` property path.
93
+ if environment.string_first_and_last:
94
+ return obj[-1] if obj else None
87
95
  return None
88
96
 
89
97
  try:
@@ -4,19 +4,19 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import os
7
- from importlib.resources import files
8
7
  from pathlib import Path
9
8
  from typing import TYPE_CHECKING
10
9
  from typing import Iterable
11
10
  from typing import Optional
12
11
  from typing import Union
13
12
 
13
+ import importlib_resources
14
+
14
15
  from liquid.exceptions import TemplateNotFoundError
15
16
  from liquid.loader import BaseLoader
16
17
  from liquid.loader import TemplateSource
17
18
 
18
19
  if TYPE_CHECKING:
19
- from importlib.abc import Traversable
20
20
  from types import ModuleType
21
21
 
22
22
  from liquid import Environment
@@ -44,31 +44,45 @@ class PackageLoader(BaseLoader):
44
44
  ext: str = ".liquid",
45
45
  ) -> None:
46
46
  if isinstance(package_path, str):
47
- self.paths = [files(package).joinpath(package_path)]
47
+ self.paths = [importlib_resources.files(package).joinpath(package_path)]
48
48
  else:
49
- _package = files(package)
49
+ _package = importlib_resources.files(package)
50
50
  self.paths = [_package.joinpath(path) for path in package_path]
51
51
 
52
52
  self.encoding = encoding
53
53
  self.ext = ext
54
54
 
55
- def _resolve_path(self, template_name: str) -> Traversable:
55
+ def _resolve_path(self, template_name: str) -> Path:
56
56
  template_path = Path(template_name)
57
57
 
58
- # Don't build a path that escapes package/package_path.
59
- # Does ".." appear in template_name?
60
- if os.path.pardir in template_path.parts:
58
+ if not template_path.name:
61
59
  raise TemplateNotFoundError(template_name)
62
60
 
63
61
  # Add suffix self.ext if template name does not have a suffix.
64
62
  if not template_path.suffix:
65
63
  template_path = template_path.with_suffix(self.ext)
66
64
 
65
+ if os.path.pardir in template_path.parts or template_path.is_absolute():
66
+ raise TemplateNotFoundError(template_name)
67
+
67
68
  for path in self.paths:
68
- source_path = path.joinpath(str(template_path))
69
- if source_path.is_file():
70
- # MyPy seems to think source_path has `Any` type :(
71
- return source_path # type: ignore
69
+ with importlib_resources.as_file(path) as base:
70
+ source_path: Path = base.joinpath(template_path)
71
+
72
+ try:
73
+ if not source_path.exists() or not source_path.is_file():
74
+ continue
75
+ except OSError:
76
+ # "File name too long", for example
77
+ continue
78
+
79
+ resolved = source_path.resolve(strict=False)
80
+ base_resolved = base.resolve(strict=False)
81
+
82
+ if not resolved.is_relative_to(base_resolved):
83
+ continue
84
+
85
+ return source_path
72
86
 
73
87
  raise TemplateNotFoundError(template_name)
74
88
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: python-liquid
3
- Version: 2.3.0
3
+ Version: 2.3.2
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/
@@ -24,6 +24,7 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
24
24
  Classifier: Programming Language :: Python :: Implementation :: PyPy
25
25
  Requires-Python: >=3.9
26
26
  Requires-Dist: babel>=2
27
+ Requires-Dist: importlib-resources>=5.12
27
28
  Requires-Dist: markupsafe>=3
28
29
  Requires-Dist: python-dateutil>=2.8.1
29
30
  Requires-Dist: pytz
@@ -1,4 +1,4 @@
1
- liquid/__init__.py,sha256=AAejVPvOWNpGawkdw3_4krnVAmu4SWjcJzbi0N5lhyw,7696
1
+ liquid/__init__.py,sha256=qJCQnUp4SFeIjlrsTo6I07bqqVUFptfOj9xbwHXb3nQ,7696
2
2
  liquid/analyze_tags.py,sha256=Uc1nueKLRiIejs2JyQIC7u2pxp9l-5HJAMWlg-Qj1m8,7615
3
3
  liquid/ast.py,sha256=4ZqSbuW4mv9IvoIMPk3OICUaOI1vPh8P8uFCrXI_3XA,8140
4
4
  liquid/context.py,sha256=fEuMsj9MVguX3Kzy9_k99TWekOwAToL0RtbGm9yZ71o,21718
@@ -33,11 +33,11 @@ liquid/builtin/expressions/_tokenize.py,sha256=p7RbeOg8iwXDnW0fIL4_7FSrBulkPYrCm
33
33
  liquid/builtin/expressions/arguments.py,sha256=hlZh7LLFT5UismTo7mTlVjM87_ihUJo0BYuCxyTwKMI,6448
34
34
  liquid/builtin/expressions/filtered.py,sha256=FqPNZLjwDdlCfivtBa7e0wuQYwcJdKUl3_UY9AS3zG4,13073
35
35
  liquid/builtin/expressions/logical.py,sha256=S8T9IgsuWgv0s7GYcGvIJFOqYMiMFRpat7zF3nCZHR0,20388
36
- liquid/builtin/expressions/loop.py,sha256=ki3kMBuDyT21NZ_VrK19avsGbwBfz0VrrJo-mFox57Q,8594
36
+ liquid/builtin/expressions/loop.py,sha256=F2mBD9_bXtarzmEKstak_u50cXF9ijlKs9Ip2Q_BbsQ,8839
37
37
  liquid/builtin/expressions/path.py,sha256=m1J33uIuEFlUaRXR_-G6qcsClOm0B5jbTZ0Pdt1P7Ak,5703
38
- liquid/builtin/expressions/primitive.py,sha256=AlG828WqP3bZuwQTBJTx6RP2FvBra_aoEsq_J4uR4Tk,11684
38
+ liquid/builtin/expressions/primitive.py,sha256=Gzaeb-dF84zsMn5ZYhe-FOosnSbkkM2C6JLqjLvK5ns,11352
39
39
  liquid/builtin/filters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
- liquid/builtin/filters/array.py,sha256=dMuT15EASXU4ejmNBmawdi92u0f7y_JhqVaGoP48zlg,9843
40
+ liquid/builtin/filters/array.py,sha256=jy35hoouAWI1yG2JX5OMG39Az2dmP--4IcuwqT59JMY,10281
41
41
  liquid/builtin/filters/extra.py,sha256=m13_1nbZyRBl3CZV9WWGLlUZueRPt8cKPEHRAioqM_o,2710
42
42
  liquid/builtin/filters/math.py,sha256=A_K2wg5gxm5Ncj9zMGDX_Lt7jjx4bRr3CywbCiCtqxo,3925
43
43
  liquid/builtin/filters/misc.py,sha256=t2ZsaDLKRc1-8XxZaiEOh7oAMqFJ4N5-tzfbpLHbmIQ,3466
@@ -48,7 +48,7 @@ liquid/builtin/loaders/choice_loader.py,sha256=FE23RLMOiWmsRGZv9t6QmlG7bgMEUSbln
48
48
  liquid/builtin/loaders/dict_loader.py,sha256=8TdMJqaYPLwllmIiEmuZNXqH6UOOsi4HuJ2t5sztOOw,1785
49
49
  liquid/builtin/loaders/file_system_loader.py,sha256=kKPUmznPgNFq3fRDeoOTbWjNwxuLrYPAteZ_Do1-Mks,4924
50
50
  liquid/builtin/loaders/mixins.py,sha256=4Hj5wWGMOi3LqA9ZJYsUb4LFbG0a5DDb9H975G8nn58,5209
51
- liquid/builtin/loaders/package_loader.py,sha256=XP7LT3aMClgfBlI8OMyHOERlXTXEWmqFH7HskCnY3cs,3659
51
+ liquid/builtin/loaders/package_loader.py,sha256=PNg-ygoRjfiALFu3bzEDRdps30rtsTIkt0voJKs9zCo,4061
52
52
  liquid/builtin/tags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
53
  liquid/builtin/tags/assign_tag.py,sha256=VTfkPbF-i0KLqMmaacBItFQHaBTP7ECWR-RvVh9Q6lY,2374
54
54
  liquid/builtin/tags/capture_tag.py,sha256=7bOG7qVwcqRx1eStb96AZKQ9tKLBjmzaYAe27LJTIm4,3037
@@ -90,7 +90,7 @@ liquid/utils/chain_map.py,sha256=nxkw3wwF6ddlGarIuL7Ii2elm4dU80LySgdQx1oift0,151
90
90
  liquid/utils/html.py,sha256=TmqOOpRMsy7fqZLj7X5ybd_XQnWW2YDAVDwTP1Gwf40,1706
91
91
  liquid/utils/lru_cache.py,sha256=p7bXOGaUwJpLsREI2lGSzK6lLna5W_k_zNXKWnPJdos,4022
92
92
  liquid/utils/text.py,sha256=1SwDECNMaqnnZ05je_AZZgxqzZd6U-mvq5jNU3W1-Qk,841
93
- python_liquid-2.3.0.dist-info/METADATA,sha256=OALhkJp1Zn90MKHRGuDDMzV9bOb1kbKN38q-2sqFMmY,6745
94
- python_liquid-2.3.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
95
- python_liquid-2.3.0.dist-info/licenses/LICENSE,sha256=yAFURzud5ERNHt1rZIPnTLJ92ep7q8y5yG9g5DUMR_E,1075
96
- python_liquid-2.3.0.dist-info/RECORD,,
93
+ python_liquid-2.3.2.dist-info/METADATA,sha256=FpoUkgvAUbCL5Xg2He4xOmqVvCU1E_xvZpPn8ci9FYw,6786
94
+ python_liquid-2.3.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
95
+ python_liquid-2.3.2.dist-info/licenses/LICENSE,sha256=yAFURzud5ERNHt1rZIPnTLJ92ep7q8y5yG9g5DUMR_E,1075
96
+ python_liquid-2.3.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.29.0
2
+ Generator: hatchling 1.32.4
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any