11from __future__ import annotations
22from typing import TYPE_CHECKING , override
3+ import pickle
34from pprint import pformat
45
56from docutils import nodes
67from docutils .parsers .rst .states import Inliner
78
8- from .template import Template
9+ from .data import ValueWrapper , ParsedData
10+ from .template import Template , Phase
911from .ctx import (
10- PendingContextRef ,
11- PendingContext ,
12- PendingContextStorage ,
12+ ResolvableContext ,
1313 ResolvedContext ,
1414)
1515from .markup import MarkupRenderer
2121)
2222
2323if TYPE_CHECKING :
24- from typing import Any , Callable , ClassVar
24+ from typing import Any , Callable
2525 from .markup import Host
2626 from .ctx import ResolvedContext
2727
@@ -30,7 +30,7 @@ class pending_node(nodes.Element):
3030 """A docutils node to be rendered."""
3131
3232 # The context to be rendered by Jinja template.
33- ctx : PendingContextRef | ResolvedContext
33+ ctx : ResolvableContext | ResolvedContext
3434 # The extra context as supplement to ctx.
3535 extra : dict [str , Any ]
3636 #: Jinja template for rendering the context.
@@ -39,34 +39,33 @@ class pending_node(nodes.Element):
3939 inline : bool
4040 #: Whether the rendering pipeline is finished (failed is also finished).
4141 rendered : bool
42-
43- #: Mapping of PendingContextRef -> PendingContext.
44- #:
45- #: NOTE: ``PendingContextStorage`` holds Unpicklable data (``PendingContext``)
46- #: but it is doesn't matters :-), cause pickle doesn't deal with ClassVar.
47- _PENDING_CONTEXTS : ClassVar [PendingContextStorage ] = PendingContextStorage ()
42+ #: Stored pickling error for later-phase resolvable context.
43+ _ctx_pickle_error : Exception | None
4844
4945 def __init__ (
5046 self ,
51- ctx : PendingContext | ResolvedContext ,
47+ ctx : ResolvableContext | ResolvedContext ,
5248 tmpl : Template ,
5349 inline : bool = False ,
5450 rawsource = '' ,
5551 * children ,
5652 ** attributes ,
5753 ) -> None :
5854 super ().__init__ (rawsource , * children , ** attributes )
59- if not isinstance (ctx , PendingContext ):
60- self .ctx = ctx
61- else :
62- self .ctx = self ._PENDING_CONTEXTS .stash (ctx )
55+ self ._ctx_pickle_error = None
56+ if isinstance (ctx , ResolvableContext ) and tmpl .phase != Phase .Parsing :
57+ try :
58+ pickle .dumps (ctx )
59+ except Exception as exc :
60+ self ._ctx_pickle_error = exc
61+ self .ctx = ctx
6362 self .extra = {}
6463 self .template = tmpl
6564 self .inline = inline
6665 self .rendered = False
6766
6867 # Init hook lists.
69- self ._pending_context_hooks = []
68+ self ._resolvable_context_hooks = []
7069 self ._resolved_data_hooks = []
7170 self ._markup_text_hooks = []
7271 self ._rendered_nodes_hooks = []
@@ -75,7 +74,7 @@ def render(self, host: Host) -> None:
7574 """
7675 The core function for rendering context and template to docutils nodes.
7776
78- 1. PendingContextRef -> PendingContext -> ResolvedContext
77+ 1. ResolvableContext -> ResolvedContext
7978 2. TemplateRenderer.render(ResolvedContext) -> Markup Text (``str``)
8079 3. MarkupRenderer.render(Markup Text) -> doctree Nodes (list[nodes.Node])
8180 """
@@ -97,29 +96,30 @@ def err_report() -> Report:
9796 return report
9897 return Report ('Render Report' , 'ERROR' , source = self .source , line = self .line )
9998
100- # 1. Prepare context for Jinja template.
101- if isinstance (self .ctx , PendingContextRef ):
102- report .text ('Pending context ref:' )
103- report .code (pformat (self .ctx ), lang = 'python' )
104-
105- pdata = self ._PENDING_CONTEXTS .retrieve (self .ctx )
106- if pdata is None :
107- report = err_report ()
108- report .text (f'Failed to retrieve pending context from ref { self .ctx } ' )
109- self += report
110- return None
99+ if self ._ctx_pickle_error is not None :
100+ report = err_report ()
101+ report .text (
102+ f'ResolvableContext used by { self .template .phase } phase templates '
103+ 'must be picklable:'
104+ )
105+ report .exception (self ._ctx_pickle_error )
106+ self += report
107+ return None
111108
112- report .text ('Pending context:' )
109+ # 1. Prepare context for Jinja template.
110+ if isinstance (self .ctx , ResolvableContext ):
111+ pdata = self .ctx
112+ report .text ('Resolvable context:' )
113113 report .code (pformat (pdata ), lang = 'python' )
114114
115- for hook in self ._pending_context_hooks :
115+ for hook in self ._resolvable_context_hooks :
116116 hook (self , pdata )
117117
118118 try :
119119 ctx = self .ctx = pdata .resolve ()
120120 except Exception as e :
121121 report = err_report ()
122- report .text ('Failed to resolve pending context:' )
122+ report .text ('Failed to resolve resolvable context:' )
123123 report .exception (e )
124124 self += report
125125 return None
@@ -221,18 +221,18 @@ def unwrap_and_replace_self_inline(self, inliner: Report.Inliner) -> None:
221221
222222 """Hooks for processing render intermediate products."""
223223
224- type PendingContextHook = Callable [[pending_node , PendingContext ], None ]
224+ type ResolvableContextHook = Callable [[pending_node , ResolvableContext ], None ]
225225 type ResolvedContextHook = Callable [[pending_node , ResolvedContext ], None ]
226226 type MarkupTextHook = Callable [[pending_node , str ], str ]
227227 type RenderedNodesHook = Callable [[pending_node , list [nodes .Node ]], None ]
228228
229- _pending_context_hooks : list [PendingContextHook ]
229+ _resolvable_context_hooks : list [ResolvableContextHook ]
230230 _resolved_data_hooks : list [ResolvedContextHook ]
231231 _markup_text_hooks : list [MarkupTextHook ]
232232 _rendered_nodes_hooks : list [RenderedNodesHook ]
233233
234- def hook_pending_context (self , hook : PendingContextHook ) -> None :
235- self ._pending_context_hooks .append (hook )
234+ def hook_resolvable_context (self , hook : ResolvableContextHook ) -> None :
235+ self ._resolvable_context_hooks .append (hook )
236236
237237 def hook_resolved_context (self , hook : ResolvedContextHook ) -> None :
238238 self ._resolved_data_hooks .append (hook )
@@ -259,3 +259,16 @@ def copy(self) -> Any:
259259 def deepcopy (self ) -> Any :
260260 # NOTE: Same to :meth:`copy`.
261261 return self .copy ()
262+
263+ @override
264+ def astext (self ) -> str :
265+ ctx = self .ctx
266+ if isinstance (ctx , ResolvableContext ):
267+ try :
268+ ctx = ctx .resolve ()
269+ except Exception :
270+ return ''
271+ if isinstance (ctx , ParsedData ):
272+ return ValueWrapper (ctx .content ).as_str () or ''
273+ else :
274+ return ''
0 commit comments