@@ -131,10 +131,18 @@ class ContextProvider(t.Generic[T]):
131
131
node : Node
132
132
133
133
def __iter__ (self ) -> Iterator [str ]:
134
- return iter_node (self )
134
+ return _stream_chunks (self , {} )
135
135
136
- def __str__ (self ) -> str :
137
- return render_node (self )
136
+ def __str__ (self ) -> _Markup :
137
+ return _as_markup (self )
138
+
139
+ __html__ = __str__
140
+
141
+ def stream_chunks (self ) -> Iterator [str ]:
142
+ return _stream_chunks (self , {})
143
+
144
+ def encode (self , encoding : str = "utf-8" , errors : str = "strict" ) -> bytes :
145
+ return str (self ).encode (encoding , errors )
138
146
139
147
140
148
@dataclasses .dataclass (frozen = True )
@@ -143,6 +151,17 @@ class ContextConsumer(t.Generic[T]):
143
151
debug_name : str
144
152
func : Callable [[T ], Node ]
145
153
154
+ def __str__ (self ) -> _Markup :
155
+ return _as_markup (self )
156
+
157
+ __html__ = __str__
158
+
159
+ def stream_chunks (self ) -> Iterator [str ]:
160
+ return _stream_chunks (self , {})
161
+
162
+ def encode (self , encoding : str = "utf-8" , errors : str = "strict" ) -> bytes :
163
+ return str (self ).encode (encoding , errors )
164
+
146
165
147
166
class _NO_DEFAULT :
148
167
pass
@@ -168,10 +187,10 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> ContextConsumer[T]:
168
187
169
188
170
189
def iter_node (x : Node ) -> Iterator [str ]:
171
- return _iter_node_context ( x , {} )
190
+ return fragment [ x ]. stream_chunks ( )
172
191
173
192
174
- def _iter_node_context (x : Node , context_dict : dict [Context [t .Any ], t .Any ]) -> Iterator [str ]:
193
+ def _stream_chunks (x : Node , context_dict : dict [Context [t .Any ], t .Any ]) -> Iterator [str ]:
175
194
while not isinstance (x , BaseElement ) and callable (x ):
176
195
x = x ()
177
196
@@ -187,24 +206,25 @@ def _iter_node_context(x: Node, context_dict: dict[Context[t.Any], t.Any]) -> It
187
206
if isinstance (x , BaseElement ):
188
207
yield from x ._iter_context (context_dict ) # pyright: ignore [reportPrivateUsage]
189
208
elif isinstance (x , ContextProvider ):
190
- yield from _iter_node_context (x .node , {** context_dict , x .context : x .value }) # pyright: ignore [reportUnknownMemberType]
209
+ yield from _stream_chunks (x .node , {** context_dict , x .context : x .value }) # pyright: ignore [reportUnknownMemberType]
191
210
elif isinstance (x , ContextConsumer ):
192
- context_value = context_dict .get (x .context , x .context .default )
211
+ context_value = context_dict .get (x .context , x .context .default ) # pyright: ignore
212
+
193
213
if context_value is _NO_DEFAULT :
194
214
raise LookupError (
195
- f'Context value for "{ x .context .name } " does not exist, '
215
+ f'Context value for "{ x .context .name } " does not exist, ' # pyright: ignore
196
216
f"requested by { x .debug_name } ()."
197
217
)
198
- yield from _iter_node_context (x .func (context_value ), context_dict )
218
+ yield from _stream_chunks (x .func (context_value ), context_dict ) # pyright: ignore
199
219
elif isinstance (x , Fragment ):
200
- yield from _iter_node_context (x ._node , context_dict ) # pyright: ignore
220
+ yield from _stream_chunks (x ._node , context_dict ) # pyright: ignore
201
221
elif isinstance (x , str | _HasHtml ):
202
222
yield str (_escape (x ))
203
223
elif isinstance (x , int ):
204
224
yield str (x )
205
225
elif isinstance (x , Iterable ) and not isinstance (x , _KnownInvalidChildren ): # pyright: ignore [reportUnnecessaryIsInstance]
206
226
for child in x :
207
- yield from _iter_node_context (child , context_dict )
227
+ yield from _stream_chunks (child , context_dict )
208
228
else :
209
229
raise TypeError (f"{ x !r} is not a valid child element" )
210
230
@@ -231,7 +251,7 @@ def __init__(self, name: str, attrs_str: str = "", children: Node = None) -> Non
231
251
self ._children = children
232
252
233
253
def __str__ (self ) -> _Markup :
234
- return _Markup ( "" . join ( self ) )
254
+ return _as_markup ( self )
235
255
236
256
__html__ = __str__
237
257
@@ -281,14 +301,14 @@ def __call__(self: BaseElementSelf, *args: t.Any, **kwargs: t.Any) -> BaseElemen
281
301
def __iter__ (self ) -> Iterator [str ]:
282
302
return self ._iter_context ({})
283
303
304
+ def stream_chunks (self ) -> Iterator [str ]:
305
+ return self ._iter_context ({})
306
+
284
307
def _iter_context (self , ctx : dict [Context [t .Any ], t .Any ]) -> Iterator [str ]:
285
308
yield f"<{ self ._name } { self ._attrs } >"
286
- yield from _iter_node_context (self ._children , ctx )
309
+ yield from _stream_chunks (self ._children , ctx )
287
310
yield f"</{ self ._name } >"
288
311
289
- # Allow starlette Response.render to directly render this element without
290
- # explicitly casting to str:
291
- # https://github.com/encode/starlette/blob/5ed55c441126687106109a3f5e051176f88cd3e6/starlette/responses.py#L44-L49
292
312
def encode (self , encoding : str = "utf-8" , errors : str = "strict" ) -> bytes :
293
313
return str (self ).encode (encoding , errors )
294
314
@@ -358,13 +378,19 @@ def __init__(self) -> None:
358
378
self ._node : Node = None
359
379
360
380
def __iter__ (self ) -> Iterator [str ]:
361
- return iter_node (self )
381
+ return _stream_chunks (self , {} )
362
382
363
- def __str__ (self ) -> str :
364
- return render_node (self )
383
+ def __str__ (self ) -> _Markup :
384
+ return _as_markup (self )
365
385
366
386
__html__ = __str__
367
387
388
+ def stream_chunks (self ) -> Iterator [str ]:
389
+ return _stream_chunks (self , {})
390
+
391
+ def encode (self , encoding : str = "utf-8" , errors : str = "strict" ) -> bytes :
392
+ return str (self ).encode (encoding , errors )
393
+
368
394
369
395
class _FragmentGetter :
370
396
def __getitem__ (self , node : Node ) -> Fragment :
@@ -376,8 +402,12 @@ def __getitem__(self, node: Node) -> Fragment:
376
402
fragment = _FragmentGetter ()
377
403
378
404
405
+ def _as_markup (renderable : Renderable ) -> _Markup :
406
+ return _Markup ("" .join (renderable .stream_chunks ()))
407
+
408
+
379
409
def render_node (node : Node ) -> _Markup :
380
- return _Markup ("" . join ( iter_node ( node )) )
410
+ return _Markup (fragment [ node ] )
381
411
382
412
383
413
def comment (text : str ) -> Fragment :
@@ -545,3 +575,14 @@ def __html__(self) -> str: ...
545
575
| Callable
546
576
| Iterable
547
577
)
578
+
579
+
580
+ class Renderable (t .Protocol ):
581
+ def __str__ (self ) -> _Markup : ...
582
+ def __html__ (self ) -> _Markup : ...
583
+ def stream_chunks (self ) -> Iterator [str ]: ...
584
+
585
+ # Allow starlette Response.render to directly render this element without
586
+ # explicitly casting to str:
587
+ # https://github.com/encode/starlette/blob/5ed55c441126687106109a3f5e051176f88cd3e6/starlette/responses.py#L44-L49
588
+ def encode (self , encoding : str = "utf-8" , errors : str = "strict" ) -> bytes : ...
0 commit comments