jinjax icon indicating copy to clipboard operation
jinjax copied to clipboard

0.53 prefix changes may break rendering JinjaX components in Jinja2 templates

Open jodal opened this issue 8 months ago • 4 comments

I'm sorry I don't have the time to make a minimal reproduction, so feel free to close this if you cannot reproduce or believe the error is elsewhere.

I suspect that the prefix changes in 0.53 breaks for projects where the Jinja2 env is configured with:

jinja2.Environment(
  ...
  undefined=jinja2.StrictUndefined,
  ...
)

Here's the output from one of our test cases when using JinjaX 0.54 (the test passes on 0.52):

.venv/lib/python3.13/site-packages/jinja2/environment.py:1295: in render
    self.environment.handle_exception()
.venv/lib/python3.13/site-packages/jinja2/environment.py:942: in handle_exception
    raise rewrite_traceback_stack(source=source)
<template>:1: in top-level template code
    ???
src/common/jinjax.py:33: in irender
    return super().irender(  # pyright: ignore[reportUnknownMemberType]
.venv/lib/python3.13/site-packages/jinjax/catalog.py:434: in irender
    component = self._get_component(__name, **kw)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <common.jinjax.Catalog object at 0x7f0e3ec51bd0>, cname = 'Py.Tests.Card', kw = {'title': 'my-title'}, source = ''
file_ext = '.j2', caller_prefix = Undefined, prefix = '', name = 'Py.Tests.Card', component = None
get_from = <bound method Catalog._get_from_cache of <common.jinjax.Catalog object at 0x7f0e3ec51bd0>>

    def _get_component(self, cname: str, **kw) -> Component:
        source = kw.pop("_source", kw.pop("__source", ""))
        file_ext = kw.pop("_file_ext", kw.pop("__file_ext", "")) or self.file_ext
        caller_prefix = kw.pop("__prefix", "")

        prefix, name = self._split_name(cname)
        component = None

        if source:
            logger.debug("Rendering from source %s", cname)
            self.jinja_env.loader = self.prefixes[prefix]
            return self._get_from_source(prefix=prefix, name=name, source=source)

        logger.debug("Rendering from cache or file %s", cname)
        get_from = self._get_from_cache if self.use_cache else self._get_from_file
>       if caller_prefix:
E       jinja2.exceptions.UndefinedError: '__prefix' is undefined

.venv/lib/python3.13/site-packages/jinjax/catalog.py:620: UndefinedError

jodal avatar Apr 09 '25 14:04 jodal

@jodal Sorry I'm unable to replicate this error with any end-to-end test, maybe is the way your test is written?

In the latest release (0.55) I've moved the __prefix injection to the Component.render method (https://github.com/jpsca/jinjax/blob/main/src/jinjax/component.py#L257), could you test if that solves the issue?

jpsca avatar Apr 11 '25 17:04 jpsca

I'm on vacation with spotty internet access now, but I'll try it out before next weekend. Thanks!

jodal avatar Apr 13 '25 19:04 jodal

@jodal Sorry I'm unable to replicate this error with any end-to-end test, maybe is the way your test is written?

In the latest release (0.55) I've moved the __prefix injection to the Component.render method (main/src/jinjax/component.py#L257), could you test if that solves the issue?

The issue seems to persist in jinjax==0.55, unfortunately.

_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.venv/lib/python3.13/site-packages/jinja2/environment.py:1295: in render
    self.environment.handle_exception()
.venv/lib/python3.13/site-packages/jinja2/environment.py:942: in handle_exception
    raise rewrite_traceback_stack(source=source)
<template>:1: in top-level template code
    ???
.venv/lib/python3.13/site-packages/jinjax/catalog.py:443: in irender
    component = self._get_component(__name, **kw)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <common.jinjax.Catalog object at 0x7adf5ae872d0>, cname = 'Py.Tests.Card', kw = {'title': 'my-title'}, source = '', file_ext = '.j2', caller_prefix = Undefined, prefix = '', name = 'Py.Tests.Card', component = None, get_from = <bound method Catalog._get_from_cache of <common.jinjax.Catalog object at 0x7adf5ae872d0>>

    def _get_component(self, cname: str, **kw) -> Component:
        source = kw.pop("_source", kw.pop("__source", ""))
        file_ext = kw.pop("_file_ext", kw.pop("__file_ext", "")) or self.file_ext
        caller_prefix = kw.pop(ARGS_PREFIX, "")
    
        prefix, name = self._split_name(cname)
        component = None
    
        if source:
            logger.debug("Rendering from source %s", cname)
            self.jinja_env.loader = self.prefixes[prefix]
            return self._get_from_source(prefix=prefix, name=name, source=source)
    
        logger.debug("Rendering from cache or file %s", cname)
        get_from = self._get_from_cache if self.use_cache else self._get_from_file
>       if caller_prefix:
E       jinja2.exceptions.UndefinedError: '__prefix' is undefined

.venv/lib/python3.13/site-packages/jinjax/catalog.py:628: UndefinedError
=================================================================================================================================================================================== short test summary info ====================================================================================================================================================================================
FAILED tests/hub/core/test_htpy.py::test_jinjax_rendering_of_component_with_children - jinja2.exceptions.UndefinedError: '__prefix' is undefined

JakobGM avatar Apr 14 '25 08:04 JakobGM

I tried hunting down the issue for a while longer, but I don't know enough Jinja2 and JinjaX internals to understand everything I'm looking at.

I can at least confirm that jinja2.StrictUndefined does not seem to be related in any way.

In the end I ended up rewriting a part of our test from rendering a Jinja2 template with a JinjaX component:

    template = catalog.jinja_env.from_string(
        '<Py.Tests.MyComponent title="my-title">my-content</Py.Tests.MyComponent>'
    )

    assert template.render() == "..."

To rendering the JinjaX component directly:

    result = catalog.render(
        "Py.Tests.MyComponent",
        title="my-title",
        _content="my-content",
    )

    assert result == "..."

This is good enough for our test case to cover what we need it to cover. I have not found any other issues through manual testing with JinjaX 0.53-0.55.

Unless you immediately see the issue here and this is something you want to keep working as it used to do, feel free to close this issue.

jodal avatar Apr 16 '25 22:04 jodal