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.
Files changed (212) hide show
  1. liquid/__about__.py +1 -0
  2. liquid/__init__.py +161 -187
  3. liquid/_base_tag.py +66 -0
  4. liquid/{utils/chain_map.py → _chain_map.py} +18 -19
  5. liquid/_context.py +465 -0
  6. liquid/_drop.py +108 -0
  7. liquid/_filter.py +174 -0
  8. liquid/_io.py +18 -0
  9. liquid/_lax_parser_mixin.py +100 -0
  10. liquid/_lexer.py +93 -0
  11. liquid/_lexer_extended.py +623 -0
  12. liquid/_lexer_legacy.py +536 -0
  13. liquid/_loader.py +84 -0
  14. liquid/{utils/lru_cache.py → _lru_cache.py} +28 -36
  15. liquid/_markup.py +253 -0
  16. liquid/_messages.py +292 -0
  17. liquid/_missing.py +3 -0
  18. liquid/_nothing.py +28 -0
  19. liquid/_parser.py +460 -0
  20. liquid/_parser_extended.py +293 -0
  21. liquid/_parser_legacy.py +819 -0
  22. liquid/_resource_limits.py +50 -0
  23. liquid/_slice.py +41 -0
  24. liquid/_static_analysis.py +503 -0
  25. liquid/_strftime.py +186 -0
  26. liquid/_template.py +536 -0
  27. liquid/drops/__init__.py +20 -0
  28. liquid/drops/forloop.py +78 -0
  29. liquid/drops/iterable.py +25 -0
  30. liquid/drops/range.py +76 -0
  31. liquid/drops/self_.py +45 -0
  32. liquid/drops/tablerowloop.py +142 -0
  33. liquid/drops/undefined.py +228 -0
  34. liquid/environment.py +717 -551
  35. liquid/exceptions.py +67 -214
  36. liquid/expressions.py +1184 -0
  37. liquid/filters/__init__.py +134 -0
  38. liquid/filters/abs.py +7 -0
  39. liquid/filters/append.py +5 -0
  40. liquid/filters/at_least.py +9 -0
  41. liquid/filters/at_most.py +9 -0
  42. liquid/filters/babel.py +560 -0
  43. liquid/filters/base64.py +26 -0
  44. liquid/filters/capitalize.py +5 -0
  45. liquid/filters/ceil.py +8 -0
  46. liquid/filters/compact.py +25 -0
  47. liquid/filters/concat.py +11 -0
  48. liquid/filters/date.py +27 -0
  49. liquid/filters/default.py +25 -0
  50. liquid/filters/divided_by.py +21 -0
  51. liquid/filters/downcase.py +5 -0
  52. liquid/filters/escape.py +56 -0
  53. liquid/filters/find.py +56 -0
  54. liquid/filters/first.py +19 -0
  55. liquid/filters/floor.py +8 -0
  56. liquid/filters/html.py +23 -0
  57. liquid/filters/index.py +12 -0
  58. liquid/filters/join.py +13 -0
  59. liquid/filters/json_.py +30 -0
  60. liquid/filters/last.py +17 -0
  61. liquid/filters/map.py +5 -0
  62. liquid/filters/minus.py +13 -0
  63. liquid/filters/modulo.py +18 -0
  64. liquid/filters/newline_to_br.py +16 -0
  65. liquid/filters/plus.py +13 -0
  66. liquid/filters/prepend.py +5 -0
  67. liquid/filters/reject.py +19 -0
  68. liquid/filters/remove.py +36 -0
  69. liquid/filters/replace.py +32 -0
  70. liquid/filters/reverse.py +5 -0
  71. liquid/filters/round.py +35 -0
  72. liquid/filters/size.py +25 -0
  73. liquid/filters/slice.py +39 -0
  74. liquid/filters/sort.py +148 -0
  75. liquid/filters/split.py +34 -0
  76. liquid/filters/squish.py +9 -0
  77. liquid/filters/strip.py +13 -0
  78. liquid/filters/strip_html.py +19 -0
  79. liquid/filters/strip_newlines.py +17 -0
  80. liquid/filters/sum.py +21 -0
  81. liquid/filters/times.py +13 -0
  82. liquid/filters/translate.py +380 -0
  83. liquid/filters/truncate.py +39 -0
  84. liquid/filters/uniq.py +11 -0
  85. liquid/filters/upcase.py +5 -0
  86. liquid/filters/url.py +18 -0
  87. liquid/filters/where.py +18 -0
  88. liquid/loaders/__init__.py +17 -0
  89. liquid/loaders/caching_file_system_loader.py +64 -0
  90. liquid/loaders/choice_loader.py +93 -0
  91. liquid/loaders/dict_loader.py +56 -0
  92. liquid/{builtin/loaders → loaders}/file_system_loader.py +51 -55
  93. liquid/{builtin/loaders → loaders}/mixins.py +39 -51
  94. liquid/loaders/package_loader.py +110 -0
  95. liquid/tags/__init__.py +61 -0
  96. liquid/tags/assign.py +68 -0
  97. liquid/tags/capture.py +83 -0
  98. liquid/tags/case.py +199 -0
  99. liquid/tags/comment.py +56 -0
  100. liquid/tags/cycle.py +131 -0
  101. liquid/tags/decrement.py +55 -0
  102. liquid/tags/doc.py +56 -0
  103. liquid/tags/echo.py +65 -0
  104. liquid/tags/else_.py +53 -0
  105. liquid/tags/error.py +51 -0
  106. liquid/tags/extends.py +448 -0
  107. liquid/tags/for_.py +421 -0
  108. liquid/tags/if_.py +156 -0
  109. liquid/tags/ifchanged.py +69 -0
  110. liquid/tags/include.py +223 -0
  111. liquid/tags/increment.py +55 -0
  112. liquid/tags/inline_comment.py +70 -0
  113. liquid/tags/liquid.py +56 -0
  114. liquid/tags/macro.py +281 -0
  115. liquid/tags/output.py +58 -0
  116. liquid/tags/raw.py +60 -0
  117. liquid/tags/render.py +245 -0
  118. liquid/tags/tablerow.py +254 -0
  119. liquid/tags/template_comment.py +56 -0
  120. liquid/tags/translate.py +316 -0
  121. liquid/tags/unless.py +121 -0
  122. liquid/tags/with_.py +83 -0
  123. liquid/tokens.py +370 -0
  124. {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/METADATA +32 -39
  125. python_liquid-3.0.0.dist-info/RECORD +128 -0
  126. {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/WHEEL +1 -1
  127. {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/licenses/LICENSE +1 -1
  128. liquid/analyze_tags.py +0 -214
  129. liquid/ast.py +0 -252
  130. liquid/builtin/__init__.py +0 -203
  131. liquid/builtin/content.py +0 -41
  132. liquid/builtin/drops/__init__.py +0 -3
  133. liquid/builtin/drops/drops.py +0 -13
  134. liquid/builtin/expressions/__init__.py +0 -47
  135. liquid/builtin/expressions/_tokenize.py +0 -186
  136. liquid/builtin/expressions/arguments.py +0 -207
  137. liquid/builtin/expressions/filtered.py +0 -387
  138. liquid/builtin/expressions/logical.py +0 -672
  139. liquid/builtin/expressions/loop.py +0 -275
  140. liquid/builtin/expressions/path.py +0 -163
  141. liquid/builtin/expressions/primitive.py +0 -417
  142. liquid/builtin/filters/__init__.py +0 -0
  143. liquid/builtin/filters/array.py +0 -329
  144. liquid/builtin/filters/extra.py +0 -83
  145. liquid/builtin/filters/math.py +0 -129
  146. liquid/builtin/filters/misc.py +0 -137
  147. liquid/builtin/filters/string.py +0 -416
  148. liquid/builtin/illegal.py +0 -28
  149. liquid/builtin/loaders/__init__.py +0 -0
  150. liquid/builtin/loaders/caching_file_system_loader.py +0 -61
  151. liquid/builtin/loaders/choice_loader.py +0 -94
  152. liquid/builtin/loaders/dict_loader.py +0 -64
  153. liquid/builtin/loaders/package_loader.py +0 -132
  154. liquid/builtin/output.py +0 -74
  155. liquid/builtin/tags/__init__.py +0 -0
  156. liquid/builtin/tags/assign_tag.py +0 -75
  157. liquid/builtin/tags/capture_tag.py +0 -98
  158. liquid/builtin/tags/case_tag.py +0 -287
  159. liquid/builtin/tags/comment_tag.py +0 -77
  160. liquid/builtin/tags/cycle_tag.py +0 -132
  161. liquid/builtin/tags/decrement_tag.py +0 -59
  162. liquid/builtin/tags/doc_tag.py +0 -68
  163. liquid/builtin/tags/echo_tag.py +0 -45
  164. liquid/builtin/tags/for_tag.py +0 -337
  165. liquid/builtin/tags/if_tag.py +0 -209
  166. liquid/builtin/tags/ifchanged_tag.py +0 -94
  167. liquid/builtin/tags/include_tag.py +0 -251
  168. liquid/builtin/tags/increment_tag.py +0 -60
  169. liquid/builtin/tags/inline_comment_tag.py +0 -47
  170. liquid/builtin/tags/liquid_tag.py +0 -183
  171. liquid/builtin/tags/render_tag.py +0 -371
  172. liquid/builtin/tags/tablerow_tag.py +0 -320
  173. liquid/builtin/tags/unless_tag.py +0 -218
  174. liquid/context.py +0 -615
  175. liquid/expression.py +0 -41
  176. liquid/extra/__init__.py +0 -98
  177. liquid/extra/filters/__init__.py +0 -33
  178. liquid/extra/filters/_json.py +0 -33
  179. liquid/extra/filters/array.py +0 -60
  180. liquid/extra/filters/babel.py +0 -552
  181. liquid/extra/filters/html.py +0 -32
  182. liquid/extra/filters/translate.py +0 -448
  183. liquid/extra/tags/__init__.py +0 -17
  184. liquid/extra/tags/_with.py +0 -80
  185. liquid/extra/tags/extends_tag.py +0 -576
  186. liquid/extra/tags/macro_tag.py +0 -270
  187. liquid/extra/tags/snippet_tag.py +0 -107
  188. liquid/extra/tags/translate_tag.py +0 -388
  189. liquid/filter.py +0 -230
  190. liquid/future/__init__.py +0 -3
  191. liquid/future/environment.py +0 -30
  192. liquid/future/filters/__init__.py +0 -1
  193. liquid/future/tags/__init__.py +0 -1
  194. liquid/lex.py +0 -260
  195. liquid/limits.py +0 -66
  196. liquid/loader.py +0 -141
  197. liquid/messages.py +0 -357
  198. liquid/mode.py +0 -12
  199. liquid/output.py +0 -35
  200. liquid/parser.py +0 -119
  201. liquid/span.py +0 -35
  202. liquid/static_analysis.py +0 -443
  203. liquid/stream.py +0 -148
  204. liquid/stringify.py +0 -31
  205. liquid/tag.py +0 -47
  206. liquid/template.py +0 -658
  207. liquid/token.py +0 -145
  208. liquid/undefined.py +0 -218
  209. liquid/utils/__init__.py +0 -15
  210. liquid/utils/html.py +0 -57
  211. liquid/utils/text.py +0 -28
  212. python_liquid-2.3.6.dist-info/RECORD +0 -96
liquid/tags/extends.py ADDED
@@ -0,0 +1,448 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import defaultdict
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING
6
+
7
+ from .. import expressions
8
+ from .._markup import SCOPE_INHERITED, Partial, render_block, render_block_async
9
+ from .._nothing import NOTHING
10
+ from ..exceptions import (
11
+ LiquidArgumentError,
12
+ LiquidSyntaxError,
13
+ NoSuchTemplateError,
14
+ RequiredBlockError,
15
+ TemplateInheritanceError,
16
+ TemplateNotFoundError,
17
+ )
18
+ from ..expressions import Name
19
+ from ..tokens import (
20
+ TOKEN_DOUBLE_QUOTE,
21
+ TOKEN_IDENT,
22
+ TOKEN_SINGLE_QUOTE,
23
+ TOKEN_TAG_END,
24
+ TOKEN_TAG_NAME,
25
+ TOKEN_TAG_START,
26
+ token_value,
27
+ )
28
+
29
+ if TYPE_CHECKING:
30
+ from .._context import RenderContext
31
+ from .._io import StringBuffer
32
+ from .._markup import Block, Expression, Markup
33
+ from .._parser import Parser
34
+ from .._template import Template
35
+ from ..tokens import Token
36
+
37
+
38
+ class ExtendsTag:
39
+ blank = False
40
+ tag = "extend"
41
+
42
+ __slots__ = ("name", "token")
43
+
44
+ def __init__(self, token: Token, name: Name) -> None:
45
+ self.token = token
46
+ self.name = name
47
+
48
+ @staticmethod
49
+ def parse(token: Token, parser: Parser) -> Markup:
50
+ name = parser.parse_name()
51
+ parser.carry_whitespace_control()
52
+ parser.eat(TOKEN_TAG_END)
53
+ return ExtendsTag(token, name)
54
+
55
+ def render(self, context: RenderContext, buffer: StringBuffer) -> None:
56
+ stacks: defaultdict[str, list[ExtendsStackItem]] = context.get_register(
57
+ "extends", lambda: defaultdict(list)
58
+ )
59
+
60
+ base_template = self._stack_blocks(context, stacks)
61
+
62
+ with context.extend({}, template=base_template):
63
+ base_template.render_with_context(context, buffer)
64
+
65
+ stacks.clear()
66
+ context.interrupts.append("stop")
67
+
68
+ async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
69
+ stacks: defaultdict[str, list[ExtendsStackItem]] = context.get_register(
70
+ "extends", lambda: defaultdict(list)
71
+ )
72
+
73
+ base_template = self._stack_blocks(context, stacks)
74
+
75
+ with context.extend({}, template=base_template):
76
+ await base_template.render_with_context_async(context, buffer)
77
+
78
+ stacks.clear()
79
+ context.interrupts.append("stop")
80
+
81
+ def children(self) -> list[Markup]:
82
+ return []
83
+
84
+ def expressions(self) -> list[Expression]:
85
+ return []
86
+
87
+ def block_scope(self) -> list[Name]:
88
+ return []
89
+
90
+ def template_scope(self) -> list[Name]:
91
+ return []
92
+
93
+ def partials(self, context: RenderContext) -> list[Partial]:
94
+ template = context.env.get_template(
95
+ self.name.value, context=context, tag="extends"
96
+ )
97
+ return [
98
+ Partial(
99
+ template=template,
100
+ scope=SCOPE_INHERITED,
101
+ in_scope=[],
102
+ key=hash(self.name.value),
103
+ )
104
+ ]
105
+
106
+ async def partials_async(self, context: RenderContext) -> list[Partial]:
107
+ template = await context.env.get_template_async(
108
+ self.name.value, context=context, tag="extends"
109
+ )
110
+
111
+ return [
112
+ Partial(
113
+ template=template,
114
+ scope=SCOPE_INHERITED,
115
+ in_scope=[],
116
+ key=hash(self.name.value),
117
+ )
118
+ ]
119
+
120
+ def _stack_blocks(
121
+ self, context: RenderContext, stacks: defaultdict[str, list[ExtendsStackItem]]
122
+ ) -> Template:
123
+ """Build a stack for each named block by visiting templates in the inheritance chain."""
124
+ seen_extends: set[str] = set()
125
+
126
+ def visit(template_: Template) -> Template | None:
127
+ extends_nodes, block_nodes = self._find_extends_nodes(context, template_)
128
+
129
+ seen_blocks: set[str] = set()
130
+
131
+ for block in block_nodes:
132
+ block_name = block.name.value
133
+ if block_name in seen_blocks:
134
+ raise TemplateInheritanceError(
135
+ f"duplicate block {block_name!r}",
136
+ token=block.token,
137
+ source=template_.source,
138
+ name=template_.name,
139
+ )
140
+
141
+ seen_blocks.add(block_name)
142
+ stack = stacks[block_name]
143
+ required = len(stack) > 1 and block.required
144
+
145
+ stack_item = ExtendsStackItem(
146
+ block=block,
147
+ required=required,
148
+ template=template_,
149
+ parent=None,
150
+ )
151
+
152
+ if stack:
153
+ stack[-1].parent = stack_item
154
+
155
+ stack.append(stack_item)
156
+
157
+ if not extends_nodes:
158
+ return None
159
+
160
+ extends_node = extends_nodes[0]
161
+ extends_name = extends_node.name.value
162
+
163
+ if len(extends_nodes) > 1:
164
+ raise TemplateInheritanceError(
165
+ "too many 'extends' tags",
166
+ token=extends_node.token,
167
+ source=template_.source,
168
+ name=template_.name,
169
+ )
170
+
171
+ if extends_name in seen_extends:
172
+ raise TemplateInheritanceError(
173
+ f"circular extends {extends_name!r}",
174
+ token=extends_node.token,
175
+ source=template_.source,
176
+ name=template_.name,
177
+ )
178
+
179
+ seen_extends.add(extends_name)
180
+
181
+ try:
182
+ return context.env.get_template(
183
+ extends_name, context=context, tag="extends"
184
+ )
185
+ except TemplateNotFoundError as err:
186
+ raise NoSuchTemplateError(
187
+ str(err),
188
+ token=extends_node.name.span,
189
+ source=template_.source,
190
+ name=template_.name,
191
+ ) from None
192
+
193
+ next_template = visit(context.template)
194
+ base = next_template
195
+
196
+ while next_template:
197
+ next_template = visit(next_template)
198
+ if next_template:
199
+ base = next_template
200
+
201
+ assert base
202
+ return base
203
+
204
+ def _find_extends_nodes(
205
+ self, context: RenderContext, template: Template
206
+ ) -> tuple[list[ExtendsTag], list[BlockTag]]:
207
+ extends_nodes: list[ExtendsTag] = []
208
+ block_nodes: list[BlockTag] = []
209
+
210
+ def visit(node: Markup) -> None:
211
+ if isinstance(node, ExtendsTag):
212
+ extends_nodes.append(node)
213
+ elif isinstance(node, BlockTag):
214
+ block_nodes.append(node)
215
+
216
+ for child in node.children():
217
+ visit(child)
218
+
219
+ for node in template.nodes:
220
+ if not isinstance(node, str):
221
+ visit(node)
222
+
223
+ return extends_nodes, block_nodes
224
+
225
+
226
+ class BlockTag:
227
+ blank = False
228
+ tag = "block"
229
+
230
+ __slots__ = ("block", "name", "required", "token")
231
+
232
+ def __init__(self, token: Token, name: Name, block: Block, required: bool) -> None:
233
+ self.token = token
234
+ self.name = name
235
+ self.block = block
236
+ self.required = required
237
+
238
+ @classmethod
239
+ def parse(cls, token: Token, parser: Parser) -> Markup:
240
+ name = parser.parse_name()
241
+ required = cls.unpack_args(parser, parser.parse_arguments(require_commas=True))
242
+ parser.carry_whitespace_control()
243
+ parser.eat(TOKEN_TAG_END)
244
+ block = parser.parse_block(("endblock",))
245
+
246
+ # Consume `{% endblock %}` with optional name: `{% endblock name %}`.
247
+ parser.eat(
248
+ TOKEN_TAG_START, message=f"expected tag 'endblock' for {name.value!r}"
249
+ )
250
+ parser.skip_whitespace_control()
251
+ token_ = parser.eat(TOKEN_TAG_NAME, message="expected 'endblock'")
252
+
253
+ if value := token_value(token_, parser.source) != "endblock":
254
+ raise LiquidSyntaxError(
255
+ f"expected endblock for {name!r}, found {value!r}",
256
+ token=token,
257
+ source=parser.source,
258
+ name=parser.template_name,
259
+ )
260
+
261
+ if parser.kind() in (TOKEN_IDENT, TOKEN_SINGLE_QUOTE, TOKEN_DOUBLE_QUOTE):
262
+ end_name = parser.parse_name()
263
+ if end_name.value != name.value:
264
+ raise TemplateInheritanceError(
265
+ f"expected endblock for {name!r}, found {end_name.value!r}",
266
+ token=end_name.span,
267
+ source=parser.source,
268
+ name=parser.template_name,
269
+ )
270
+
271
+ parser.carry_whitespace_control()
272
+ parser.eat(TOKEN_TAG_END, message="expected a closing tag delimiter")
273
+
274
+ return BlockTag(token, name, block, required)
275
+
276
+ @classmethod
277
+ def unpack_args(
278
+ cls, parser: Parser, args: list[Expression | expressions.KeywordArgument]
279
+ ) -> bool:
280
+ required = False
281
+
282
+ for arg in args:
283
+ if (
284
+ isinstance(arg, expressions.Variable)
285
+ and isinstance(arg.root, Name)
286
+ and arg.root.value == "required"
287
+ and arg.selectors is None
288
+ ):
289
+ required = True
290
+ else:
291
+ raise LiquidArgumentError(
292
+ "unexpected argument",
293
+ token=arg.span,
294
+ source=parser.source,
295
+ name=parser.template_name,
296
+ )
297
+
298
+ return required
299
+
300
+ def render(self, context: RenderContext, buffer: StringBuffer) -> None:
301
+ stacks: defaultdict[str, list[ExtendsStackItem]] = context.get_register(
302
+ "extends", lambda: defaultdict(list)
303
+ )
304
+
305
+ stack = stacks[self.name.value]
306
+
307
+ if not stack:
308
+ # This is a base block being rendered directly
309
+ if self.required:
310
+ raise RequiredBlockError(
311
+ f"block {self.name.value!r} is required",
312
+ token=self.name.span,
313
+ source=context.template.source,
314
+ name=context.template.name,
315
+ )
316
+
317
+ with context.extend({"block": BlockDrop(self.name.value)}):
318
+ render_block(self.block, context, buffer)
319
+
320
+ return
321
+
322
+ itm = stack[0]
323
+
324
+ if itm.block.required:
325
+ raise RequiredBlockError(
326
+ f"block {self.name.value!r} is required",
327
+ token=self.name.span,
328
+ source=context.template.source,
329
+ name=context.template.name,
330
+ )
331
+
332
+ scope: dict[str, object] = {"block": BlockDrop(self.name.value, itm.parent)}
333
+ block_context = context.copy(scope, block_scope=True, template=itm.template)
334
+ render_block(itm.block.block, block_context, buffer)
335
+ context.render_score_cumulative += block_context.render_score
336
+
337
+ async def render_async(self, context: RenderContext, buffer: StringBuffer) -> None:
338
+ stacks: defaultdict[str, list[ExtendsStackItem]] = context.get_register(
339
+ "extends", lambda: defaultdict(list)
340
+ )
341
+
342
+ stack = stacks[self.name.value]
343
+
344
+ if not stack:
345
+ # This is a base block being rendered directly
346
+ if self.required:
347
+ raise RequiredBlockError(
348
+ f"block {self.name.value!r} is required",
349
+ token=self.name.span,
350
+ source=context.template.source,
351
+ name=context.template.name,
352
+ )
353
+
354
+ with context.extend({"block": BlockDrop(self.name.value)}):
355
+ await render_block_async(self.block, context, buffer)
356
+
357
+ return
358
+
359
+ itm = stack[0]
360
+
361
+ if itm.block.required:
362
+ raise RequiredBlockError(
363
+ f"block {self.name.value!r} is required",
364
+ token=self.name.span,
365
+ source=context.template.source,
366
+ name=context.template.name,
367
+ )
368
+
369
+ scope: dict[str, object] = {"block": BlockDrop(self.name.value, itm.parent)}
370
+ block_context = context.copy(scope, block_scope=True, template=itm.template)
371
+ await render_block_async(itm.block.block, block_context, buffer)
372
+ context.render_score_cumulative += block_context.render_score
373
+
374
+ def children(self) -> list[Markup]:
375
+ return [node for node in self.block if not isinstance(node, str)]
376
+
377
+ def expressions(self) -> list[Expression]:
378
+ return []
379
+
380
+ def block_scope(self) -> list[Name]:
381
+ return [Name(self.token, "block")]
382
+
383
+ def template_scope(self) -> list[Name]:
384
+ return []
385
+
386
+ def partials(self, context: RenderContext) -> list[Partial]:
387
+ return []
388
+
389
+ async def partials_async(self, context: RenderContext) -> list[Partial]:
390
+ return []
391
+
392
+
393
+ @dataclass(slots=True, kw_only=True)
394
+ class ExtendsStackItem:
395
+ block: BlockTag
396
+ required: bool
397
+ template: Template
398
+ parent: ExtendsStackItem | None
399
+
400
+
401
+ class BlockDrop:
402
+ __slots__ = ("name", "parent")
403
+
404
+ def __init__(self, name: str, parent: ExtendsStackItem | None = None) -> None:
405
+ super().__init__()
406
+ self.name = name
407
+ self.parent = parent
408
+
409
+ def __liquid_hasattr__(self, name: str, context: RenderContext) -> bool:
410
+ return name == "super"
411
+
412
+ def __liquid_getattr__(self, name: str, context: RenderContext) -> object:
413
+ if self.parent is None or name != "super":
414
+ return NOTHING
415
+
416
+ buffer = context.env.buffer_factory()
417
+
418
+ scope: dict[str, object] = {
419
+ "block": BlockDrop(self.parent.template.name, self.parent.parent)
420
+ }
421
+
422
+ with context.extend(scope, template=self.parent.template):
423
+ render_block(self.parent.block.block, context, buffer)
424
+
425
+ return buffer.getvalue()
426
+
427
+ async def __liquid_hasattr_async__(self, name: str, context: RenderContext) -> bool:
428
+ return name == "super"
429
+
430
+ async def __liquid_getattr_async__(
431
+ self, name: str, context: RenderContext
432
+ ) -> object:
433
+ if self.parent is None or name != "super":
434
+ return NOTHING
435
+
436
+ buffer = context.env.buffer_factory()
437
+
438
+ scope: dict[str, object] = {
439
+ "block": BlockDrop(self.parent.template.name, self.parent.parent)
440
+ }
441
+
442
+ with context.extend(scope, template=self.parent.template):
443
+ await render_block_async(self.parent.block.block, context, buffer)
444
+
445
+ return buffer.getvalue()
446
+
447
+ def __str__(self) -> str:
448
+ return f"BlockDrop({self.name})"