# coding: utf-8
"""
jinja2schema.core
~~~~~~~~~~~~~~~~~
"""
import jinja2
from .config import Config
from .model import Dictionary, List, Tuple, Scalar, Number, Unknown, Boolean, String
from .visitors import visit
from . import _compat
[docs]def parse(template, jinja2_env=None):
"""Parses Jinja2 template and returns it's AST.
:type template: basestring
:type jinja2_env: :class:`jinja2.Environment`
:rtype: :class:`jinja2.nodes.Template`
"""
if jinja2_env is None:
jinja2_env = jinja2.Environment()
return jinja2_env.parse(template)
def _ignore_constants(var):
if isinstance(var, Dictionary):
for k, v in list(_compat.iteritems(var)):
if v.constant and not v.may_be_defined:
del var[k]
else:
_ignore_constants(v)
return var
[docs]def infer_from_ast(ast, ignore_constants=True, config=Config()):
"""Returns a :class:`.model.Dictionary` which reflects a structure of variables used
within ``ast``.
:param ast: AST
:type ast: :class:`jinja2.nodes.Node`
:param ignore_constants: excludes constant variables from a resulting structure
:param config: a config
:type config: :class:`.config.Config`
:rtype: :class:`.model.Dictionary`
:raises: :class:`.exceptions.MergeException`, :class:`.exceptions.InvalidExpression`,
:class:`.exceptions.UnexpectedExpression`
"""
rv = visit(ast, {}, config)
if ignore_constants:
rv = _ignore_constants(rv)
return rv
[docs]def infer(template, config=Config()):
"""Returns a :class:`.model.Dictionary` which reflects a structure of the context required by ``template``.
:param template: a template
:type template: string
:param config: a config
:type config: :class:`.config.Config`
:rtype: :class:`.model.Dictionary`
:raises: :class:`.exceptions.MergeException`, :class:`.exceptions.InvalidExpression`,
:class:`.exceptions.UnexpectedExpression`
"""
return infer_from_ast(parse(template), config=config, ignore_constants=True)
[docs]class JSONSchemaDraft4Encoder(object):
"""Extensible JSON schema encoder for :class:`.model.Variable`."""
[docs] def encode_common_attrs(self, var):
"""Returns a subset of JSON schema of variable `var` that describes
attributes that are common for all the variable types, such as label.
"""
rv = {}
if var.label:
rv['title'] = var.label
if var.value and var.used_with_default:
rv['default'] = var.value
if var.order_nr:
rv['order_number'] = var.order_nr
return rv
[docs] def encode(self, var):
"""Returns a JSON schema of variable `var`.
:type var: :class:`.model.Variable`
:rtype: :class:`dict`
"""
rv = self.encode_common_attrs(var)
if isinstance(var, Dictionary):
rv.update({
'type': 'object',
'properties': dict((k, self.encode(v)) for k, v in var.iteritems()),
'required': [k for k, v in var.iteritems() if v.required],
})
elif isinstance(var, List):
rv.update({
'type': 'array',
'items': self.encode(var.item),
})
elif isinstance(var, Tuple):
rv.update({
'type': 'array',
'items': [self.encode(item) for item in var.items],
})
elif isinstance(var, Unknown):
rv['anyOf'] = [
{'type': 'object'},
{'type': 'array'},
{'type': 'string'},
{'type': 'number'},
{'type': 'boolean'},
{'type': 'null'},
]
elif isinstance(var, String):
rv['type'] = 'string'
elif isinstance(var, Number):
rv['type'] = 'number'
elif isinstance(var, Boolean):
rv['type'] = 'boolean'
elif isinstance(var, Scalar):
rv['anyOf'] = [
{'type': 'boolean'},
{'type': 'null'},
{'type': 'number'},
{'type': 'string'},
]
return rv
[docs]class StringJSONSchemaDraft4Encoder(JSONSchemaDraft4Encoder):
"""Encodes :class:`.model.Unknown` and :class:`.model.Scalar` (but not it's subclasses --
:class:`.model.String`, :class:`.model.Number` or :class:`.model.Boolean`) variables as strings.
Useful for rendering forms using resulting JSON schema, as most of form-rendering
tools do not support "anyOf" validator.
"""
def encode(self, var):
if isinstance(var, Unknown) or type(var) is Scalar:
rv = self.encode_common_attrs(var)
rv['type'] = 'string'
else:
rv = super(StringJSONSchemaDraft4Encoder, self).encode(var)
return rv
[docs]def to_json_schema(var, jsonschema_encoder=JSONSchemaDraft4Encoder):
"""Returns JSON schema that describes ``var``.
:param var: a variable
:type var: :class:`.model.Variable`
:param jsonschema_encoder: JSON schema encoder
:type jsonschema_encoder: subclass of :class:`JSONSchemaEncoder`
:return: :class:`dict`
"""
return jsonschema_encoder().encode(var)