sphinxnotes-render 1.0b4__tar.gz → 1.0b5__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 (86) hide show
  1. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/.cruft.json +1 -1
  2. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/.gitignore +1 -0
  3. {sphinxnotes_render-1.0b4/src/sphinxnotes_render.egg-info → sphinxnotes_render-1.0b5}/PKG-INFO +1 -1
  4. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/api.rst +11 -19
  5. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/conf.py +1 -1
  6. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/conf.rst +0 -2
  7. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/ext.rst +2 -6
  8. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/tmpl.rst +8 -22
  9. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/usage.rst +0 -6
  10. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/__init__.py +6 -10
  11. sphinxnotes_render-1.0b5/src/sphinxnotes/render/ctx.py +23 -0
  12. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/ctxnodes.py +64 -47
  13. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/data.py +5 -6
  14. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/ext/adhoc.py +2 -11
  15. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/ext/derive.py +0 -2
  16. sphinxnotes_render-1.0b5/src/sphinxnotes/render/ext/extractx.py +89 -0
  17. sphinxnotes_render-1.0b5/src/sphinxnotes/render/extractx.py +98 -0
  18. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/jinja.py +8 -28
  19. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/pipeline.py +3 -13
  20. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/sources.py +4 -4
  21. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/template.py +6 -9
  22. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5/src/sphinxnotes_render.egg-info}/PKG-INFO +1 -1
  23. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes_render.egg-info/SOURCES.txt +5 -0
  24. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/conftest.py +3 -0
  25. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-extra-context/conf.py +4 -4
  26. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-extra-context/index.rst +0 -1
  27. sphinxnotes_render-1.0b5/tests/roots/test-extra-context-rebuild/conf.py +1 -0
  28. sphinxnotes_render-1.0b5/tests/roots/test-extra-context-rebuild/index.rst +11 -0
  29. sphinxnotes_render-1.0b5/tests/test_ctx.py +60 -0
  30. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/test_e2e.py +11 -0
  31. sphinxnotes_render-1.0b5/tests/test_extractx.py +36 -0
  32. sphinxnotes_render-1.0b5/tests/test_jinja.py +12 -0
  33. sphinxnotes_render-1.0b4/src/sphinxnotes/render/ctx.py +0 -69
  34. sphinxnotes_render-1.0b4/src/sphinxnotes/render/ext/extractx.py +0 -81
  35. sphinxnotes_render-1.0b4/src/sphinxnotes/render/extractx.py +0 -208
  36. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/.github/workflows/lint.yml +0 -0
  37. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/.github/workflows/pages.yml +0 -0
  38. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/.github/workflows/pypi.yml +0 -0
  39. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/.github/workflows/release.yml +0 -0
  40. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/.github/workflows/test.yml +0 -0
  41. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/.pre-commit-config.yaml +0 -0
  42. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/LICENSE +0 -0
  43. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/MANIFEST.in +0 -0
  44. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/Makefile +0 -0
  45. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/README.rst +0 -0
  46. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/Makefile +0 -0
  47. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/_images/.gitkeep +0 -0
  48. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/_static/.gitkeep +0 -0
  49. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/_static/custom.css +0 -0
  50. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/_static/sphinx-notes.png +0 -0
  51. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/changelog.rst +0 -0
  52. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/dsl.rst +0 -0
  53. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/index.rst +3 -3
  54. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/make.bat +0 -0
  55. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/pyproject.toml +0 -0
  56. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/ruff.toml +0 -0
  57. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/setup.cfg +0 -0
  58. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/ext/__init__.py +0 -0
  59. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/ext/filters.py +0 -0
  60. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/markup.py +0 -0
  61. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/meta.py +0 -0
  62. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/py.typed +0 -0
  63. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/utils/__init__.py +0 -0
  64. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/utils/ctxproxy.py +0 -0
  65. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/utils/freestyle.py +0 -0
  66. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes_render.egg-info/dependency_links.txt +0 -0
  67. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes_render.egg-info/requires.txt +0 -0
  68. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes_render.egg-info/top_level.txt +0 -0
  69. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/__init__.py +0 -0
  70. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-base-context-directive-example/conf.py +0 -0
  71. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-base-context-directive-example/index.rst +0 -0
  72. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-base-data-define-directive-example/conf.py +0 -0
  73. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-base-data-define-directive-example/index.rst +0 -0
  74. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-data-define/conf.py +0 -0
  75. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-data-define/index.rst +0 -0
  76. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-derive/conf.py +0 -0
  77. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-derive/index.rst +0 -0
  78. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-extra-context/cat.json +0 -0
  79. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-filter-example/conf.py +0 -0
  80. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-filter-example/index.rst +0 -0
  81. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-strict-data-define-directive-example/conf.py +0 -0
  82. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-strict-data-define-directive-example/index.rst +0 -0
  83. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-strictdir-card/conf.py +0 -0
  84. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-strictdir-card/index.rst +0 -0
  85. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/test_always_pass.py +0 -0
  86. {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/test_data.py +0 -0
@@ -9,7 +9,7 @@
9
9
  "full_name": "sphinxnotes-render",
10
10
  "author": "Shengyu Zhang",
11
11
  "description": "Define, constrain, and render data in Sphinx documentation",
12
- "version": "1.0a0",
12
+ "version": "1.0b4",
13
13
  "github_owner": "sphinx-notes",
14
14
  "github_repo": "data",
15
15
  "pypi_name": "sphinxnotes-render",
@@ -135,3 +135,4 @@ poetry.lock
135
135
  docs/_build/
136
136
  # sphinxnotes-any >= 2.5
137
137
  docs/.any*
138
+ .worktrees/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sphinxnotes-render
3
- Version: 1.0b4
3
+ Version: 1.0b5
4
4
  Summary: Define, constrain, and render data in Sphinx documentation
5
5
  Author: Shengyu Zhang
6
6
  Maintainer: Shengyu Zhang
@@ -55,22 +55,22 @@ Context refers to the dynamic content of a Jinja template. It can be:
55
55
  Our dedicated data type (:py:class:`sphinxnotes.render.ParsedData`), or any
56
56
  Python ``dict``.
57
57
 
58
- :py:class:`~sphinxnotes.render.PendingContext`:
58
+ :py:class:`~sphinxnotes.render.UnresolvedContext`:
59
59
  Context that is not yet available. For example, it may contain
60
60
  :py:class:`unparsed data <sphinxnotes.render.RawData>`,
61
61
  remote data, and more.
62
62
 
63
- :py:class:`PendingContext` can be resolved to
63
+ :py:class:`UnresolvedContext` can be resolved to
64
64
  :py:class:`~sphinxnotes.render.ResolvedContext` by calling
65
- :py:meth:`~sphinxnotes.render.PendingContext.resolve`.
65
+ :py:meth:`~sphinxnotes.render.UnresolvedContext.resolve`.
66
66
 
67
67
  .. autotype:: sphinxnotes.render.ResolvedContext
68
68
 
69
- .. autoclass:: sphinxnotes.render.PendingContext
69
+ .. autoclass:: sphinxnotes.render.UnresolvedContext
70
70
  :members: resolve
71
71
 
72
- ``PendingContext`` Implementations
73
- ----------------------------------
72
+ ``UnresolvedContext`` Implementations
73
+ -------------------------------------
74
74
 
75
75
  .. autoclass:: sphinxnotes.render.UnparsedData
76
76
  :show-inheritance:
@@ -92,24 +92,16 @@ Extra Context
92
92
  =============
93
93
 
94
94
  See :doc:`tmpl` for built-in extra-context names such as ``doc`` and
95
- ``sphinx``, plus usage examples.
95
+ ``env``, plus usage examples.
96
96
 
97
97
  .. autodecorator:: sphinxnotes.render.extra_context
98
98
 
99
- .. autoclass:: sphinxnotes.render.ParsingPhaseExtraContext
100
- :members: phase, generate
99
+ .. autoclass:: sphinxnotes.render.ExtraContext
100
+ :members: generate
101
101
  :undoc-members:
102
102
 
103
- .. autoclass:: sphinxnotes.render.ParsedPhaseExtraContext
104
- :members: phase, generate
105
- :undoc-members:
106
-
107
- .. autoclass:: sphinxnotes.render.ResolvingPhaseExtraContext
108
- :members: phase, generate
109
- :undoc-members:
110
-
111
- .. autoclass:: sphinxnotes.render.GlobalExtraContext
112
- :members: phase, generate
103
+ .. autoclass:: sphinxnotes.render.ExtraContextRequest
104
+ :members:
113
105
  :undoc-members:
114
106
 
115
107
  Filters
@@ -14,7 +14,7 @@ author = 'Shengyu Zhang'
14
14
  copyright = "2025, " + author
15
15
 
16
16
  # The full version, including alpha/beta/rc tags
17
- version = release = '1.0a0'
17
+ version = release = '1.0b4'
18
18
 
19
19
  # -- General configuration ---------------------------------------------------
20
20
 
@@ -25,7 +25,5 @@ The extension provides the following configuration:
25
25
  - ``text`` (str): the Jinja2 template text.
26
26
  - ``on`` (str, optional): same as :rst:dir:`data.template:on`
27
27
  - ``debug`` (bool, optional): same as :rst:dir:`data.template:debug`
28
- - ``extra`` (list[str], optional): same as :rst:dir:`data.template:extra`
29
28
 
30
29
  See :ref:`usage-custom-directive` for example.
31
-
@@ -69,11 +69,8 @@ Extending Extra Contexts
69
69
  Extra contexts are registered by a
70
70
  :py:deco:`sphinxnotes.render.extra_context` class decorator.
71
71
 
72
- The decorated class must be one of the following classes:
73
- :py:class:`~sphinxnotes.render.ParsingPhaseExtraContext`,
74
- :py:class:`~sphinxnotes.render.ParsedPhaseExtraContext`,
75
- :py:class:`~sphinxnotes.render.ResolvingPhaseExtraContext`,
76
- :py:class:`~sphinxnotes.render.GlobalExtraContext`.
72
+ The decorated class must be a subclass of
73
+ :py:class:`~sphinxnotes.render.ExtraContext`.
77
74
 
78
75
  .. literalinclude:: ../tests/roots/test-extra-context/conf.py
79
76
  :language: python
@@ -88,7 +85,6 @@ The decorated class must be one of the following classes:
88
85
  :style: grid
89
86
 
90
87
  .. data.render::
91
- :extra: cat
92
88
 
93
89
  {{ load_extra('cat').name }}
94
90
 
@@ -158,20 +158,13 @@ sources (such as Sphinx application, JSON file, and etc.). Unlike main context
158
158
  which comes from the directive/role itself, extra context lets you fetch data
159
159
  that was prepared beforehand.
160
160
 
161
- Extra contexts are typically generated on demand at different construction stages,
162
- so you need to declare them in advance, and load it in the template using the
163
- ``load_extra()`` function:
164
-
165
- The way of declaring extra context is vary depending on the extension you use.
166
- For ``sphinxnotes.render.ext`` extension, :rst:dir:`data.template:extra`,
167
- :rst:dir:`data.render:extra` and the ``templat.extra`` field of
168
- :confval:`render_ext_data_define_directives` are for this.
161
+ Extra contexts are generated on demand. Load them in the template using the
162
+ ``load_extra()`` function.
169
163
 
170
164
  .. example::
171
165
  :style: grid
172
166
 
173
167
  .. data.render::
174
- :extra: doc
175
168
 
176
169
  {% set doc = load_extra('doc') %}
177
170
 
@@ -191,7 +184,6 @@ The following extra contexts are available:
191
184
  :style: grid
192
185
 
193
186
  .. data.render::
194
- :extra: app
195
187
 
196
188
  {% set app = load_extra('app') %}
197
189
 
@@ -207,7 +199,6 @@ The following extra contexts are available:
207
199
  :style: grid
208
200
 
209
201
  .. data.render::
210
- :extra: env
211
202
 
212
203
  {% set env = load_extra('env') %}
213
204
 
@@ -215,7 +206,7 @@ The following extra contexts are available:
215
206
  documents found.
216
207
 
217
208
  ``markup``
218
- :Phase: parsing and later
209
+ :Phase: :term:`parsing`
219
210
 
220
211
  Information about the current directive or role invocation, such as its
221
212
  type, name, source text, and line number.
@@ -224,7 +215,6 @@ The following extra contexts are available:
224
215
  :style: grid
225
216
 
226
217
  .. data.render::
227
- :extra: markup
228
218
 
229
219
  {%
230
220
  set m = load_extra('markup')
@@ -238,22 +228,22 @@ The following extra contexts are available:
238
228
  {% endfor %}
239
229
 
240
230
  ``section``
241
- :Phase: parsing and later
231
+ :Phase: :term:`parsed` and :term:`resolving`
242
232
 
243
233
  A proxy to the current :py:class:`docutils.nodes.section` node, when one
244
- exists.
234
+ exists. This extra context is not available during the parsing phase.
245
235
 
246
236
  .. example::
247
237
  :style: grid
248
238
 
249
239
  .. data.render::
250
- :extra: section
240
+ :on: parsed
251
241
 
252
- Section Title:
242
+ Section Title:
253
243
  "{{ load_extra('section').title }}"
254
244
 
255
245
  ``doc``
256
- :Phase: parsing and later
246
+ :Phase: all
257
247
 
258
248
  A proxy to the current :py:class:`docutils.notes.document` node.
259
249
 
@@ -261,7 +251,6 @@ The following extra contexts are available:
261
251
  :style: grid
262
252
 
263
253
  .. data.render::
264
- :extra: doc
265
254
 
266
255
  Document title:
267
256
  "{{ load_extra('doc').title }}".
@@ -331,7 +320,6 @@ Each template has a render phase that determines when it is processed:
331
320
 
332
321
  .. data.render::
333
322
  :on: parsing
334
- :extra: doc env
335
323
 
336
324
  {% set doc = load_extra('doc') %}
337
325
  {% set env = load_extra('env') %}
@@ -354,7 +342,6 @@ Each template has a render phase that determines when it is processed:
354
342
 
355
343
  .. data.render::
356
344
  :on: parsed
357
- :extra: doc env
358
345
 
359
346
  {% set doc = load_extra('doc') %}
360
347
  {% set env = load_extra('env') %}
@@ -378,7 +365,6 @@ Each template has a render phase that determines when it is processed:
378
365
 
379
366
  .. data.render::
380
367
  :on: resolving
381
- :extra: doc env
382
368
 
383
369
  {% set doc = load_extra('doc') %}
384
370
  {% set env = load_extra('env') %}
@@ -57,11 +57,6 @@ Directives
57
57
 
58
58
  Enable :ref:`debug report <debug>` for template rendering.
59
59
 
60
- .. rst:directive:option:: extra
61
- :type: space separted list
62
-
63
- List of :ref:`extra-context` to be used in the template.
64
-
65
60
  The content of the directive should be Jinja2 Template, please refer to
66
61
  ::doc:`tmpl`.
67
62
 
@@ -116,7 +111,6 @@ Directives
116
111
 
117
112
  .. rst:directive:option:: on
118
113
  .. rst:directive:option:: debug
119
- .. rst:directive:option:: extra
120
114
 
121
115
  The options of this directive are same to :rst:dir:`data.template`.
122
116
 
@@ -22,14 +22,12 @@ from .data import (
22
22
  Schema,
23
23
  )
24
24
  from .template import Phase, Template
25
- from .ctx import PendingContext, ResolvedContext
25
+ from .ctx import UnresolvedContext, ResolvedContext
26
26
  from .ctxnodes import pending_node
27
27
  from .extractx import (
28
28
  extra_context,
29
- ParsingPhaseExtraContext,
30
- ParsedPhaseExtraContext,
31
- ResolvingPhaseExtraContext,
32
- GlobalExtraContext,
29
+ ExtraContext,
30
+ ExtraContextRequest,
33
31
  )
34
32
  from .pipeline import BaseContextRole, BaseContextDirective
35
33
  from .sources import (
@@ -56,12 +54,10 @@ __all__ = [
56
54
  'Schema',
57
55
  'Phase',
58
56
  'Template',
59
- 'PendingContext',
57
+ 'UnresolvedContext',
60
58
  'ResolvedContext',
61
- 'ParsingPhaseExtraContext',
62
- 'ParsedPhaseExtraContext',
63
- 'ResolvingPhaseExtraContext',
64
- 'GlobalExtraContext',
59
+ 'ExtraContext',
60
+ 'ExtraContextRequest',
65
61
  'extra_context',
66
62
  'pending_node',
67
63
  'BaseContextRole',
@@ -0,0 +1,23 @@
1
+ """
2
+ This module wraps the :py:mod:`sphinxnotes.render.data` module into context
3
+ suitable for use with Jinja templates.
4
+ """
5
+
6
+ from __future__ import annotations
7
+ from typing import Any
8
+ from abc import ABC, abstractmethod
9
+ from collections.abc import Hashable
10
+ from .data import ParsedData
11
+
12
+ type ResolvedContext = ParsedData | dict[str, Any]
13
+ """Resolved context types used by template rendering."""
14
+
15
+
16
+ class UnresolvedContext(ABC, Hashable):
17
+ """An abstract representation of context that will be resolved later."""
18
+
19
+ @abstractmethod
20
+ def resolve(self) -> ResolvedContext:
21
+ """This method will be called when rendering to get the available
22
+ :py:type:`ResolvedContext`."""
23
+ ...
@@ -1,17 +1,18 @@
1
1
  from __future__ import annotations
2
2
  from typing import TYPE_CHECKING, override
3
+ import pickle
3
4
  from pprint import pformat
4
5
 
5
6
  from docutils import nodes
6
7
  from docutils.parsers.rst.states import Inliner
7
8
 
8
- from .template import Template
9
+ from .data import ValueWrapper, ParsedData
10
+ from .template import Template, Phase
9
11
  from .ctx import (
10
- PendingContextRef,
11
- PendingContext,
12
- PendingContextStorage,
12
+ UnresolvedContext,
13
13
  ResolvedContext,
14
14
  )
15
+ from .extractx import ExtraContextRequest, extra_context_loader, extra_context_names
15
16
  from .markup import MarkupRenderer
16
17
  from .jinja import TemplateRenderer
17
18
  from .utils import (
@@ -21,7 +22,7 @@ from .utils import (
21
22
  )
22
23
 
23
24
  if TYPE_CHECKING:
24
- from typing import Any, Callable, ClassVar
25
+ from typing import Callable, Any
25
26
  from .markup import Host
26
27
  from .ctx import ResolvedContext
27
28
 
@@ -30,25 +31,19 @@ class pending_node(nodes.Element):
30
31
  """A docutils node to be rendered."""
31
32
 
32
33
  # The context to be rendered by Jinja template.
33
- ctx: PendingContextRef | ResolvedContext
34
- # The extra context as supplement to ctx.
35
- extra: dict[str, Any]
34
+ ctx: UnresolvedContext | ResolvedContext
36
35
  #: Jinja template for rendering the context.
37
36
  template: Template
38
37
  #: Whether rendering to inline nodes.
39
38
  inline: bool
40
39
  #: Whether the rendering pipeline is finished (failed is also finished).
41
40
  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()
41
+ #: Stored pickling error for later-phase unresolved context.
42
+ _ctx_pickle_error: Exception | None
48
43
 
49
44
  def __init__(
50
45
  self,
51
- ctx: PendingContext | ResolvedContext,
46
+ ctx: UnresolvedContext | ResolvedContext,
52
47
  tmpl: Template,
53
48
  inline: bool = False,
54
49
  rawsource='',
@@ -56,18 +51,20 @@ class pending_node(nodes.Element):
56
51
  **attributes,
57
52
  ) -> None:
58
53
  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)
63
- self.extra = {}
54
+ self._ctx_pickle_error = None
55
+ if isinstance(ctx, UnresolvedContext) and tmpl.phase != Phase.Parsing:
56
+ try:
57
+ pickle.dumps(ctx)
58
+ except Exception as exc:
59
+ self._ctx_pickle_error = exc
60
+ self.ctx = ctx
64
61
  self.template = tmpl
65
62
  self.inline = inline
66
63
  self.rendered = False
67
64
 
68
65
  # Init hook lists.
69
- self._pending_context_hooks = []
70
- self._resolved_data_hooks = []
66
+ self._unresolved_context_hooks = []
67
+ self._resolved_context_hooks = []
71
68
  self._markup_text_hooks = []
72
69
  self._rendered_nodes_hooks = []
73
70
 
@@ -75,7 +72,7 @@ class pending_node(nodes.Element):
75
72
  """
76
73
  The core function for rendering context and template to docutils nodes.
77
74
 
78
- 1. PendingContextRef -> PendingContext -> ResolvedContext
75
+ 1. UnresolvedContext -> ResolvedContext
79
76
  2. TemplateRenderer.render(ResolvedContext) -> Markup Text (``str``)
80
77
  3. MarkupRenderer.render(Markup Text) -> doctree Nodes (list[nodes.Node])
81
78
  """
@@ -97,48 +94,55 @@ class pending_node(nodes.Element):
97
94
  return report
98
95
  return Report('Render Report', 'ERROR', source=self.source, line=self.line)
99
96
 
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
97
+ if self._ctx_pickle_error is not None:
98
+ report = err_report()
99
+ report.text(
100
+ f'UnresolvedContext used by {self.template.phase} phase templates '
101
+ 'must be picklable:'
102
+ )
103
+ report.exception(self._ctx_pickle_error)
104
+ self += report
105
+ return None
111
106
 
112
- report.text('Pending context:')
107
+ # 1. Prepare context for Jinja template.
108
+ if isinstance(self.ctx, UnresolvedContext):
109
+ pdata = self.ctx
110
+ report.text('Unresolved context:')
113
111
  report.code(pformat(pdata), lang='python')
114
112
 
115
- for hook in self._pending_context_hooks:
113
+ for hook in self._unresolved_context_hooks:
116
114
  hook(self, pdata)
117
115
 
118
116
  try:
119
117
  ctx = self.ctx = pdata.resolve()
120
118
  except Exception as e:
121
119
  report = err_report()
122
- report.text('Failed to resolve pending context:')
120
+ report.text('Failed to resolve unresolved context:')
123
121
  report.exception(e)
124
122
  self += report
125
123
  return None
126
124
  else:
127
125
  ctx = self.ctx
128
126
 
129
- for hook in self._resolved_data_hooks:
127
+ for hook in self._resolved_context_hooks:
130
128
  hook(self, ctx)
131
129
 
132
130
  report.text(f'Resolved context (type: {type(ctx)}):')
133
131
  report.code(pformat(ctx), lang='python')
134
- report.text('Extra context (only keys):')
135
- report.code(pformat(list(self.extra.keys())), lang='python')
136
132
  report.text(f'Template (phase: {self.template.phase}):')
137
133
  report.code(self.template.text, lang='jinja')
138
134
 
135
+ extractx_req = ExtraContextRequest(self.template.phase, self, host.env, host)
136
+ report.text('Available extra context names:')
137
+ report.code(pformat(sorted(extra_context_names())), lang='python')
138
+
139
139
  # 2. Render the template and context to markup text.
140
140
  try:
141
- markup = TemplateRenderer(self.template.text).render(ctx, extra=self.extra)
141
+ markup = TemplateRenderer(self.template.text).render(
142
+ ctx,
143
+ globals={'load_extra': extra_context_loader(extractx_req)},
144
+ debug=self.template.debug,
145
+ )
142
146
  except Exception as e:
143
147
  report = err_report()
144
148
  report.text('Failed to render Jinja template:')
@@ -221,21 +225,21 @@ class pending_node(nodes.Element):
221
225
 
222
226
  """Hooks for processing render intermediate products."""
223
227
 
224
- type PendingContextHook = Callable[[pending_node, PendingContext], None]
228
+ type UnresolvedContextHook = Callable[[pending_node, UnresolvedContext], None]
225
229
  type ResolvedContextHook = Callable[[pending_node, ResolvedContext], None]
226
230
  type MarkupTextHook = Callable[[pending_node, str], str]
227
231
  type RenderedNodesHook = Callable[[pending_node, list[nodes.Node]], None]
228
232
 
229
- _pending_context_hooks: list[PendingContextHook]
230
- _resolved_data_hooks: list[ResolvedContextHook]
233
+ _unresolved_context_hooks: list[UnresolvedContextHook]
234
+ _resolved_context_hooks: list[ResolvedContextHook]
231
235
  _markup_text_hooks: list[MarkupTextHook]
232
236
  _rendered_nodes_hooks: list[RenderedNodesHook]
233
237
 
234
- def hook_pending_context(self, hook: PendingContextHook) -> None:
235
- self._pending_context_hooks.append(hook)
238
+ def hook_unresolved_context(self, hook: UnresolvedContextHook) -> None:
239
+ self._unresolved_context_hooks.append(hook)
236
240
 
237
241
  def hook_resolved_context(self, hook: ResolvedContextHook) -> None:
238
- self._resolved_data_hooks.append(hook)
242
+ self._resolved_context_hooks.append(hook)
239
243
 
240
244
  def hook_markup_text(self, hook: MarkupTextHook) -> None:
241
245
  self._markup_text_hooks.append(hook)
@@ -259,3 +263,16 @@ class pending_node(nodes.Element):
259
263
  def deepcopy(self) -> Any:
260
264
  # NOTE: Same to :meth:`copy`.
261
265
  return self.copy()
266
+
267
+ @override
268
+ def astext(self) -> str:
269
+ ctx = self.ctx
270
+ if isinstance(ctx, UnresolvedContext):
271
+ try:
272
+ ctx = ctx.resolve()
273
+ except Exception:
274
+ return ''
275
+ if isinstance(ctx, ParsedData):
276
+ return ValueWrapper(ctx.content).as_str() or ''
277
+ else:
278
+ return ''
@@ -14,8 +14,6 @@ import re
14
14
  from dataclasses import dataclass, asdict, field as dataclass_field
15
15
  from ast import literal_eval
16
16
 
17
- from .utils import Unpicklable
18
-
19
17
  if TYPE_CHECKING:
20
18
  from typing import Any, Callable, Generator, Self
21
19
 
@@ -287,7 +285,7 @@ class ParsedData:
287
285
 
288
286
 
289
287
  @dataclass
290
- class Field(Unpicklable):
288
+ class Field:
291
289
  #: Type of element.
292
290
  etype: type = str
293
291
  #: Type of container (if the field holds multiple values).
@@ -374,8 +372,9 @@ class Field(Unpicklable):
374
372
  raise ValueError(f"Failed to parse '{rawval}' as {self.etype}: {e}") from e
375
373
 
376
374
  def __getattr__(self, name: str) -> Value:
377
- if name in self.flags:
378
- return self.flags[name]
375
+ flags = self.__dict__.get('flags')
376
+ if flags is not None and name in flags:
377
+ return flags[name]
379
378
  raise AttributeError(name)
380
379
 
381
380
 
@@ -491,7 +490,7 @@ class DSLParser:
491
490
 
492
491
 
493
492
  @dataclass(frozen=True)
494
- class Schema(Unpicklable):
493
+ class Schema:
495
494
  name: Field | None
496
495
  attrs: dict[str, Field] | Field
497
496
  content: Field | None
@@ -33,7 +33,7 @@ if TYPE_CHECKING:
33
33
  from types import ModuleType
34
34
  from docutils.utils import Reporter
35
35
  from sphinx.util.typing import RoleFunction
36
- from .. import PendingContext, ResolvedContext
36
+ from .. import UnresolvedContext, ResolvedContext
37
37
 
38
38
 
39
39
  # Keys of env.temp_data.
@@ -50,19 +50,15 @@ class TemplateDefineDirective(SphinxDirective):
50
50
  option_spec = {
51
51
  'on': phase_option_spec,
52
52
  'debug': directives.flag,
53
- 'extra': directives.unchanged,
54
53
  }
55
54
  has_content = True
56
55
 
57
56
  @override
58
57
  def run(self) -> list[nodes.Node]:
59
- extra = self.options.get('extra', '')
60
-
61
58
  self.env.temp_data[TEMPLATE_KEY] = Template(
62
59
  '\n'.join(self.content),
63
60
  phase=self.options.get('on', Phase.default()),
64
61
  debug='debug' in self.options,
65
- extra=extra.split() if extra else [],
66
62
  )
67
63
 
68
64
  return []
@@ -135,24 +131,19 @@ class DataRenderDirective(BaseContextDirective):
135
131
  option_spec = {
136
132
  'on': phase_option_spec,
137
133
  'debug': directives.flag,
138
- 'extra': directives.unchanged,
139
134
  }
140
135
  has_content = True
141
136
 
142
137
  @override
143
- def current_context(self) -> PendingContext | ResolvedContext:
138
+ def current_context(self) -> UnresolvedContext | ResolvedContext:
144
139
  return {}
145
140
 
146
141
  @override
147
142
  def current_template(self) -> Template:
148
- extra_str = self.options.get('extra', '')
149
- extra_list = extra_str.split() if extra_str else []
150
-
151
143
  return Template(
152
144
  '\n'.join(self.content),
153
145
  phase=self.options.get('on', Phase.default()),
154
146
  debug='debug' in self.options,
155
- extra=extra_list,
156
147
  )
157
148
 
158
149
 
@@ -31,7 +31,6 @@ DATA_DEFINE_DIRECTIVE = DictSchema(
31
31
  Optional('on', default='parsing'): Or('parsing', 'parsed', 'resolving'),
32
32
  'text': str,
33
33
  Optional('debug', default=False): bool,
34
- Optional('extra', default=[]): list,
35
34
  },
36
35
  }
37
36
  )
@@ -50,7 +49,6 @@ def _validate_directive_define(d: dict, config: Config) -> tuple[Schema, Templat
50
49
  text=tmpldef['text'],
51
50
  phase=Phase[tmpldef['on'].title()],
52
51
  debug=tmpldef['debug'],
53
- extra=tmpldef['extra'],
54
52
  )
55
53
 
56
54
  return schema, template