53

I am trying to get list of all variables and blocks in a template. I don't want to create my own parser to find variables. I tried using following snippet.

from jinja2 import Environment, PackageLoader
env = Environment(loader=PackageLoader('gummi', 'templates'))
template = env.get_template('chat.html')

template.blocks is dict where keys are blocks, how can I get all variables inside the blocks ?

Martin Valgur
  • 5,793
  • 1
  • 33
  • 45
Kracekumar
  • 19,457
  • 10
  • 47
  • 56

6 Answers6

80

Since no one has answered the question and I found the answer

from jinja2 import Environment, PackageLoader, meta
env = Environment(loader=PackageLoader('gummi', 'templates'))
template_source = env.loader.get_source(env, 'page_content.html')
parsed_content = env.parse(template_source)
meta.find_undeclared_variables(parsed_content)

This will yield list of undeclared variables since this is not executed at run time, it will yield list of all variables.

Note: This will yield html files which are included using include and extends.

Aaron Lelevier
  • 19,850
  • 11
  • 76
  • 111
Kracekumar
  • 19,457
  • 10
  • 47
  • 56
  • 13
    Unfortunately does not pick up properties of the vars (i.e. {{ something.nested }} provides set(['something']) – Bemis Mar 13 '14 at 17:37
  • 7
    I have template which contains variables like {{ properties.env }} , {{ proerties.sys }} etc. When I use above function I am getting "properties" but I am interested in getting one level deeper i.e. properties.env or properties.sys. Can someone suggest. – sudhanshu May 09 '16 at 07:11
  • 1
    This does not work as expected when extending templates - I seem to only get the variables defined in the template file I'm loading, but not from the ones that I am extending. – Hans Nov 09 '22 at 13:13
  • Not working fro me when extending and including – G M Nov 21 '22 at 13:23
  • You can use `meta.find_referenced_templates(parsed_content)` to find the extended or included templates and recurse over those to get a complete list – Violet Shreve May 08 '23 at 17:10
16

I had the same need and I've written a tool called jinja2schema. It provides a heuristic algorithm for inferring types from Jinja2 templates and can also be used for getting a list of all template variables, including nested ones.

Here is a short example of doing that:

>>> import jinja2
>>> import jinja2schema
>>>
>>> template = '''
... {{ x }}
... {% for y in ys %}
...     {{ y.nested_field_1 }}
...     {{ y.nested_field_2 }}
... {% endfor %}
... '''
>>> variables = jinja2schema.infer(template)
>>>
>>> variables
{'x': <scalar>,
 'ys': [{'nested_field_1': <scalar>, 'nested_field_2': <scalar>}]}
>>>
>>> variables.keys()
['x', 'ys']
>>> variables['ys'].item.keys()
['nested_field_2', 'nested_field_1']
aromanovich
  • 316
  • 2
  • 3
  • (Note: jinja2schema [uses `Environment.parse`](https://github.com/aromanovich/jinja2schema/blob/ca50530e83a928cfa836f257ad232f5c47be1659/jinja2schema/core.py#L23) to get an AST from the template and then use it.) – user202729 May 31 '19 at 08:40
7

For my pelican theme, i have created a tools for analyse all jinja variables in my templates files.

I share my code

This script generate a sample configuration from all variables exists in template files and get a variables from my official pelicanconf.py

The function that extract all variables from template file

def get_variables(filename):
    env = Environment(loader=FileSystemLoader('templates'))
    template_source = env.loader.get_source(env, filename)[0]
    parsed_content = env.parse(template_source)

The complete script

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# use:
# generate_pelicanconf-sample.py my_official_blog/pelicanconf.py

import sys
import imp
import os

from jinja2 import Environment, FileSystemLoader, meta


# Search all template files
def list_html_templates():
    dirList = os.listdir('templates')

    return dirList


# get all variable in template file
def get_variables(filename):
    env = Environment(loader=FileSystemLoader('templates'))
    template_source = env.loader.get_source(env, filename)[0]
    parsed_content = env.parse(template_source)

    return meta.find_undeclared_variables(parsed_content)


# Check if the pelicanconf.py is in param
if len(sys.argv) != 2:
    print("Please indicate the pelicanconf.py file")
    sys.exit()

# Get all vars from templates files
all_vars = set()
files = list_html_templates()
for fname in files:
    variables = get_variables(fname)
    for var in variables:
        if var.isupper():
            all_vars.add(var)

m = imp.load_source('pelicanconf', sys.argv[1])

# Show pelicanconf.py vars content
for var in all_vars:
    varname = 'm.%s' % var
    if var in m.__dict__:
        print ("%s = %s" % (var, repr(m.__dict__[var])))


    return meta.find_undeclared_variables(parsed_content)

The sample result of this program

LINKS = ((u'Home', u'/'), (u'archives', u'/archives.html'), (u'tags', u'/tags.html'), (u'A propos', u'http://bruno.adele.im'))
SITESUBTITLE = u'Une famille compl\xe8tement 633<'
DEFAULT_LANG = u'fr'
SITEURL = u'http://blog.jesuislibre.org'
AUTHOR = u'Bruno Adel\xe9'
SITENAME = u'Famille de geeks'
SOCIAL = ((u'adele', u'http://adele.im'), (u'feed', u'http://feeds.feedburner.com/FamilleDeGeek'), (u'twitter', u'http://twitter.com/jesuislibre.org'), (u'google+', u'https://plus.google.com/100723270029692582967'), (u'blog', u'http://blog.jesuislibre.org'), (u'facebook', u'http://www.facebook.com/bruno.adele'), (u'flickr', u'http://www.flickr.com/photos/b_adele'), (u'linkedin', u'http://fr.linkedin.com/in/brunoadele'))
FEED_DOMAIN = u'http://blog.jesuislibre.org'
FEED_ALL_ATOM = u'feed.atom'
DISQUS_SITENAME = u'blogdejesuislibreorg'
DEFAULT_PAGINATION = 10
GITHUB_BLOG_SITE = u'https://github.com/badele/blog.jesuislibre.org'

For more détail of this script see https://github.com/badele/pelican-theme-jesuislibre

bufh
  • 3,153
  • 31
  • 36
Bruno Adelé
  • 1,087
  • 13
  • 16
3

For me jinja2.meta.find_undeclared_variables(parsed_content) is not a good fit because it does not provide nested variables.

jinja2schema tool was kinda ok for simple scenarios but with all the loops and other jinja2 dark powers it was failing with errors.

I have played around with jinja2 data structures and was able to get all variables including nested ones. For my use case this was enough. Maybe this will also help for somebody else :)

Here is the code:

from jinja2 import Environment, FileSystemLoader, nodes


def get_variables(path, filename):
    template_variables = set()
    env = Environment(loader=FileSystemLoader(searchpath=path))
    template_source = env.loader.get_source(env, filename)[0]
    parsed_content = env.parse(template_source)
    if parsed_content.body and hasattr(parsed_content.body[0], 'nodes'):
        for variable in parsed_content.body[0].nodes:
            if type(variable) is nodes.Name or type(variable) is nodes.Getattr:
                parsed_variable = parse_jinja_variable(variable)
                if parsed_variable:
                    template_variables.add(parsed_variable)

    return template_variables


def parse_jinja_variable(variable, suffix=''):
    if type(variable) is nodes.Name:
        variable_key = join_keys(variable.name, suffix)
        return variable_key
    elif type(variable) is nodes.Getattr:
        return parse_jinja_variable(variable.node, join_keys(variable.attr, suffix))


def join_keys(parent_key, child_key):
    key = child_key if child_key else parent_key
    if parent_key and child_key:
        key = parent_key + '.' + key
    return key


if __name__ == "__main__":
    variable_keys = get_variables({replace_with_your_template directory}, {replace_with_your_template_file})
    print(*variable_keys, sep='\n')


Tomas Zuklys
  • 315
  • 1
  • 2
  • 10
  • Nice try, unfortunatly it does not work if the template uses `extends`and `block` elements – G M Nov 21 '22 at 12:49
2

Why not regex?

If find it a lot easier to use regex:

import re
with open('templates/templatename.html') as f:
    variables = re.findall("\{\{\s(.*?)\s\}\}", f.read())
G M
  • 20,759
  • 10
  • 81
  • 84
1

Based on @Kracekumar's answer, but for the simplest use-case of just extracting tokens from a template passed as a string argument with no loading semantics or filter overrides:

env = jinja2.Environment()
parsed_content = env.parse(template_source)
tokens = jinja2.meta.find_undeclared_variables(parsed_content)

tokens will be a set.

Dustin Oprea
  • 9,673
  • 13
  • 65
  • 105