From the document, Jinja template engine is very flexible. A template contains variables and/or expressions, which get replaced with values when a template is rendered. {{ ... }} for Expressions to print to the template output. Template variables are defined by the context dictionary passed to the template. You can use a dot (.) to access attributes of a variable. If a variable or attribute does not exist, you will get back an undefined value.
Call trace is found as follows:
render_templaate('temp.html', **context) in flask/templating.py which imports class Environment and class Template from jinja2/environment.py. ==>>
_render(template: Template, context: dict, app: "Flask") ==>>
rv = template.render(context) # rv will have the rendered string ==>>
def render(self, *args: t.Any, **kwargs: t.Any) # class Template in environment.py
{
ctx = self.new_context(dict(*args, **kwargs))
try:
return concat(self.root_render_func(ctx)) # type: ignore
except Exception:
self.environment.handle_exception()
}
where ctx is a new Context class that has all the class Template environment such as jinja2 lexer, compiler, CodeGnerator, loader and
variable_start_string: str = VARIABLE_START_STRING, Defaults to ``'{{'``.
variable_end_string: str = VARIABLE_END_STRING, Defaults to ``'}}'``.
The strings marking the beginning and end of a print statement.
The context/args variables such as 'fruit' object will be passed to the template via ctx.parent['fruit'].
If the object is missing in the context dict (args -> ctx.parent) or has no attribute, a jinja2.exception.UndefinedError will be raised.
The statement self.root_render_func(ctx) is the source code as far as I can trace.
The way it uses keyword argument **context is same as the String format() Method str.format(*args, **kwargs) as shown in the code below.
hourlyRate = { "Monday" : 20, "Tuesday" : 25, "Wednesday" : 30, "Thursday" : 35,
"Friday" : 40, "Saturday" : 50, "Sunday" : 60 }
def weeklyPay(**weekly_hrs):
sum = 0
for i in weekly_hrs:
sum += weekly_hrs[i] * hourlyRate[i]
print(f"Weekly work hours = {weekly_hrs} \t=> Week pay = {sum}")
# Simulating the String format() Method. accessing arguments’ attributes
def sFormat(**cmplx):
c1 = cmplx['c1']
c2 = cmplx['c2']
print('Inside sFormat c1 = %.2f + %.2fj' % (c1.real, c1.imag))
print('Inside sFormat c2 = %.2f + %.2fj' % (c2.real, c2.imag))
print(f'sFormat(**c): {c1} formed from real part {c1.real} and imaginary part {c1.imag}\n'
f'sFormat(**c): {c2} formed from real part {c2.real} and imaginary part {c2.imag}')
if __name__ == "__main__":
# A function taking wild/variable number of keyword arguments
weeklyPay(**{'Friday' : 7, "Tuesday" : 10, "Wednesday" : 6})
weeklyPay(Monday = 10, Thursday = 6, Saturday = 4)
weeklyPay(Saturday = 5, **{"Wednesday": 12, "Thursday": 7})
weeklyPay(**{"Friday": 10, "Sunday": 5}, Wednesday = 8, Thursday = 10)
print()
# str. format(*args, **kwargs)
print('String formatting {c1} with real part {c1.real} and imaginary part {c1.imag}\n'
'String formatting {c2} with real part {c2.real} and imaginary part {c2.imag}'
.format(**{"c1":8+20j, "c2":19-7j}))
print('String formatting {c1} with real part {c1.real} and imaginary part {c1.imag}\n'
'String formatting {c2} with real part {c2.real} and imaginary part {c2.imag}'
.format(c1 = 71 + 92j, c2 = 34 - 82j))
# Simulated string formatting
sFormat(**{'c1':3+5j, 'c2':6-10j})
sFormat(c1=30+50j, c2=15-25j)
Output:
Weekly work hours = {'Friday': 7, 'Tuesday': 10, 'Wednesday': 6} => Week pay = 710
Weekly work hours = {'Monday': 10, 'Thursday': 6, 'Saturday': 4} => Week pay = 610
Weekly work hours = {'Saturday': 5, 'Wednesday': 12, 'Thursday': 7} => Week pay = 855
Weekly work hours = {'Friday': 10, 'Sunday': 5, 'Wednesday': 8, 'Thursday': 10} => Week pay = 1290
String formatting (8+20j) with real part 8.0 and imaginary part 20.0
String formatting (19-7j) with real part 19.0 and imaginary part -7.0
String formatting (71+92j) with real part 71.0 and imaginary part 92.0
String formatting (34-82j) with real part 34.0 and imaginary part -82.0
Inside sFormat c1 = 3.00 + 5.00j
Inside sFormat c2 = 6.00 + -10.00j
sFormat(**c): (3+5j) formed from real part 3.0 and imaginary part 5.0
sFormat(**c): (6-10j) formed from real part 6.0 and imaginary part -10.0
Inside sFormat c1 = 30.00 + 50.00j
Inside sFormat c2 = 15.00 + -25.00j
sFormat(**c): (30+50j) formed from real part 30.0 and imaginary part 50.0
sFormat(**c): (15-25j) formed from real part 15.0 and imaginary part -25.0
On a side note, the AST tree structure helps me better understand compiler design. Below is a Python program for ast_node_visit.py.
class Node:
def __init__(self, value, level):
self.value = value
self.level = level
self.children = []
def child(self, value):
child = Node(value, self.level + 1)
self.children.append(child)
return child
def visit(self):
for n in self.children:
print(f'Node : {self.value}, child : {n.value} @ level {n.level}')
n.visit()
a = Node('A', 0)
b = a.child('B')
c = a.child('C')
d = b.child('D')
e = b.child('E')
f = c.child('F')
g = c.child('G')
h = c.child('H')
i = e.child('I')
j = g.child('J')
a.visit()
output:
Node : A, child : B @ level 1
Node : B, child : D @ level 2
Node : B, child : E @ level 2
Node : E, child : I @ level 3
Node : A, child : C @ level 1
Node : C, child : F @ level 2
Node : C, child : G @ level 2
Node : G, child : J @ level 3
Node : C, child : H @ level 2
where "level" roughly aligns with the Python code's indentation. You can add the following 'main' function in your source code and check out the 'tree.body' attribute:
import ast
def main(srcCode):
with open(srcCode, 'r') as source:
tree = ast.parse(source.read())
if __name__ == '__main__':
main(__file__)