# Copyright, 2018-2019, Deutsches Zentrum für Luft- und Raumfahrt e.V.
# Licensed under LGPLv3+, see LICENSE for details.
"""
Instantiations tie everything together. An
:class:`~ontocode.instantiation.Instantiation` aggregates a list of
:ref:`ontology locators<ontology-locators>`, a :ref:`template<templates>`, and
list, as well as optionally a single instance, of
:ref:`template inputs<template-inputs>`.
Calling one of their execution methods will cause it to load the ontologies
specified by the :ref:`ontology locators<ontology-locators>`, let the
:ref:`template inputs<template-inputs>` generate arguments for the
:ref:`template<templates>` and pass the results to its
:ref:`template<templates>`.
A :class:`~ontocode.instantiation.Instantiation` object can instantiate its
:ref:`template<templates>` multiple times for a single execution. This is
controlled by the second :ref:`template input<template-inputs>` argument to
:class:`~ontocode.instantiation.Instantiation`\\´s constructor:
``per_row_input``.
:ref:`Template inputs<template-inputs>` passed as arguments to
``per_row_input`` are expected to return a list of dictionaries, while all
:ref:`template inputs<template-inputs>` in lists passed as arguments to
``at_once_inputs`` are expected to return dictionaries. The number of elements
in the list generated by the ``per_row_input`` argument determines the number
of :ref:`template<templates>` instantiations.
When :func:`~ontocode.instantiation.Instantiation.execute` is called (directly
or via :func:`~ontocode.instantiation.Instantiation.execute_and_write_to_file`)
with arguments ``*args`` and ``**kwargs``, the
:ref:`template inputs<template-inputs>` passed to ``at_once_inputs`` are called
to generate a set of dictionaries and, if not ``None``, the argument passed to
``per_row_input`` is called to generate a list of dictionaries.
Now two cases have to be distinguished: ``per_row_input`` is ``None`` or
generated an empty list and ``per_row_input`` generated a non-empty list.
In the former case, the :ref:`template<templates>` is instantiated once.
``*arg`` is passed right through from
:func:`~ontocode.instantiation.Instantiation.execute`. The argument to
``**kwargs`` is a merge of ``**kwargs`` as passed to
:func:`~ontocode.instantiation.Instantiation.execute` with the dictionaries
generated by the argument to ``at_once_inputs``, where
:func:`~ontocode.instantiation.Instantiation.execute`\\´s ``**kwargs`` take
precedence over the generated dictionaries and the precedence order within
the dictionaries is determined by their order as part of the list passed to
``at_once_inputs``.
In the latter case, the :ref:`template<templates>` is instantiated once for
each dictionary generated by the argument to ``per_row_input``. The arguments
to the :ref:`template<templates>` are constructed similarly to the previous
case. The only difference is, that for each invocation a different dictionary
of those generated by the argument to ``per_row_input`` is merged with the
other dictionaries to produce the ``**kwargs`` argument. In the precedence
order, it is located between the ``**kwargs`` as passed to
:func:`~ontocode.instantiation.Instantiation.execute` and the dictionaries
generated by the argument to ``at_once_inputs``.
"""
import codecs
import errno
import os
import owlready2 as owl
__all__ = ['Instantiation', 'TemplateInputArgumentError',
'TemplateInputResultError']
def _merge_dicts(dicts):
result = {}
for dic in dicts:
result.update(dic)
return result
def _write_to_file(path, content):
abspath = os.path.abspath(path)
dirname = os.path.dirname(abspath)
try:
os.makedirs(dirname)
except OSError as error: # pragma: no cover
if errno.EEXIST != error.errno:
raise
with codecs.open(abspath, 'w', 'utf-8') as result_file:
result_file.write(content)
[docs]class Instantiation():
"""Instantiation of a template based on data queried from ontologies.
:param list ontology_locators: a list of
:ref:`ontology locators<ontology-locators>`
:param ontocode.template.Template template: a template
:param list at_once_inputs: a list of
:class:`~ontocode.template_input.TemplateInput`\\s
:param ontocode.template_input.TemplateInput per_row_input: a template
input
"""
def __init__(self, ontology_locators, template, at_once_inputs,
per_row_input=None):
self._ontology_locators = ontology_locators
self._at_once_inputs = at_once_inputs
self._per_row_input = per_row_input
self._template = template
def _execute(self, *args, **kwargs):
world = owl.World()
self._load_ontologies(world)
at_once_input = self._generate_at_once_input(world)
if self._per_row_input:
per_row_input = self._generate_per_row_input(world)
render_kwargs_list = [{**at_once_input, **row, **kwargs}
for row in per_row_input]
return [(args, render_kwargs,
self._template.render(*args, **render_kwargs))
for render_kwargs in render_kwargs_list]
render_kwargs = {**at_once_input, **kwargs}
return [(args, render_kwargs,
self._template.render(*args, **render_kwargs))]
[docs] def execute(self, *args, **kwargs):
"""Executes the instantiation and returns a list of the results.
The :ref:`instantiations` module documentation describes the
instantiation process."""
return [content for (_, __, content) in self._execute(*args, **kwargs)]
[docs] def execute_and_write_to_file(self, path, *args, **kwargs):
"""Executes the instantiation and writes result to file.
Raises an :class:`ontocode.instantiation.TemplateInputArgumentError`,
if a non-null value for ``per_row_input`` was passed into the
constructor of this :class:`~ontocode.instantiation.Instantiation`
instance.
The :ref:`instantiations` module documentation describes the
instantiation process.
:param str path: path to target file
"""
if self._per_row_input:
raise TemplateInputArgumentError()
content = self.execute(*args, **kwargs)[0]
_write_to_file(path, content)
[docs] def execute_and_write_to_files(self, path_function, *args, **kwargs):
"""Executes the instantiation and writes result to files.
The :ref:`instantiations` module documentation describes the
instantiation process.
:param generator path_function: a function that takes the same
arguments as the instances template and returns a path for the
corresponding template invocation
"""
for (args, kwargs, content) in self._execute(*args, **kwargs):
path = path_function(*args, **kwargs)
_write_to_file(path, content)
def _load_ontologies(self, world):
for locator in self._ontology_locators:
locator.load(world)
def _generate_at_once_input(self, world):
def generate_at_once_input(input_, world):
result = input_.generate(world)
if not isinstance(result, dict):
raise TemplateInputResultError('at_once_inputs argument \
element must return a dict type object')
return result
at_once_inputs = [generate_at_once_input(input_, world)
for input_ in self._at_once_inputs]
return _merge_dicts(at_once_inputs)
def _generate_per_row_input(self, world):
result = self._per_row_input.generate(world)
try:
iter(result)
except TypeError:
raise TemplateInputResultError('per_row_input argument must \
return an iterable')
return result