python-liquid 2.3.0__tar.gz → 2.3.1__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.0 → python_liquid-2.3.1}/PKG-INFO +2 -1
  2. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/__init__.py +1 -1
  3. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/filters/array.py +10 -2
  4. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/loaders/package_loader.py +26 -12
  5. {python_liquid-2.3.0 → python_liquid-2.3.1}/pyproject.toml +9 -1
  6. {python_liquid-2.3.0 → python_liquid-2.3.1}/.gitignore +0 -0
  7. {python_liquid-2.3.0 → python_liquid-2.3.1}/LICENSE +0 -0
  8. {python_liquid-2.3.0 → python_liquid-2.3.1}/README.md +0 -0
  9. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/analyze_tags.py +0 -0
  10. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/ast.py +0 -0
  11. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/__init__.py +0 -0
  12. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/content.py +0 -0
  13. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/drops/__init__.py +0 -0
  14. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/drops/drops.py +0 -0
  15. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/expressions/__init__.py +0 -0
  16. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/expressions/_tokenize.py +0 -0
  17. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/expressions/arguments.py +0 -0
  18. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/expressions/filtered.py +0 -0
  19. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/expressions/logical.py +0 -0
  20. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/expressions/loop.py +0 -0
  21. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/expressions/path.py +0 -0
  22. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/expressions/primitive.py +0 -0
  23. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/filters/__init__.py +0 -0
  24. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/filters/extra.py +0 -0
  25. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/filters/math.py +0 -0
  26. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/filters/misc.py +0 -0
  27. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/filters/string.py +0 -0
  28. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/illegal.py +0 -0
  29. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/loaders/__init__.py +0 -0
  30. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/loaders/caching_file_system_loader.py +0 -0
  31. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/loaders/choice_loader.py +0 -0
  32. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/loaders/dict_loader.py +0 -0
  33. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/loaders/file_system_loader.py +0 -0
  34. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/loaders/mixins.py +0 -0
  35. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/output.py +0 -0
  36. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/__init__.py +0 -0
  37. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/assign_tag.py +0 -0
  38. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/capture_tag.py +0 -0
  39. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/case_tag.py +0 -0
  40. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/comment_tag.py +0 -0
  41. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/cycle_tag.py +0 -0
  42. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/decrement_tag.py +0 -0
  43. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/doc_tag.py +0 -0
  44. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/echo_tag.py +0 -0
  45. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/for_tag.py +0 -0
  46. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/if_tag.py +0 -0
  47. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/ifchanged_tag.py +0 -0
  48. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/include_tag.py +0 -0
  49. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/increment_tag.py +0 -0
  50. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/inline_comment_tag.py +0 -0
  51. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/liquid_tag.py +0 -0
  52. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/render_tag.py +0 -0
  53. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/tablerow_tag.py +0 -0
  54. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/builtin/tags/unless_tag.py +0 -0
  55. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/context.py +0 -0
  56. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/environment.py +0 -0
  57. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/exceptions.py +0 -0
  58. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/expression.py +0 -0
  59. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/extra/__init__.py +0 -0
  60. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/extra/filters/__init__.py +0 -0
  61. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/extra/filters/_json.py +0 -0
  62. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/extra/filters/array.py +0 -0
  63. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/extra/filters/babel.py +0 -0
  64. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/extra/filters/html.py +0 -0
  65. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/extra/filters/translate.py +0 -0
  66. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/extra/tags/__init__.py +0 -0
  67. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/extra/tags/_with.py +0 -0
  68. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/extra/tags/extends_tag.py +0 -0
  69. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/extra/tags/macro_tag.py +0 -0
  70. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/extra/tags/snippet_tag.py +0 -0
  71. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/extra/tags/translate_tag.py +0 -0
  72. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/filter.py +0 -0
  73. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/future/__init__.py +0 -0
  74. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/future/environment.py +0 -0
  75. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/future/filters/__init__.py +0 -0
  76. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/future/tags/__init__.py +0 -0
  77. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/lex.py +0 -0
  78. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/limits.py +0 -0
  79. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/loader.py +0 -0
  80. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/messages.py +0 -0
  81. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/mode.py +0 -0
  82. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/output.py +0 -0
  83. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/parser.py +0 -0
  84. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/py.typed +0 -0
  85. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/span.py +0 -0
  86. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/static_analysis.py +0 -0
  87. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/stream.py +0 -0
  88. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/stringify.py +0 -0
  89. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/tag.py +0 -0
  90. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/template.py +0 -0
  91. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/token.py +0 -0
  92. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/undefined.py +0 -0
  93. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/utils/__init__.py +0 -0
  94. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/utils/chain_map.py +0 -0
  95. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/utils/html.py +0 -0
  96. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/utils/lru_cache.py +0 -0
  97. {python_liquid-2.3.0 → python_liquid-2.3.1}/liquid/utils/text.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-liquid
3
- Version: 2.3.0
3
+ Version: 2.3.1
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
@@ -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.1"
60
60
 
61
61
  __all__ = (
62
62
  "AwareBoundTemplate",
@@ -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
 
@@ -18,7 +18,14 @@ classifiers = [
18
18
  "Programming Language :: Python :: 3.13",
19
19
  "Programming Language :: Python :: 3.14",
20
20
  ]
21
- dependencies = ["MarkupSafe>=3", "python-dateutil>=2.8.1", "Babel>=2", "pytz"]
21
+ dependencies = [
22
+ "MarkupSafe>=3",
23
+ "python-dateutil>=2.8.1",
24
+ "Babel>=2",
25
+ "pytz",
26
+ "importlib-resources>=5.12",
27
+ ]
28
+
22
29
  description = "A Python engine for the Liquid template language."
23
30
  dynamic = ["version"]
24
31
  license = "MIT"
@@ -217,6 +224,7 @@ ignore = [
217
224
  "S704",
218
225
  "TC006",
219
226
  "PLW1641",
227
+ "PLR0917",
220
228
  ]
221
229
 
222
230
  fixable = ["I", "SIM", "D202"]
File without changes
File without changes
File without changes