jinja2schema

Release v0.0.2.

Introduction

jinja2schema is a library for inferring types from Jinja2 templates.

One of the possible usages of jinja2schema is to create a JSON schema of a context expected by the template and then use it to render a form (using such JS libraries as Alpaca or JSON Editor) or to validate user input.

The library is in an early stage of development. Although the code is extensively tested, please be prepared for bugs and if you find some, let the author know by opening a ticket.

Examples

Let’s get started by inferring types from some expressions.

>>> from jinja2schema import infer
>>> infer('{{ x }}')
{'x': <scalar>}
>>> infer('{{ x.a.b }}')
{'x': {'a': {'b': <scalar>}}}
>>> infer('{{ x.a.b|first }}')
{'x': {'a': {'b': [<scalar>]}}}
>>> infer('{{ (x.a.b|first).name }}')
{'x': {'a': {'b': [{'name': <scalar>}]}}

jinja2schema supports all the flow control structures...

>>> infer('''
... {% for row in items|batch(3, '&nbsp;') %}
...     {% for column in row %}
...         {% if column.has_title %}
...             {{ column.title }}
...         {% else %}
...             {{ column.desc|truncate(10) }}
...         {% endif %}
...     {% endfor %}
... {% endfor %}
... ''')
{'items': [{'desc': <scalar>, 'has_title': <unknown>, 'title': <scalar>}]}

...and doesn’t get confused by scopes.

>>> s = infer('''
... {% for x in xs %}
...     {% for x in ys %}
...         {{ x.a }}
...     {% endfor %}
...     {{ x.b }}
... {% endfor %}
... ''')
>>> s
{'xs': [{'b': <scalar>}], 'ys': [{'a': <scalar>}]}

As it was said before, any structure can be converted to JSON schema.

>>> schema = infer('{% for x in xs %}{{ x }}{% endfor %}').to_json_schema()
>>> print json.dumps(schema, indent=2)
{
  "type": "object",
  "properties": {
    "xs": {
      "type": "array",
      "title": "xs",
      "items": {
        "title": "x",
        "anyOf": [
          {"type": "string"},
          {"type": "number"},
          {"type": "boolean"},
          {"type": "null"}
        ]
      }
    }
  },
  "required": ["xs"]
}

To get a more detailed representation of a structure, one could use jinja2schema.util.debug_repr().

>>> from jinja2schema.util import debug_repr
>>> print debug_repr(s)
Dictionary(label=None, required=True, constant=False, linenos=[], {
    xs: List(label=xs, required=True, constant=False, linenos=[2],
            Dictionary(label=x, required=True, constant=False, linenos=[6], {
                b: Scalar(label=b, required=True, constant=False, linenos=[6])
            })
        )
    ys: List(label=ys, required=True, constant=False, linenos=[3],
            Dictionary(label=x, required=True, constant=False, linenos=[4], {
                a: Scalar(label=a, required=True, constant=False, linenos=[4])
            })
        )
})

How It Works

jinja2schema logic based on the following common sense assumptions.

Note

This list is not exhausting and is a subject to change. Some of these “axioms” probably will be customizable at some point in the future.

  • If x is printed ({{ x }}), x is a scalar: a string, a number or a boolean;

  • If x is used as an iterable in for loop ({% for item in x %}), used with a filter that accepts lists (x|first), or being indexed with a number (x[0]), x is a list;

  • If x is used with a dot (x.field) or being indexed with a string (x['field']), x is a dictionary.

  • A variable can only be used in the one role. So that a list or dictionary can not be printed, a string can not be indexed:

    >>> infer('''
    ... {{ x }}
    ... {{ x.name }}
    ... ''')
    jinja2schema.exceptions.MergeException: variable "x" (lines: 2, used as scalar)
    conflicts with variable "x" (lines: 3, used as dictionary)
    
  • Lists are assumed to be homogeneous, meaning all elements of the same list are assumed to have the same structure:

    >>> infer('''
    ... {% set xs = [
    ...    1,
    ...    {}
    ... ] %}
    ... ''')
    jinja2schema.exceptions.MergeException: unnamed variable (lines: 3, used as scalar)
    conflicts with unnamed variable (lines: 4, used as dictionary)
    

Installation

$ pip install jinja2schema

API

To infer types from a template, simply call jinja2schema.infer().

jinja2schema.infer(template, config=<jinja2schema.config.Config object at 0x7fd2fbf288d0>)

Returns a model.Dictionary which reflects a structure of the context required by template.

Parameters:
  • template (string) – a template
  • config (config.Config) – a config
Return type:

model.Dictionary

Raises:

exceptions.MergeException, exceptions.InvalidExpression, exceptions.UnexpectedExpression

It will return a model.Variable instance, which can be converted to JSON schema using it’s model.Variable.to_json_schema() method.

infer() logic can be tuned by specifying a custom jinja2schema.config.Config.

If you need more than that, please take a look at Internals.

Contributing

The project is hosted on GitHub. Please feel free to send a pull request or open an issue.

Running the Tests

$ pip install -r ./requirements-dev.txt
$ ./test.sh

Table Of Contents

Next topic

jinja2schema.core

This Page