Source code for capsul.process.xml

# -*- coding: utf-8 -*-
'''
Read and write a Process as an XML file.

Classes
=======
:class:`XMLProcess`
-------------------

Functions
=========
:func:`string_to_value`
-----------------------
:func:`trait_from_xml`
----------------------
:func:`create_xml_process`
--------------------------

Decorator
=========
:func:`xml_process`
-------------------
'''

from __future__ import absolute_import

import sys
import xml.etree.cElementTree as ET
from ast import literal_eval

from capsul.process.process import Process

from soma.utils.functiontools import getArgumentsSpecification

from traits.api import (Int, Float, String, Unicode, File, Directory, Enum,
                        Bool, List, Any, Undefined)
from six.moves import range

if sys.version_info[0] >= 3:
    xrange = range

_known_values = {
    'Undefined': Undefined,
    'None': Undefined
}
    
[docs] def string_to_value(string): """ Converts a string into a Python value without executing code. """ value = _known_values.get(string) if value is None: try: value = literal_eval(string) except ValueError as e: raise ValueError('%s: %s' % (str(e), repr(string))) return value
[docs] class XMLProcess(Process): """ Base class of all generated classes for processes defined as a Python function decorated with an XML string. """ def _run_process(self): """ Execute the XMLProcess. Runs the function and check its result to set the output attributes values accordingly. """ # Build function parameters with identified input attributes kwargs = dict((i, getattr(self, i)) for i in self._function_inputs) # Calls the function and get the result result = self._function(**kwargs) if self._function_return: # Process was declared to return a single value setattr(self, self._function_return, result) elif self._function_outputs: # Process was declared to return several output values if isinstance(result, (list, tuple)): # Return value is a list, maps the output parameter names in # their declaration order if len(result) > len(self._function_outputs): raise ValueError('Too many values (%d instead of %d) ' 'returned by process %s' % (len(result), len(self._function_outputs), self.id)) for i in range(len(self._function_outputs)): setattr(self, self._function_outputs[i], result[i]) elif isinstance(result, dict): # Return value is a dict, set the output parameter values. for i in self._function_outputs: setattr(self, i, result[i]) else: raise ValueError('Value returned by process %s must be a list,' ' tuple or dict' % self.id)
string_to_trait = { 'int': (Int, {}), 'float': (Float, {}), 'string': (String, {}), 'unicode': (Unicode, {}), 'bool': (Bool, {}), 'file': (File, {}), 'directory': (Directory, {}), 'any': (Any, {}), 'list_int': (List, {'trait': Int}), 'list_float': (List, {'trait': Float}), 'list_string': (List, {'trait': String}), 'list_unicode': (List, {'trait': Unicode}), 'list_bool': (List, {'trait': Bool}), 'list_file': (List, {'trait': File}), 'list_directory': (List, {'trait': Directory}), 'list_any': (List, {'trait': Any}), }
[docs] def trait_from_xml(element, order=None): """ Creates a trait from an XML element type (<input>, <output> or <return>). """ type = element.get('type') trait_args = () t = string_to_trait.get(type) if t: trait_class, trait_kwargs = t elif type == 'enum': trait_class = Enum trait_args = string_to_value(element.get('values')) trait_kwargs = {} else: raise ValueError('Invalid parameter type: %s' % type) trait_kwargs = trait_kwargs.copy() trait_kwargs['output'] = (element.tag != 'input') doc = element.get('doc') if doc: trait_kwargs['desc'] = doc optional = element.get('optional') if optional is not None: trait_kwargs['optional'] = bool(optional == 'true') allowed_ext = element.get('allowed_extensions') if allowed_ext is not None: trait_kwargs['allowed_extensions'] = eval(allowed_ext) if type in ('file', 'list_file'): input_filename = element.get('input_filename') if input_filename is not None: trait_kwargs['input_filename'] = input_filename else: trait_kwargs['input_filename'] = False if order is not None: trait_kwargs['order'] = order return trait_class(*trait_args, **trait_kwargs)
[docs] def create_xml_process(module, name, function, xml): """ Create a new process class given a Python function and a string containing the corresponding Capsul XML 2.0 definition. Parameters ---------- module: str (mandatory) name of the module for the created Process class (the Python module is not modified). name: str (mandatory) name of the new process class function: callable (mandatory) function to call to execute the process. xml: str (mandatory) XML definition of the function. Returns ------- results: XMLProcess subclass created process class. """ xml_process = ET.fromstring(xml) class_kwargs = { "__module__": module, } if function.__doc__: class_kwargs['__doc__'] = function.__doc__ args, varargs, varkw, defaults = getArgumentsSpecification(function) if defaults: default_values = dict((args[-1-i], defaults[-1-i]) for i in range(len(defaults))) else: default_values = {} class_kwargs['default_values'] = default_values version = xml_process.get('capsul_xml') if version and version != '2.0': raise ValueError('Only Capsul XML 2.0 is supported, not %s' % version) role = xml_process.get('role') if role: class_kwargs['role'] = role function_inputs = [] function_outputs = [] function_return = None trait_count = 0 # used to order traits in class for child in xml_process: if child.tag in ('input', 'output'): n = child.get('name') trait = trait_from_xml(child, trait_count) trait_count += 1 class_kwargs[n] = trait if trait.output and trait.input_filename is False: function_outputs.append(n) else: function_inputs.append(n) elif child.tag == 'return': n = child.get('name') if n: trait = trait_from_xml(child, trait_count) trait_count += 1 class_kwargs[n] = trait function_return = n else: for parameter in child: n = parameter.get('name') trait = trait_from_xml(parameter, trait_count) trait_count += 1 class_kwargs[n] = trait function_outputs.append(n) else: raise ValueError('Invalid tag in <process>: %s' % child.tag) class_kwargs['_function'] = staticmethod(function) class_kwargs['_function_inputs'] = function_inputs class_kwargs['_function_outputs'] = function_outputs class_kwargs['_function_return'] = function_return # Get the process instance associated to the function process_class = type(str(name), (XMLProcess, ), class_kwargs) return process_class
[docs] def xml_process(xml): """ Decorator used to associate a Python function to its Process XML representation. """ def set_capsul_xml(function): function.capsul_xml = xml return function return set_capsul_xml