Skip to content

Build the render context without per-key ChainMap lookups#2185

Open
frenck wants to merge 1 commit into
pallets:mainfrom
frenck:frenck/chainmap-render-context
Open

Build the render context without per-key ChainMap lookups#2185
frenck wants to merge 1 commit into
pallets:mainfrom
frenck:frenck/chainmap-render-context

Conversation

@frenck

@frenck frenck commented Jun 13, 2026

Copy link
Copy Markdown

A template's globals are stored as a collections.ChainMap that layers the template globals over the environment globals (see Environment.make_globals).

On every render runtime.new_context flattens that into the context parent dict with dict(globals or (), **vars). Since globals is a ChainMap, that calls ChainMap.__getitem__ once for every key. On top of that Context.__init__ walks the same mapping again with ChainMap.__iter__ to build globals_keys

Flattening to a plain dict is fine and worth keeping, it makes the later resolve_or_missing lookups fast. This PR only changes how the flatten happens: it merges the ChainMaps .maps with dict.update (and set.union for globals_keys) instead of going through the ChainMap. The result is the same dict, it just takes fewer calls to build the result.

It's per-render work that grows with the number of globals, and every template pays it. Even

Environment().from_string("{{ x }}").render(x=1)

does 6 ChainMap.__getitem__ and 2 ChainMap.__iter__ calls for the 6 default globals. After this change it does none.

Whether that's noticeable depends a lot on how Jinja is used. As the FAQ says, for plenty of apps the engine is dwarfed by database or API calls and you won't see any difference. Where it does help is the other case: lots of small renders, a decent number of globals, and little I/O per render. Like Home Assistant 😄 (2M+ installs) where templates render off an in-memory state machine in the hot path, so doing fewer calls per render adds up across a lot of renders.

Behavior is unchanged either way, it's the same output with less work.

One honest note on the tests: The contributing guide asks for tests that fail without the change, and these don't, because there's no behavior change to catch. I added them anyway as a safety net.

../Frenck

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant