Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork100
Description
Context
When a template has an expression{{ ... }} or a tag{% ... %}, we can treat it as a function - same input should return same output.
I'm currently preparing the template AST parser. And in order to support#1083 in the future, I'll be adding a feature to the AST parser to keep track of which context variables were used in an expression or a tag.
So e.g. in this template:
<div> {{ title }}</div>{%component"user_settings"settings=settingsuser=usertitle="{{ title }}: {{ user.first_name }} {{ user.last_name }}"/%}
We have:
- Expression that uses 1 variable -
title user_settingscomponent tag that uses 3 variables -settings,user, andtitle.
What#1083 does is that if these variables are marked asConst, then we can replace these expressions and tags with static values. Saving us from having to re-compute these values in the future.
E.g. iftitle was statically defined asConst("User settings"), then after the first render we'd replace{{ title }} with the actual value:
<div> User settings</div>{%component"user_settings"settings=settingsuser=usertitle="User settings: {{ user.first_name }} {{ user.last_name }}"/%}
But expressions like{{ user.first_name }} can never be static - they depend on the logged-in user.
Yet, right now we re-compute the value of{{ user.first_name }} every time, even if I hit this page with the same user hundred times.
Proposal
With the upcoming AST, if we store which variables were used inside of a tag or expression, we could use that to cache the results of expressions, and avoid re-computing them.
It would look roughly like this (pseudocode):
classExpr:defrender(self,context):expr_str=self.raw# e.g. `{{ user.first_name }}`used_vars=self.used_vars# ["user"]expr_data= {key:context[key]forkeyinused_vars}# {"user": User<id=1>}cache_key=expr_str+"::"+hash(frozenset(var_values.items()))component_context=get_closest_component(context)ifcomponent_contextandcomponent_context.expressions_cache:expr_cache=component_context.expressions_cache# E.g. caches["djc-expressions"]else:expr_cache=None# Case: This expr was already rendered with this inputifexpr_cacheandcache_keyinexpr_cache:returnexpr_cache[cache_key]# Case: First time rendering this expr with this input, cache itelifexpr_cacheisnotNone:result=super().render(context)expr_cache[cache_key]=resultreturnresult# Case: NOT caching the expression resultelse:result=super().render(context)returnresult
Tho I'm concerned that caching every single expression could be actually detrimental as it might introduce too much overhead.
A middle ground could be to walk down the template nodes, and for each one check which variables it uses in itself and it's body.
To achieve that , each AST node should report not only which variables it uses in its tag ({% ... %}), but also what variables it uses in it's body.
So imagine I walk down the following template:
<div>{# <-- 1 #}{%component"table"headers=table_headersrows=rows%}{# <-- 2 #}{%fill"pagination"%}{# <-- 3 #}{%component"button"%}{# <-- 4 #} Edit user: {{ user.username }}{# <-- 5 #}{%endcomponent%}{%endfill%}{%endcomponent%}</div>
I start at the level of the entire template. I see that the entire body uses variables
table_headers,rows,anduserLet's say that
table_headersanduservariables are the same as the previous render, butrowshas new data. Nothing we can do on the template-level.Then we reach
{% component "table" %}. It again uses the same variables,table_headers,rows, anduser, so nothing to do.Next we visit the
{% fill "pagination" %}node. This already contains only 1 variable -user.Since we had previously rendered this section with the same
userentry, we can access the cached result for the entire{% fill "pagination" %}section.Thus, we managed to skip the rendering of
{% component "button" %}and{{ user.username }}.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status