Skip to content

Commit cd48f91

Browse files
fsecada01claude
andauthored
docs(jinjax): document sharing an existing Catalog with JinjaxRenderer (closes #13) (#18)
* docs(jinjax): document sharing an existing Catalog with JinjaxRenderer (closes #13) Minimal examples create a fresh `Catalog()`, which has its own empty Jinja environment — component templates then silently lose the host app's custom filters, globals (e.g. url_for), and extensions and render incorrectly. - README: add a "Sharing an existing JinjaX catalog" subsection showing the Jinja2Templates env-sharing pattern and `Component.renderer = JinjaxRenderer(existing_catalog)`, with a warning about fresh catalogs. - JinjaxRenderer.__init__: enrich the docstring (surfaces in the pdoc API reference) to direct users to pass their existing catalog. - Add tests/test_jinjax_renderer.py proving a shared catalog's globals and filters reach rendered components, plus a contrast test showing a fresh catalog lacks them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017hdmTV88wAXU8ew1rorMjt * docs(jinjax): use fastapi.templating import in catalog example --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 52c341b commit cd48f91

3 files changed

Lines changed: 99 additions & 3 deletions

File tree

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,37 @@ python examples/fastapi_example.py
162162
# Open http://localhost:8000
163163
```
164164

165+
#### Sharing an existing JinjaX catalog
166+
167+
Most FastAPI + JinjaX projects already create a `Catalog` at startup — often
168+
sharing its Jinja environment with `Jinja2Templates` so page templates and
169+
components see the same custom filters, globals (e.g. `url_for`), and extensions.
170+
171+
**Pass that existing catalog to `JinjaxRenderer`** — do not create a new one:
172+
173+
```python
174+
# consts.py — your existing setup
175+
from fastapi.templating import Jinja2Templates
176+
from jinjax import Catalog, JinjaX
177+
178+
templates = Jinja2Templates(directory="templates")
179+
templates.env.add_extension(JinjaX) # share one Jinja environment
180+
templates.env.globals["url_for"] = my_url_for # custom global
181+
catalog = Catalog(jinja_env=templates.env) # catalog reuses that env
182+
catalog.add_folder("templates/components")
183+
184+
# Wire the component framework to the SAME catalog
185+
from component_framework.adapters.jinjax_renderer import JinjaxRenderer
186+
from component_framework.core.component import Component
187+
188+
Component.renderer = JinjaxRenderer(catalog) # ✅ inherits filters/globals/extensions
189+
```
190+
191+
> ⚠️ Creating a **fresh** `Catalog()` (as in minimal examples) gives it a *new,
192+
> empty* Jinja environment. Component templates then silently lose your
193+
> `url_for`, custom filters, and extensions and render incorrectly. Always hand
194+
> `JinjaxRenderer` the catalog your app already configured.
195+
165196
### Django Example
166197

167198
```bash

src/component_framework/adapters/jinjax_renderer.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,23 @@
1111

1212

1313
class JinjaxRenderer(Renderer):
14-
"""Renderer using Jinjax component system."""
14+
"""Renderer using the JinjaX component system."""
1515

1616
def __init__(self, catalog: Catalog):
1717
"""
18-
Initialize Jinjax renderer.
18+
Initialize the JinjaX renderer.
19+
20+
Pass the ``Catalog`` your application already configured rather than a
21+
fresh one. The renderer renders through this catalog's Jinja
22+
environment, so it inherits every custom filter, global (e.g.
23+
``url_for``), and extension registered on it. A brand-new ``Catalog()``
24+
has its own empty environment, so component templates would silently
25+
lose that context. When sharing with ``Jinja2Templates``, build the
26+
catalog from the same env, e.g. ``Catalog(jinja_env=templates.env)``.
1927
2028
Args:
21-
catalog: Jinjax Catalog instance
29+
catalog: The JinjaX ``Catalog`` instance to render through —
30+
ideally the one shared with the rest of your app.
2231
"""
2332
self.catalog = catalog
2433

tests/test_jinjax_renderer.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""Tests for JinjaxRenderer, focused on sharing an existing Catalog (issue #13).
2+
3+
The documented guidance is that consumers should pass their *existing* JinjaX
4+
``Catalog`` to ``JinjaxRenderer`` so component templates inherit the host app's
5+
custom globals, filters, and extensions. These tests lock that behavior in.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
import pytest
11+
12+
pytest.importorskip("jinjax")
13+
14+
from jinjax import Catalog # noqa: E402
15+
16+
from component_framework.adapters.jinjax_renderer import JinjaxRenderer # noqa: E402
17+
18+
19+
def _write_component(folder, name: str, body: str) -> None:
20+
(folder / f"{name}.jinja").write_text(body, encoding="utf-8")
21+
22+
23+
def test_renderer_uses_shared_catalog_globals_and_filters(tmp_path):
24+
"""A Catalog whose jinja_env carries custom globals/filters is honored by
25+
JinjaxRenderer — this is the 'share your existing catalog' pattern from the
26+
docs."""
27+
components = tmp_path / "components"
28+
components.mkdir()
29+
_write_component(
30+
components,
31+
"Greeting",
32+
"{# def name #}\n<p>{{ url_for('home') }} {{ name | shout }}</p>",
33+
)
34+
35+
catalog = Catalog()
36+
catalog.jinja_env.globals["url_for"] = lambda route: f"/{route}/"
37+
catalog.jinja_env.filters["shout"] = lambda value: str(value).upper()
38+
catalog.add_folder(str(components))
39+
40+
html = JinjaxRenderer(catalog).render("Greeting", {"name": "world"})
41+
42+
assert "/home/" in html # shared global reached the component
43+
assert "WORLD" in html # shared filter reached the component
44+
45+
46+
def test_fresh_catalog_does_not_inherit_host_globals():
47+
"""Contrast: a fresh Catalog() (the README's old example) starts with an
48+
empty jinja_env, so it lacks the host app's globals — exactly the silent
49+
breakage issue #13 documents."""
50+
shared = Catalog()
51+
shared.jinja_env.globals["url_for"] = lambda route: f"/{route}/"
52+
53+
fresh = Catalog()
54+
55+
assert "url_for" in shared.jinja_env.globals
56+
assert "url_for" not in fresh.jinja_env.globals

0 commit comments

Comments
 (0)