Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ def setup(app):

sys.path.insert(0, test_roots)
__import__('test-extra-context.conf').conf.setup(app)
__import__('test-extra-context-params.conf').conf.setup(app)
__import__('test-base-context-directive-example.conf').conf.setup(app)
__import__('test-base-data-define-directive-example.conf').conf.setup(app)
__import__('test-strict-data-define-directive-example.conf').conf.setup(app)
Expand Down
23 changes: 22 additions & 1 deletion docs/ext.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ Extra contexts are registered by a
:py:deco:`sphinxnotes.render.extra_context` class decorator.

The decorated class must be a subclass of
:py:class:`~sphinxnotes.render.ExtraContext`.
:py:class:`~sphinxnotes.render.ExtraContext`. The ``generate()`` method
accepts the ``ExtraContextRequest`` as the first argument, plus any
positional and keyword arguments passed by the template via
``load_extra()``.

.. literalinclude:: ../tests/roots/test-extra-context/conf.py
:language: python
Expand All @@ -88,6 +91,24 @@ The decorated class must be a subclass of

{{ load_extra('cat').name }}

To accept custom parameters in your extra context, add ``*args`` and
``**kwargs`` to the ``generate()`` method signature:

.. literalinclude:: ../tests/roots/test-extra-context-params/conf.py
:language: python
:start-after: [literalinclude start]
:end-before: [literalinclude end]

.. example::
:style: grid

.. data.render::

{% set docs = load_extra('all_docs', 3) %}
{% for doc in docs %}
- :doc:`{{ doc }}`
{% endfor %}

.. _ext-filters:

Extending ilters
Expand Down
10 changes: 9 additions & 1 deletion docs/tmpl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,9 @@ which comes from the directive/role itself, extra context lets you fetch data
that was prepared beforehand.

Extra contexts are generated on demand. Load them in the template using the
``load_extra()`` function.
``load_extra()`` function. You can also pass positional and keyword arguments
to ``load_extra()``, which are forwarded to the extra context's
``generate()`` method.

.. example::
:style: grid
Expand All @@ -170,6 +172,12 @@ Extra contexts are generated on demand. Load them in the template using the

Document Title: "{{ doc.title }}"

.. data.render::

{% set docs = load_extra('all_docs', count=3) %}

{{ docs | join(', ') }}

Built-in Extra Contexts
~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
8 changes: 2 additions & 6 deletions src/sphinxnotes/render/ext/extractx.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ class MarkupExtraContext(ExtraContext):
def generate(self, req: ExtraContextRequest) -> Any:
host = req.host
if not isinstance(host, (SphinxDirective, SphinxRole)):
raise ValueError(
f'Extra context "markup" is not available at phase {req.phase}.'
)
raise ValueError(f'Not available at phase {req.phase}')
isdir = isinstance(host, SphinxDirective)
return {
'type': 'directive' if isdir else 'role',
Expand All @@ -56,9 +54,7 @@ class SectionExtraContext(ExtraContext):
@override
def generate(self, req: ExtraContextRequest) -> Any:
if req.phase == Phase.Parsing:
raise ValueError(
f'Extra context "section" is not available at phase {req.phase}.'
)
raise ValueError(f'Not available at phase {req.phase}')
if req.node.parent is not None:
parent = req.node.parent
elif isinstance(req.host, SphinxDirective):
Expand Down
6 changes: 3 additions & 3 deletions src/sphinxnotes/render/extractx.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class ExtraContext(ABC):
"""Base class of extra context."""

@abstractmethod
def generate(self, req: ExtraContextRequest) -> Any: ...
def generate(self, req: ExtraContextRequest, *args, **kwargs) -> Any: ...


# ==========================
Expand Down Expand Up @@ -82,7 +82,7 @@ def extra_context_names() -> set[str]:


def extra_context_loader(request: ExtraContextRequest):
def load_extra(name: str) -> Any:
def load_extra(name: str, *args, **kwargs) -> Any:
ctx = _REGISTRY.get(name)
if ctx is None:
raise ValueError(
Expand All @@ -91,7 +91,7 @@ def load_extra(name: str) -> Any:
)

try:
return ctx.generate(request)
return ctx.generate(request, *args, **kwargs)
except Exception as e:
raise ValueError(f'Failed to load extra context "{name}".') from e

Expand Down
22 changes: 22 additions & 0 deletions tests/roots/test-extra-context-params/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# [literalinclude start]
from sphinxnotes.render import (
extra_context,
ExtraContext,
ExtraContextRequest,
)


@extra_context('all_docs')
class AllDocsExtraContext(ExtraContext):
def generate(self, req: ExtraContextRequest, *args, **kwargs):
count = args[0] if args else kwargs.get('count', 5)
return sorted(req.env.all_docs.keys())[:count]


# [literalinclude end]

extensions = ['sphinxnotes.render.ext']

keep_warnings = True

def setup(app): ...
8 changes: 8 additions & 0 deletions tests/roots/test-extra-context-params/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Extra Context Params Test
=========================

.. data.render::
:on: resolving
:debug:

{{ load_extra('all_docs', 3) }}
2 changes: 2 additions & 0 deletions tests/roots/test-extra-context/conf.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# [literalinclude start]
from os import path
import json
from typing import override

from sphinxnotes.render import (
extra_context,
Expand All @@ -11,6 +12,7 @@

@extra_context('cat')
class CatExtraContext(ExtraContext):
@override
def generate(self, req: ExtraContextRequest):
with open(path.join(path.dirname(__file__), 'cat.json')) as f:
return json.loads(f.read())
Expand Down
10 changes: 10 additions & 0 deletions tests/test_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ def test_extra_context_custom_loader(app, status, warning):
assert 'mimi' in html


@pytest.mark.sphinx('html', testroot='extra-context-params')
def test_extra_context_params(app, status, warning):
app.build()

html = (app.outdir / 'index.html').read_text(encoding='utf-8')

assert '['index']' in html



@pytest.mark.sphinx('html', testroot='extra-context-rebuild')
def test_extra_context_rebuild(app, status, warning):
app.build()
Expand Down
23 changes: 22 additions & 1 deletion tests/test_extractx.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class CountingExtraContext(ExtraContext):
def __init__(self) -> None:
self.calls = 0

def generate(self, req):
def generate(self, req, *args, **kwargs):
self.calls += 1
return self.calls

Expand All @@ -34,3 +34,24 @@ def test_extra_context_loader_does_not_cache_values():
assert load_extra(name) == 2
finally:
_REGISTRY.ctxs.pop(name, None)


class ParamExtraContext(ExtraContext):
def generate(self, req, *args, **kwargs):
return {'args': args, 'kwargs': kwargs}


def test_extra_context_loader_passes_parameters():
name = 'test_params'
_REGISTRY.register(name, ParamExtraContext())

try:
node = pending_node({}, Template(''))
host = SimpleNamespace(env=SimpleNamespace())
req = ExtraContextRequest(Template('').phase, node, host.env, host)
load_extra = extra_context_loader(req)

result = load_extra(name, 10, limit=20)
assert result == {'args': (10,), 'kwargs': {'limit': 20}}
finally:
_REGISTRY.ctxs.pop(name, None)