コード例 #1
0
    def workflow_from_graph(graph, temp_map={}, shared_map={},
                            transfers=[{}, {}], shared_paths={},
                            disabled_nodes=set(), forbidden_temp=set(),
                            jobs_priority=0, steps={}, current_step='',
                            study_config={}):
        """ Convert a CAPSUL graph to a soma-workflow workflow

        Parameters
        ----------
        graph: Graph (mandatory)
            a CAPSUL graph
        temp_map: dict (optional)
            temporary files to replace by soma_workflow TemporaryPath objects
        shared_map: dict (optional)
            shared translated paths maps (global to pipeline).
            This dict is updated when needed during the process.
        transfers: list of 2 dicts (optional)
            File transfers dicts (input / ouput), indexed by process, then by
            file path.
        shared_paths: dict (optional)
            holds information about shared resource paths from soma-worflow
            section in study config.
            If not specified, no translation will be used.
        jobs_priority: int (optional, default: 0)
            set this priority on soma-workflow jobs.
        steps: dict (optional)
            node name -> step name dict
        current_step: str (optional)
            the parent node step name
        study_config: StydyConfig instance (optional)
            used only for iterative nodes, to be passed to create sub-workflows

        Returns
        -------
        workflow: tuple (jobs, dependencies, groups, root_jobs)
            the corresponding soma-workflow workflow definition (to be passed
            to Workflow constructor)
        """
        jobs = {}
        groups = {}
        root_jobs = {}
        dependencies = set()
        group_nodes = {}

        ordered_nodes = graph.topological_sort()
        proc_keys = dict([(node[1] if isinstance(node[1], Graph)
                              else node[1][0].process, i)
                           for i, node in enumerate(ordered_nodes)])

        # Go through all graph nodes
        for node_name, node in six.iteritems(graph._nodes):
            # If the the node meta is a Graph store it
            if isinstance(node.meta, Graph):
                group_nodes[node_name] = node
            # Otherwise convert all the processes in meta as jobs
            else:
                sub_jobs = {}
                for pipeline_node in node.meta:
                    process = pipeline_node.process
                    if pipeline_node in disabled_nodes:
                        continue
                    step_name = current_step or steps.get(pipeline_node.name)
                    if isinstance(process, ProcessIteration):
                        # iterative node
                        group_nodes.setdefault(
                            node_name, []).append(pipeline_node)
                    elif (not isinstance(process, Pipeline) and
                            isinstance(process, Process)):
                        job = build_job(process, temp_map, shared_map,
                                        transfers, shared_paths,
                                        forbidden_temp=forbidden_temp,
                                        name=pipeline_node.name,
                                        priority=jobs_priority,
                                        step_name=step_name)
                        sub_jobs[process] = job
                        root_jobs[process] = [job]
                        #node.job = job
                jobs.update(sub_jobs)

        # Recurrence on graph node
        for node_name, node in six.iteritems(group_nodes):
            if isinstance(node, list):
                # iterative nodes
                for i, it_node in enumerate(node):
                    process = it_node.process
                    sub_workflows = build_iteration(
                        process, step_name, temp_map,
                        shared_map, transfers, shared_paths, disabled_nodes,
                        {}, steps, study_config={})
                    (sub_jobs, sub_deps, sub_groups, sub_root_jobs) = \
                        sub_workflows
                    group = build_group(node_name, six_values(sub_root_jobs))
                    groups.setdefault(process, []).append(group)
                    root_jobs.setdefault(process, []).append(group)
                    groups.update(sub_groups)
                    jobs.update(sub_jobs)
                    dependencies.update(sub_deps)
            else:
                # sub-pipeline
                wf_graph = node.meta
                step_name = current_step or steps.get(node_name, '')
                (sub_jobs, sub_deps, sub_groups, sub_root_jobs) \
                    = workflow_from_graph(
                        wf_graph, temp_map, shared_map, transfers,
                        shared_paths, disabled_nodes,
                        jobs_priority=jobs_priority,
                        steps=steps, current_step=step_name)
                group = build_group(node_name, six_values(sub_root_jobs))
                groups[node.meta] = group
                root_jobs[node.meta] = [group]
                jobs.update(sub_jobs)
                groups.update(sub_groups)
                dependencies.update(sub_deps)

        # Add dependencies between a source job and destination jobs
        for node_name, node in six.iteritems(graph._nodes):
            # Source job
            if isinstance(node.meta, list):
                if isinstance(node.meta[0].process, ProcessIteration):
                    sjobs = groups.get(node.meta[0].process)
                    if not sjobs:
                        continue # disabled
                elif node.meta[0].process in jobs:
                    sjobs = [jobs[node.meta[0].process]]
                else:
                    continue # disabled node
            else:
                sjobs = [groups[node.meta]]
            # Destination jobs
            for dnode in node.links_to:
                if isinstance(dnode.meta, list):
                    if isinstance(dnode.meta[0].process, ProcessIteration):
                        djobs = groups.get(dnode.meta[0].process)
                        if not djobs:
                            continue # disabled
                    elif dnode.meta[0].process in jobs:
                        djobs = [jobs[dnode.meta[0].process]]
                    else:
                        continue # disabled node
                else:
                    djobs = groups[dnode.meta]
                    if not isinstance(djobs, list):
                        djobs = [djobs]
                for djob in djobs:
                    dependencies.update([(sjob, djob) for sjob in sjobs])

        # sort root jobs/groups
        root_jobs_list = []
        for p, js in six.iteritems(root_jobs):
            root_jobs_list.extend([(proc_keys[p], p, j) for j in js])
        root_jobs_list.sort()
        root_jobs = OrderedDict([x[1:] for x in root_jobs_list])
        return jobs, dependencies, groups, root_jobs
コード例 #2
0
def create_xml_pipeline(module, name, xml_file):
    """
    Create a pipeline class given its Capsul XML 2.0 representation.
    
    Parameters
    ----------
    module: str (mandatory)
        name of the module for the created Pipeline class (the Python module is
        not modified).
    name: str (mandatory)
        name of the new pipeline class
    xml_file: str (mandatory)
        name of file containing the XML description or XML string.
    
    """
    if os.path.exists(xml_file):
        xml_pipeline = ET.parse(xml_file).getroot()
    else:
        xml_pipeline = ET.fromstring(xml_file)
    version = xml_pipeline.get('capsul_xml')
    if version and version != '2.0':
        raise ValueError('Only Capsul XML 2.0 is supported, not %s' % version)

    class_name = xml_pipeline.get('name')
    if class_name:
        if name is None:
            name = class_name
        elif name != class_name:
            raise KeyError('pipeline name (%s) and requested object name '
                           '(%s) differ.' % (class_name, name))
    elif name is None:
        name = os.path.basename(xml_file).rsplit('.', 1)[0]

    builder = PipelineConstructor(module, name)
    exported_parameters = set()

    for child in xml_pipeline:
        if child.tag == 'doc':
            builder.set_documentation(child.text.strip())
        elif child.tag == 'process':
            process_name = child.get('name')
            module = child.get('module')
            args = (process_name, module)
            kwargs = {}
            nipype_usedefault = []
            iterate = []
            iteration = child.get('iteration')
            if iteration:
                iterate = [x.strip() for x in iteration.split(',')]
            for process_child in child:
                if process_child.tag == 'set':
                    name = process_child.get('name')
                    value = process_child.get('value')
                    value = string_to_value(value)
                    if value is not None:
                        kwargs[name] = value
                    kwargs.setdefault('make_optional', []).append(name)
                elif process_child.tag == 'nipype':
                    name = process_child.get('name')
                    usedefault = process_child.get('usedefault')
                    if usedefault == 'true':
                        nipype_usedefault.append(name)
                    copyfile = process_child.get('copyfile')
                    if copyfile == 'true':
                        kwargs.setdefault('inputs_to_copy', []).append(name)
                    elif copyfile == 'discard':
                        kwargs.setdefault('inputs_to_copy', []).append(name)
                        kwargs.setdefault('inputs_to_clean', []).append(name)
                else:
                    raise ValueError('Invalid tag in <process>: %s' %
                                     process_child.tag)
            if iterate:
                kwargs['iterative_plugs'] = iterate
                builder.add_iterative_process(*args, **kwargs)
            else:
                builder.add_process(*args, **kwargs)
            for name in nipype_usedefault:
                builder.call_process_method(process_name, 'set_usedefault',
                                            name, True)
            enabled = child.get('enabled')
            if enabled == 'false':
                builder.set_node_enabled(process_name, False)
        elif child.tag == 'switch':
            switch_name = child.get('name')
            value = child.get('switch_value')
            kwargs = {'export_switch': False}
            if value:
                kwargs['switch_value'] = value
            inputs = []
            outputs = []
            for process_child in child:
                if process_child.tag == 'input':
                    name = process_child.get('name')
                    inputs.append(name)
                elif process_child.tag == 'output':
                    name = process_child.get('name')
                    outputs.append(name)
                    optional = process_child.get('optional')
                    if optional == 'true':
                        kwargs.setdefault('make_optional', []).append(name)
            builder.add_switch(switch_name, inputs, outputs, **kwargs)
            enabled = child.get('enabled')
            if enabled == 'false':
                builder.set_node_enabled(switch_name, False)
        elif child.tag == 'link':
            source = child.get('source')
            dest = child.get('dest')
            weak_link = child.get('weak_link')
            if weak_link == 'true':
                weak_link = True
            else:
                weak_link = False
            if '.' in source:
                if '.' in dest:
                    builder.add_link('%s->%s' % (source, dest),
                                     weak_link=weak_link)
                elif dest in exported_parameters:
                    builder.add_link('%s->%s' % (source, dest),
                                     weak_link=weak_link)
                else:
                    node, plug = source.rsplit('.', 1)
                    builder.export_parameter(node,
                                             plug,
                                             dest,
                                             weak_link=weak_link)
                    exported_parameters.add(dest)
            elif source in exported_parameters:
                builder.add_link('%s->%s' % (source, dest))
            else:
                node, plug = dest.rsplit('.', 1)
                builder.export_parameter(node,
                                         plug,
                                         source,
                                         weak_link=weak_link)
                exported_parameters.add(source)
        elif child.tag == 'processes_selection':
            selection_parameter = child.get('name')
            selection_groups = OrderedDict()
            for select_child in child:
                if select_child.tag == 'processes_group':
                    group_name = select_child.get('name')
                    group = selection_groups[group_name] = []
                    for group_child in select_child:
                        if group_child.tag == 'process':
                            group.append(group_child.get('name'))
                        else:
                            raise ValueError('Invalid tag in <processes_group>'
                                             '<process>: %s' % group_child.tag)
                else:
                    raise ValueError(
                        'Invalid tag in <processes_selection>: %s' %
                        select_child.tag)
            builder.add_processes_selection(selection_parameter,
                                            selection_groups)
        elif child.tag == 'pipeline_steps':
            for step_child in child:
                step_name = step_child.get('name')
                enabled = step_child.get('enabled')
                if enabled == 'false':
                    enabled = False
                else:
                    enabled = True
                nodes = []
                for step_node in step_child:
                    nodes.append(step_node.get('name'))
                builder.add_pipeline_step(step_name, nodes, enabled)
        elif child.tag == 'gui':
            for gui_child in child:
                if gui_child.tag == 'position':
                    name = gui_child.get('name')
                    x = float(gui_child.get('x'))
                    y = float(gui_child.get('y'))
                    builder.set_node_position(name, x, y)
                elif gui_child.tag == 'zoom':
                    builder.set_scene_scale_factor(
                        float(gui_child.get('level')))
                else:
                    raise ValueError('Invalid tag in <gui>: %s' %
                                     gui_child.tag)
        else:
            raise ValueError('Invalid tag in <pipeline>: %s' % child.tag)
    return builder.pipeline
コード例 #3
0
ファイル: attributes_schema.py プロジェクト: M40V/capsul
class ProcessAttributes(Controller):
    '''
    This is the base class for managing attributes for a process.
    '''
    def __init__(self, process, schema_dict):
        super(ProcessAttributes, self).__init__()
        self._process = process
        self._schema_dict = schema_dict
        self.editable_attributes = OrderedDict()
        self.parameter_attributes = {}

    def __getinitargs__(self):
        # needed for self.copy()
        return (self._process, self._schema_dict)

    def set_parameter_attributes(self, parameter, schema, editable_attributes,
                                 fixed_attibute_values):
        if parameter in self.parameter_attributes:
            raise KeyError('Attributes already set for parameter %s' %
                           parameter)
        if isinstance(editable_attributes, six.string_types) or isinstance(
                editable_attributes, EditableAttributes):
            editable_attributes = [editable_attributes]
        parameter_editable_attributes = []
        for ea in editable_attributes:
            add_editable_attributes = False
            if isinstance(ea, six.string_types):
                key = ea
                ea = self.editable_attributes.get(key)
                if ea is None:
                    ea = getattr(self._schema_dict[schema], key)()
                    self.editable_attributes[key] = ea
                    add_editable_attributes = True
            elif isinstance(ea, EditableAttributes):
                key = ea
                if key not in self.editable_attributes:
                    self.editable_attributes[key] = ea
                    add_editable_attributes = True
            else:
                raise TypeError(
                    'Invalid value for editable attributes: {0}'.format(ea))
            parameter_editable_attributes.append(ea)
            if add_editable_attributes:
                for name, trait in six.iteritems(ea.user_traits()):
                    self.add_trait(name, trait)
                    f = SomaPartial(set_attribute, ea)
                    self.on_trait_change(f, name)
        self.parameter_attributes[parameter] = (parameter_editable_attributes,
                                                fixed_attibute_values)

    def get_parameters_attributes(self):
        pa = {}
        for parameter, trait in six.iteritems(self._process.user_traits()):
            if trait.output:
                if hasattr(self._process, 'id'):
                    process_name = self._process.id
                else:
                    process_name = self._process.name
                attributes = {
                    'generated_by_process': process_name,
                    'generated_by_parameter': parameter
                }
            else:
                attributes = {}
            editable_fixed = self.parameter_attributes.get(parameter, ([], {}))
            editable_attributes, fixed_attibute_values = editable_fixed
            for ea in editable_attributes:
                for attribute in ea.user_traits():
                    value = getattr(ea, attribute)
                    attributes[attribute] = value
            attributes.update(fixed_attibute_values)
            if attributes:
                pa[parameter] = attributes
        return pa
コード例 #4
0
ファイル: attributes_schema.py プロジェクト: M40V/capsul
 def __init__(self, process, schema_dict):
     super(ProcessAttributes, self).__init__()
     self._process = process
     self._schema_dict = schema_dict
     self.editable_attributes = OrderedDict()
     self.parameter_attributes = {}
コード例 #5
0
ファイル: controller_widget.py プロジェクト: M40V/soma-base
    def __init__(self,
                 controller,
                 parent=None,
                 name=None,
                 live=False,
                 hide_labels=False,
                 select_controls=None,
                 editable_labels=False):
        """ Method to initilaize the ControllerWidget class.

        Parameters
        ----------
        controller: derived Controller instance (mandatory)
            a class derived from the Controller class we want to parametrize
            with a widget.
        parent: QtGui.QWidget (optional, default None)
            the controller widget parent widget.
        name: (optional, default None)
            the name of this controller widget
        live: bool (optional, default False)
            if True, synchronize the edited values in the widget with the
            controller values on the fly,
            otherwise, wait the synchronization instruction to update the
            controller values.
        hide_labels: bool (optional, default False)
            if True, don't show the labels associated with the controls
        select_controls: str (optional, default None)
            parameter to select specific conrtoller traits. Authorized options
            are 'inputs' or 'outputs'.
        editable_labels: bool (optional, default False)
            if True, labels (trait keys) may be edited by the user, their
            modification will trigger a signal.
        """
        # Inheritance
        super(ControllerWidget, self).__init__(parent)

        QtCore.QResource.registerResource(
            os.path.join(os.path.dirname(os.path.dirname(__file__)),
                         'resources', 'widgets_icons.rcc'))

        # Class parameters
        self.controller = controller
        self.live = live
        self.hide_labels = hide_labels
        self.select_controls = select_controls
        # Parameter to store the connection status between the
        # controller widget and the controller
        self.connected = False
        # Parameter to store all the controller widget controls:
        # the keys correspond to the control name (a control name is
        # associated to a controller trait with the same name), the
        # dictionary elements are 4-uplets of the form (trait, control_class,
        # control_instance, control_label).
        self._controls = {}
        self._keys_connections = {}
        self.editable_labels = editable_labels

        # If possilbe, set the widget name
        if name:
            self.setObjectName(name)

        # Create the layout of the controller widget
        # We will add all the controls to this layout
        self._grid_layout = QtGui.QGridLayout()
        self._grid_layout.setAlignment(QtCore.Qt.AlignTop)
        self._grid_layout.setSpacing(3)
        self._grid_layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(self._grid_layout)

        self._groups = OrderedDict()

        # Create all the layout controls associated with the controller values
        # we want to tune (ie the user traits)
        self._create_controls()
        self.connect_keys()

        # Start the event loop that check for wrong edited fields (usefull
        # when we work off line, otherwise the traits make the job but it is
        # still user friendly).
        self._check()

        # Set the synchrinization between this object and the input controller:
        # 1) synchronize the edited values in the widget with the controller
        # values on the fly
        if self.live:
            self.connect()

        # 2) initialize the controller widget with the controller values and
        # wait synchronization instructions to update the controller values.
        else:
            self.update_controller_widget()
コード例 #6
0
ファイル: controller_widget.py プロジェクト: M40V/soma-base
class ControllerWidget(QtGui.QWidget):
    """ Class that create a widget to set the controller parameters.
    """

    # Parameter to store the mapping between the string trait descriptions and
    # the associated control classes
    _defined_controls = {}

    def __init__(self,
                 controller,
                 parent=None,
                 name=None,
                 live=False,
                 hide_labels=False,
                 select_controls=None,
                 editable_labels=False):
        """ Method to initilaize the ControllerWidget class.

        Parameters
        ----------
        controller: derived Controller instance (mandatory)
            a class derived from the Controller class we want to parametrize
            with a widget.
        parent: QtGui.QWidget (optional, default None)
            the controller widget parent widget.
        name: (optional, default None)
            the name of this controller widget
        live: bool (optional, default False)
            if True, synchronize the edited values in the widget with the
            controller values on the fly,
            otherwise, wait the synchronization instruction to update the
            controller values.
        hide_labels: bool (optional, default False)
            if True, don't show the labels associated with the controls
        select_controls: str (optional, default None)
            parameter to select specific conrtoller traits. Authorized options
            are 'inputs' or 'outputs'.
        editable_labels: bool (optional, default False)
            if True, labels (trait keys) may be edited by the user, their
            modification will trigger a signal.
        """
        # Inheritance
        super(ControllerWidget, self).__init__(parent)

        QtCore.QResource.registerResource(
            os.path.join(os.path.dirname(os.path.dirname(__file__)),
                         'resources', 'widgets_icons.rcc'))

        # Class parameters
        self.controller = controller
        self.live = live
        self.hide_labels = hide_labels
        self.select_controls = select_controls
        # Parameter to store the connection status between the
        # controller widget and the controller
        self.connected = False
        # Parameter to store all the controller widget controls:
        # the keys correspond to the control name (a control name is
        # associated to a controller trait with the same name), the
        # dictionary elements are 4-uplets of the form (trait, control_class,
        # control_instance, control_label).
        self._controls = {}
        self._keys_connections = {}
        self.editable_labels = editable_labels

        # If possilbe, set the widget name
        if name:
            self.setObjectName(name)

        # Create the layout of the controller widget
        # We will add all the controls to this layout
        self._grid_layout = QtGui.QGridLayout()
        self._grid_layout.setAlignment(QtCore.Qt.AlignTop)
        self._grid_layout.setSpacing(3)
        self._grid_layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(self._grid_layout)

        self._groups = OrderedDict()

        # Create all the layout controls associated with the controller values
        # we want to tune (ie the user traits)
        self._create_controls()
        self.connect_keys()

        # Start the event loop that check for wrong edited fields (usefull
        # when we work off line, otherwise the traits make the job but it is
        # still user friendly).
        self._check()

        # Set the synchrinization between this object and the input controller:
        # 1) synchronize the edited values in the widget with the controller
        # values on the fly
        if self.live:
            self.connect()

        # 2) initialize the controller widget with the controller values and
        # wait synchronization instructions to update the controller values.
        else:
            self.update_controller_widget()

    #
    # Public members
    #

    def is_valid(self):
        """ Check that all edited fields are correct.

        Returns
        -------
        valid: bool
            True if all the controller widget controls are correctly filled,
            False otherwise
        """
        # Initilaized the output
        valid = True

        # Go through all the controller widget controls
        for control_name, control_groups in six.iteritems(self._controls):
            for group_name, control in six.iteritems(control_groups):

                # Unpack the control item
                trait, control_class, control_instance, control_label = control

                # Call the current control specific check method
                valid = control_class.is_valid(control_instance)

                # Stop checking if a wrong control has been found
                if not valid:
                    break

        return valid

    def update_controller(self):
        """ Update the controller.

        At the end the controller traits values will match the controller
        widget user defined parameters.
        """
        # Go through all the controller widget controls
        for control_name, control_groups in six.iteritems(self._controls):
            for group_name, control in six.iteritems(control_groups):

                # Unpack the control item
                trait, control_class, control_instance, control_label = control

                # Call the current control specific update controller method
                control_class.update_controller(self, control_name,
                                                control_instance)

    def update_controller_widget(self):
        """ Update the controller widget.

        At the end the controller widget user editable parameters will match
        the controller traits values.
        """
        # Go through all the controller widget controls
        for control_name, control_groups in six.iteritems(self._controls):
            for group_name, control in six.iteritems(control_groups):

                # Unpack the control item
                trait, control_class, control_instance, control_label = control

                # Call the current control specific update controller widget
                # method
                control_class.update_controller_widget(self, control_name,
                                                       control_instance)

    def connect(self):
        """ Connect the controller trait and the controller widget controls

        At the end al control will be connected with the associated trait, and
        when a 'user_traits_changed' signal is emited, the controls are updated
        (ie, deleted if necessary).
        """
        # If the controller and controller widget are not yet connected
        if not self.connected:

            # Go through all the controller widget controls
            for control_name, control_groups in six.iteritems(self._controls):
                for group_name, control in six.iteritems(control_groups):

                    # Unpack the control item
                    trait, control_class, control_instance, control_label \
                        = control

                    # Call the current control specific connection method
                    logger.debug("Connecting control '{0}: {1}'...".format(
                        control_name, control_instance))
                    control_class.connect(self, control_name, control_instance)

            # Add an event connected with the 'user_traits_changed' controller
            # signal: update the controls
            self.controller.on_trait_change(self.update_controls,
                                            "user_traits_changed",
                                            dispatch='ui')

            # if 'visible_groups' is a trait, connect it to groups
            if self.controller.trait('visible_groups'):
                self.controller.on_trait_change(self.groups_vibility_changed,
                                                'visible_groups',
                                                dispatch='ui')

            # Update the controller widget values
            self.update_controller_widget()

            # Update the connection status
            self.connected = True

    def connect_keys(self):
        if not self.editable_labels or self._keys_connections:
            return
        keys_connect = {}
        # Go through all the controller widget controls
        for control_name, control_groups in six.iteritems(self._controls):
            for group_name, control in six.iteritems(control_groups):
                hook1 = partial(self.__class__._key_modified,
                                weakref.proxy(self), control_name)
                hook2 = partial(self.__class__._delete_key,
                                weakref.proxy(self), control_name)
                #control = self._controls[control_name]
                label_control = control[3]
                if isinstance(label_control, tuple):
                    label_control = label_control[0]
                label_control.userModification.connect(hook1)
                label_control.buttonPressed.connect(hook2)
                keys_connect[control_name] = (label_control, hook1, hook2)
        self._keys_connections = keys_connect

    def disconnect(self):
        """ Disconnect the controller trait and the controller widget controls
        """
        # If the controller and controller widget are connected
        if self.connected:

            # Remove the 'update_controls' event connected with the
            # 'user_traits_changed' controller signal
            self.controller.on_trait_change(self.update_controls,
                                            "user_traits_changed",
                                            remove=True)

            # if 'visible_groups' is a trait, connect it to groups
            if self.controller.trait('visible_groups'):
                self.controller.on_trait_change(self.groups_vibility_changed,
                                                'visible_groups',
                                                remove=True)

            # Go through all the controller widget controls
            for control_name, control_groups in six.iteritems(self._controls):
                for group_name, control in six.iteritems(control_groups):

                    # Unpack the control item
                    trait, control_class, control_instance, control_label \
                        = control

                    # Call the current control specific disconnection method
                    control_class.disconnect(self, control_name,
                                             control_instance)

            # Update the connection status
            self.connected = False

    def disconnect_keys(self):
        for control_name, connections in six.iteritems(self._keys_connections):
            label_widget, hook1, hook2 = connections
            label_widget.userModification.disconnect(hook1)
            label_widget.buttonPressed.disconnect(hook2)
        self._keys_connections = {}

    def update_controls(self):
        """ Event to refresh controller widget controls and intern parameters.

        The refresh is done off line, ie. we need first to disconnect the
        controller and the controller widget.
        """
        # Get the controller traits
        user_traits = self.controller.user_traits()

        # Assess the refreshing is done off line
        was_connected = self.connected
        if was_connected:
            self.disconnect()
        self.disconnect_keys()

        # Go through all the controller widget controls
        to_remove_controls = []
        for control_name, control_groups in six.iteritems(self._controls):
            for group_name, control in six.iteritems(control_groups):

                # Message
                logger.debug(
                    "Check if we need to update '{0}': trait in '{1}' "
                    "different from '{2}'?".format(
                        control_name, control, user_traits.get(control_name)))

                # Unpack the control item
                trait, control_class, control_instance, control_label = control

                # If the the controller trait is different from the trait
                # associated with the control
                if user_traits.get(control_name) != trait:

                    # Close and schedule for deletation the control widget
                    control_instance.close()
                    control_instance.deleteLater()

                    # Close and schedule for deletation the control labels
                    if isinstance(control_label, tuple):
                        for label in control_label:
                            label.close()
                            label.deleteLater()
                    elif control_label:
                        control_label.close()
                        control_label.deleteLater()

                    # Store the controls to be removed
                    to_remove_controls.append(control_name)

        # Delete all dead controls from the class '_controls' intern parameter
        for control_name in to_remove_controls:
            logger.debug("Delete control '{0}'.".format(control_name))
            del self._controls[control_name]

        # Recreate all the layout controls associated with the controller
        # values we want to tune (ie the user_traits): this procedure check
        # if the control has not already been created.
        self._create_controls()

        # Restore the connection status
        if was_connected:
            self.connect()
        self.connect_keys()

        # Update the widget geometry
        self.updateGeometry()

    #
    # Private members
    #

    def _check(self):
        """ Check that all edited fields are correct.

        At the end the controls with wrong values will be colored in red.
        """
        # Go through all the controller widget controls
        for control_name, control_groups in six.iteritems(self._controls):
            for group_name, control in six.iteritems(control_groups):

                # Unpack the control item
                trait, control_class, control_instance, control_label = control

                # Call the current control specific check method
                control_class.check(control_instance)

    def _create_controls(self):
        """ Method that will create a control for each user trait of the
        controller.

        Controller trait parameters that cannot be maped to controls
        will not appear in the user interface.
        """
        # Select only the controller traits of interest
        all_traits = self.controller.user_traits()
        if self.select_controls is None:
            selected_traits = all_traits
        elif self.select_controls == "inputs":
            selected_traits = dict(
                (trait_name, trait)
                for trait_name, trait in six.iteritems(all_traits)
                if trait.output == False)
        elif self.select_controls == "outputs":
            selected_traits = dict(
                (trait_name, trait)
                for trait_name, trait in six.iteritems(all_traits)
                if trait.output == True)
        else:
            raise Exception(
                "Unrecognized 'select_controls' option '{0}'. Valid "
                "options are 'inputs' or 'outputs'.".format(
                    self.select_controls))

        # Go through all the controller user traits
        skipped = set(['visible_groups'])
        for trait_name, trait in six.iteritems(selected_traits):
            if trait_name in skipped:
                continue
            # Create the widget
            self.create_control(trait_name, trait)

    def create_control(self, trait_name, trait):
        """ Create a control associated to a trait.

        Parameters
        ----------
        trait_name: str (mandatory)
            the name of the trait from which we want to create a control. The
            control widget will share the same name
        trait: Trait (mandatory)
            a trait item
        """
        # Search if the current trait has already been processed
        control_groups = self._controls.get(trait_name)
        control_instances = []
        control_labels = []

        # If no control has been found in the class intern parameters
        if control_groups is None:

            # Call the search function that will map the trait type to the
            # corresponding control type
            control_class = self.get_control_class(trait)

            # If no control has been found, skip this trait and print
            # an error message. Note that the parameter will not be
            # accessible in the user interface.
            if control_class is None:
                logger.error("No control defined for trait '{0}': {1}. This "
                             "parameter will not be accessible in the "
                             "user interface.".format(trait_name,
                                                      trait_ids(trait)))
                return

            # handle groups
            layouts = []
            groups = trait.groups
            if groups:
                for group in groups:
                    group_widget = self._groups.get(group)
                    if group_widget is None:
                        group_widget = self._create_group_widget(group)
                        self._groups[group] = group_widget
                    layouts.append(group_widget.hideable_widget.layout())
            else:
                layouts.append(self._grid_layout)

            group = None
            for i, layout in enumerate(layouts):
                if groups:
                    group = groups[i]
                control_instance, control_label \
                      = self._create_control_in_layout(trait_name, trait,
                                                       layout, group)
                control_instances.append(control_instance)
                if control_label:
                    if not isinstance(control_label, tuple):
                        control_label = [control_label]
                    control_labels += list(control_label)
                    if isinstance(control_label[0], QtGui.QLabel):
                        control_label[0].setTextInteractionFlags(
                            QtCore.Qt.TextSelectableByKeyboard
                            | QtCore.Qt.TextSelectableByMouse)

        # Otherwise, the control associated with the current trait name is
        # already inserted in the grid layout, just unpack the values
        # contained in the private '_controls' class parameter
        else:
            for group, control in six.iteritems(control_groups):
                trait, control_class, control_instance, control_label = control
                control_instances.append(control_instance)
                if control_label:
                    if isinstance(control_label, tuple):
                        control_labels += list(control_label)
                    else:
                        control_labels.append(control_label)

        # Each trait has a hidden property. Take care of this information
        hide = (getattr(trait, 'hidden', False)
                or getattr(trait, 'unused', False))

        # Show/Hide the control and associated labels
        for control_instance in control_instances:
            control_instance.setVisible(not hide)
        for label in control_labels:
            label.setVisible(not hide)

        ## Show the control and associated labels
        #else:
        #for control_instance in control_instances:
        #control_instance.show()
        #for label in control_labels:
        #label.show()

    def _create_group_widget(self, group):
        group_widget = QtGui.QGroupBox()
        last_row = self._grid_layout.rowCount()
        self._grid_layout.addWidget(group_widget, last_row, 0, 1, 2)
        lay1 = QtGui.QVBoxLayout()
        lay1.setContentsMargins(0, 0, 0, 0)
        lay2 = QtGui.QHBoxLayout()
        lay1.addLayout(lay2)
        lay2.setContentsMargins(10, 0, 0, 0)
        lay2.addWidget(QtGui.QLabel('<html><em>%s</em></html>' % group))
        lay2.addStretch(1)
        icon = QtGui.QIcon()
        icon.addPixmap(
            QtGui.QPixmap(_fromUtf8(":/soma_widgets_icons/nav_down")),
            QtGui.QIcon.Normal, QtGui.QIcon.Off)
        group_widget.fold_button = QtGui.QPushButton(icon, '')
        group_widget.fold_button.setFixedSize(30, 20)
        lay2.addWidget(group_widget.fold_button)
        widget = QtGui.QWidget()
        group_widget.setLayout(lay1)
        lay1.addWidget(widget)
        group_widget.hideable_widget = widget
        layout = QtGui.QGridLayout()
        widget.setLayout(layout)
        layout.setAlignment(QtCore.Qt.AlignTop)
        layout.setSpacing(3)
        layout.setContentsMargins(5, 5, 5, 5)
        group_widget.setAlignment(QtCore.Qt.AlignLeft)

        visible_groups = getattr(self.controller, 'visible_groups', set())
        if group in visible_groups:
            show = True
        else:
            show = False
        group_widget.hideable_widget.setVisible(show)

        if not show:
            icon = QtGui.QIcon()
            icon.addPixmap(
                QtGui.QPixmap(_fromUtf8(":/soma_widgets_icons/nav_right")),
                QtGui.QIcon.Normal, QtGui.QIcon.Off)
            group_widget.fold_button.setIcon(icon)

        #group_widget.fold_button.clicked.connect(SomaPartial(
        #self._toggle_group_visibility, group))
        # FIXME: if we use this, self gets deleted somewhere. This is not
        # normal.
        group_widget.fold_button.clicked.connect(
            partial(self.__class__._toggle_group_visibility, weak_proxy(self),
                    group))

        return group_widget

    def _set_group_visibility(self, group, checked):
        group_widget = self._groups[group]
        group_widget.hideable_widget.setVisible(checked)
        icon = QtGui.QIcon()
        if checked:
            icon.addPixmap(
                QtGui.QPixmap(_fromUtf8(":/soma_widgets_icons/nav_down")),
                QtGui.QIcon.Normal, QtGui.QIcon.Off)
        else:
            icon.addPixmap(
                QtGui.QPixmap(_fromUtf8(":/soma_widgets_icons/nav_right")),
                QtGui.QIcon.Normal, QtGui.QIcon.Off)
        group_widget.fold_button.setIcon(icon)

    def _toggle_group_visibility(self, group, checked=False):
        visible_groups = getattr(self.controller, 'visible_groups', set())
        if group in visible_groups:
            show = False
            visible_groups.remove(group)
        else:
            show = True
            visible_groups.add(group)
        self._set_group_visibility(group, show)
        self.controller.visible_groups = visible_groups

    def _create_control_in_layout(self, trait_name, trait, layout, group=None):
        # Call the search function that will map the trait type to the
        # corresponding control type
        control_class = self.get_control_class(trait)
        # FIXME: for now we use a hack for compound/either traits, until
        # we write a "real" GUI for them
        if isinstance(trait.trait_type, (traits.TraitCompound, traits.Either)):
            # compound trait: use the 1st
            trait = trait.handler.handlers[0].as_ctrait()
        # Create the control instance and associated label
        if self.editable_labels:
            label_class = DeletableLineEdit
        else:
            label_class = QtGui.QLabel
        control_instance, control_label = control_class.create_widget(
            self, trait_name, getattr(self.controller, trait_name), trait,
            label_class)
        control_class.is_valid(control_instance)

        # If the trait contains a description, insert a tool tip to the
        # control instance
        tooltip = ""
        if trait.desc:
            tooltip = "<b>" + trait_name + ":</b> " + trait.desc
        control_instance.setToolTip(tooltip)

        # Get the last empty row in the grid layout
        # Trick: If the grid layout is empty check the element 0
        # (not realistic but te grid layout return None)
        last_row = layout.rowCount()
        widget_item = layout.itemAtPosition(last_row, 1)
        while widget_item is None and last_row > 0:
            last_row -= 1
            widget_item = layout.itemAtPosition(last_row, 1)
        last_row += 1

        # If the control has no label, append the control in a two
        # columns span area of the grid layout
        if control_label is None:

            # Grid layout 'addWidget' parameters: QWidget, int row,
            # int column, int rowSpan, int columnSpan
            layout.addWidget(control_instance, last_row, 0, 1, 2)

        # If the control has two labels, add a first row with the
        # labels (one per column), and add the control in
        # the next row of the grid layout in the second column
        elif isinstance(control_label, tuple):

            # Get the number of label
            nb_of_labels = len(control_label)

            # If more than two labels are detected, print an error message.
            # We actually consider only the two first labels and skip the
            # others
            if nb_of_labels > 2:
                logger.error("To many labels associated with control "
                             "'{0}': {1}. Only consider the two first "
                             "labels and skip the others".format(
                                 trait_name, control_label))

            # Append each label in different columns
            if not self.hide_labels:
                layout.addWidget(control_label[0], last_row, 0)
            if nb_of_labels >= 2:
                layout.addWidget(control_label[1], last_row, 1)

            # Append the control in the next row in the second column
            layout.addWidget(control_instance, last_row + 1, 1)

        # Otherwise, append the label and control in two separate
        # columns of the grid layout
        else:

            # Append the label in the first column
            if not self.hide_labels:
                layout.addWidget(control_label, last_row, 0)

            # Append the control in the second column
            layout.addWidget(control_instance, last_row, 1, 1, 1)

        # Store some informations about the inserted control in the
        # private '_controls' class parameter
        # Keys: the trait names
        # Parameters: the trait - the control name - the control - and
        # the labels associated with the control
        self._controls.setdefault(trait_name,
                                  {})[group] = (trait, control_class,
                                                control_instance,
                                                control_label)

        return control_instance, control_label

    def _key_modified(self, old_key):
        """ Dict / open controller key modification callback
        """
        control_groups = self._controls[old_key]
        control_labels = []
        for group_name, control in six.iteritems(control_groups):
            control_labels += control[3]
        for control_label in control_labels:
            key = str(control_label.text())
            was_connected = self.connected
            if was_connected:
                self.disconnect()
            self.disconnect_keys()

        controller = self.controller
        trait = controller.trait(old_key)
        controller.add_trait(key, trait)
        setattr(controller, key, getattr(controller, old_key))
        controller.remove_trait(old_key)
        self._controls[key] = self._controls[old_key]
        del self._controls[old_key]
        if was_connected:
            self.connect()
        # reconnect label widget
        self.connect_keys()
        # self.update_controls()  # not even needed
        if hasattr(self, 'main_controller_def'):
            main_control_class, controller_widget, control_name, frame = \
                self.main_controller_def
            main_control_class.update_controller(controller_widget,
                                                 control_name, frame)
        # self.update_controller()

    def _delete_key(self, key):
        """ Dict / open controller key deletion callback
        """
        controller = self.controller
        trait = controller.trait(key)
        controller.remove_trait(key)
        self.update_controls()
        if hasattr(self, 'main_controller_def'):
            main_control_class, controller_widget, control_name, frame = \
                self.main_controller_def
            main_control_class.update_controller(controller_widget,
                                                 control_name, frame)
        # self.update_controller()

    def groups_vibility_changed(self):
        visible_groups = self.controller.visible_groups or set()
        for group, group_widget in six.iteritems(self._groups):
            if group in visible_groups:
                show = True
            else:
                show = False
            self._set_group_visibility(group, show)

    #
    # Class Methods
    #
    @classmethod
    def get_control_class(cls, trait):
        """ Find the control associated with the input trait.

        The mapping is defined in the global class parameter
        '_defined_controls'.

        Parameters
        ----------
        cls: ControllerWidget (mandatory)
            a ControllerWidget class
        trait: Trait (mandatory)
            a trait item

        Returns
        -------
        control_class: class
            the control class associated with the input trait.
            If no match has been found, return None
        """
        # Initilaize the output variable
        control_class = None

        # Go through the trait string description: can have multiple element
        # when either trait is used
        # Todo:: we actualy need to create all the controls and let the user
        # choose which one he wants to fill.
        for trait_id in trait_ids(trait):

            # Recursive construction: consider only the top level
            trait_id = trait_id.split("_")[0]

            # Try to get the control class
            control_class = cls._defined_controls.get(trait_id)

            # Stop when we have a match
            if control_class is not None:
                break

        return control_class
コード例 #7
0
class ProcessAttributes(Controller):
    '''
    This is the base class for managing attributes for a process.

    It stores attributes associated with a Process, for each of its parameters.
    Attribute values can be accessed "globally" (for the whole process) as
    ProcessAttributes instance traits.
    Or each parameter attributes set may be accessed individually, using
    :meth:`get_parameters_attributes()`.

    To define attributes for a process, the programmer may subclass
    ProcessAttributes and define some EditableAttributes in it. Each
    EditableAttributes is a group of attributes (traits in the EditableAttributes instance or subclass).

    A ProcessAttributes subclass should be registered to a factory to be linked
    to a process name, using the `factory_id` class variable.

    See Capsul :ref:`advanced usage doc <completion>` for details.
    '''
    def __init__(self, process, schema_dict):
        super(ProcessAttributes, self).__init__()
        self._process = process
        self._schema_dict = schema_dict
        self.editable_attributes = OrderedDict()
        self.parameter_attributes = {}

    def __getinitargs__(self):
        # needed for self.copy()
        return (self._process, self._schema_dict)

    def set_parameter_attributes(self,
                                 parameter,
                                 schema,
                                 editable_attributes,
                                 fixed_attibute_values,
                                 allow_list=True):
        '''
        Set attributes associated with a single process parameter.

        Parameters
        ----------
        parameter: str
            process parameter name
        schema: str
            schema used for it (input, output, shared)
        editable_attributes: str, EditableAttributes instance, or list of them
            EditableAttributes or id containing attributes traits
        fixed_attibute_values: dict (str/str)
            values of non-editable attributes
        allow_list: bool
            if True (the default), it the process parameter is a list, then
            attributes are transformed into lists.
        '''
        if parameter in self.parameter_attributes:
            if schema == 'link':
                return  # this is just a lower priority
            raise KeyError('Attributes already set for parameter %s' %
                           parameter)
        process = self._process
        if isinstance(process, ProcessNode):
            process = process.process
        if parameter not in process._instance_traits():
            print('WARNING: parameter', parameter, 'not in process',
                  process.name)
            return
        if isinstance(editable_attributes, six.string_types) \
                or isinstance(editable_attributes, EditableAttributes):
            editable_attributes = [editable_attributes]
        parameter_editable_attributes = []
        for ea in editable_attributes:
            add_editable_attributes = False
            if isinstance(ea, six.string_types):
                key = ea
                ea = self.editable_attributes.get(key)
                if ea is None:
                    ea = getattr(self._schema_dict[schema], key)()
                    self.editable_attributes[key] = ea
                    add_editable_attributes = True
            elif isinstance(ea, EditableAttributes):
                key = ea
                if key not in self.editable_attributes:
                    self.editable_attributes[key] = ea
                    add_editable_attributes = True
            else:
                raise TypeError(
                    'Invalid value for editable attributes: {0}'.format(ea))
            parameter_editable_attributes.append(ea)
            if add_editable_attributes:
                is_list = allow_list \
                        and isinstance(process.trait(parameter).trait_type,
                                       traits.List)
                for name in list(ea.user_traits().keys()):
                    # don't use items() since traits may change during iter.
                    trait = ea.trait(name)
                    if is_list:
                        trait = traits.List(trait)
                        ea.remove_trait(name)
                        ea.add_trait(name, trait)
                    if name in self.user_traits():
                        if not is_list \
                                and isinstance(self.trait(name).trait_type,
                                               traits.List):
                            # a process attribute trait may have been changed
                            # into a list. Here we assume attributes are only
                            # strings (at this point - it can be changed at
                            # higher lever afterwards), so change it back into
                            # a single value trait.
                            self.remove_trait(name)
                            self.add_trait(name, trait)
                        # else don't add it again: this way non-list versions
                        # of attributes get priority. If both exist, lists
                        # should get one single value (otherwise there is an
                        # ambiguity or inconsistency), so the attribute should
                        # not be a list.
                    else:
                        self.add_trait(name, trait)
                    f = SomaPartial(set_attribute, ea)
                    self.on_trait_change(f, name)
        self.parameter_attributes[parameter] = (parameter_editable_attributes,
                                                fixed_attibute_values)

    def get_parameters_attributes(self):
        ''' Get attributes for each process parameter
        '''
        pa = {}
        process = self._process
        if isinstance(process, ProcessNode):
            process = process.process
        for parameter, trait in six.iteritems(process.user_traits()):
            if trait.output:
                if hasattr(process, 'id'):
                    process_name = process.id
                else:
                    process_name = process.name
                attributes = {
                    'generated_by_process': process_name,
                    'generated_by_parameter': parameter
                }
            else:
                attributes = {}
            editable_fixed = self.parameter_attributes.get(parameter, ([], {}))
            editable_attributes, fixed_attibute_values = editable_fixed
            for ea in editable_attributes:
                for attribute in ea.user_traits():
                    value = getattr(ea, attribute)
                    attributes[attribute] = value
            attributes.update(fixed_attibute_values)
            if attributes:
                pa[parameter] = attributes
        return pa

    def copy(self, with_values=True):
        ''' overloads :meth:`soma.Controller.copy`
        '''
        other = self.__class__(self._process, self._schema_dict)
        ea_map = {}
        for parameter, pa in six.iteritems(self.parameter_attributes):
            if parameter not in other.parameter_attributes:
                # otherwise assume this has been done in a subclass constructor
                eas, fa = pa
                oeas = []
                for ea in eas:
                    oea = ea_map.get(ea)
                    if oea is None:
                        oea = ea.copy()
                    oeas.append(oea)
                other.set_parameter_attributes(parameter,
                                               '',
                                               oeas,
                                               fa,
                                               allow_list=False)
        # copy the values
        if with_values:
            for name in self.user_traits():
                #print('copy attribs:', self, name, getattr(self, name))
                #print('   to:', other.trait(name).trait_type)
                #if isinstance(other.trait(name).trait_type, traits.List):
                #print('    ', other.trait(name).inner_traits[0].trait_type)
                #if isinstance(self.trait(name).trait_type, traits.List):
                #print('    self list:', self.trait(name).inner_traits[0].trait_type)
                setattr(other, name, getattr(self, name))

        return other

    def copy_to_single(self, with_values=True):
        ''' Similar to :meth:`copy`, excepts that it converts list attributes
        into single values. This is useful within the completion system
        infrastructure, to get from an attributes set containing lists
        (process parameters which are lists), a single value allowing to
        determine a single path.

        This method is merely useful to the end user.
        '''
        other = ProcessAttributes(self._process, self._schema_dict)

        ea_map = {}
        for parameter, pa in six.iteritems(self.parameter_attributes):
            if parameter not in other.parameter_attributes:
                # otherwise assume this has been done in a subclass constructor
                eas, fa = pa
                oeas = []
                for ea in eas:
                    oea = ea_map.get(ea)
                    if oea is None:
                        oea = EditableAttributes()
                        for name, trait in six.iteritems(ea.user_traits()):
                            if isinstance(trait.trait_type, traits.List):
                                trait = trait.inner_traits[0]
                            oea.add_trait(name, trait)
                        ea_map[ea] = oea
                    oeas.append(oea)
                other.set_parameter_attributes(parameter,
                                               '',
                                               oeas,
                                               fa,
                                               allow_list=False)
        # copy the values
        if with_values:
            for name in self.user_traits():
                value = getattr(self, name)
                if isinstance(value, list):
                    if len(value) != 0:
                        value = value[0]
                    else:
                        value = self.trait(name).inner_traits[0].default
                if value is not None:
                    setattr(other, name, value)
        return other
コード例 #8
0
ファイル: attributes_schema.py プロジェクト: cedrixic/capsul
 def __init__(self, process, schema_dict):
     super(ProcessAttributes, self).__init__()
     self._process = process
     self._schema_dict = schema_dict
     self.editable_attributes = OrderedDict()
     self.parameter_attributes = {}
コード例 #9
0
ファイル: attributes_schema.py プロジェクト: cedrixic/capsul
class ProcessAttributes(Controller):
    '''
    This is the base class for managing attributes for a process.
    '''
    
    def __init__(self, process, schema_dict):
        super(ProcessAttributes, self).__init__()
        self._process = process
        self._schema_dict = schema_dict
        self.editable_attributes = OrderedDict()
        self.parameter_attributes = {}

    
    def set_parameter_attributes(self, parameter, schema, editable_attributes, fixed_attibute_values):
        if parameter in self.parameter_attributes:
            raise KeyError('Attributes already set for parameter %s' % parameter)
        if isinstance(editable_attributes, six.string_types) or isinstance(editable_attributes, EditableAttributes):
            editable_attributes = [editable_attributes]
        parameter_editable_attributes = []
        for ea in editable_attributes:
            add_editable_attributes = False
            if isinstance(ea, six.string_types):
                key = ea
                ea = self.editable_attributes.get(key)
                if ea is None:
                    ea = getattr(self._schema_dict[schema], key)()
                    self.editable_attributes[key] = ea
                    add_editable_attributes = True
            elif isinstance(ea, EditableAttributes):
                key = ea
                if key not in self.editable_attributes:
                    self.editable_attributes[key] = ea
                    add_editable_attributes = True
            else:
                raise TypeError('Invalid value for editable attributes: {0}'.format(ea))
            parameter_editable_attributes.append(ea)
            if add_editable_attributes:
                for name, trait in six.iteritems(ea.user_traits()):
                    self.add_trait(name, trait)
                    f = SomaPartial(set_attribute, ea)
                    self.on_trait_change(f, name)
        self.parameter_attributes[parameter] = (parameter_editable_attributes, fixed_attibute_values)

    def get_parameters_attributes(self):
        pa = {}
        for parameter, trait in six.iteritems(self._process.user_traits()):
            if trait.output:
                if hasattr(self._process, 'id'):
                    process_name = self._process.id
                else:
                    process_name = self._process.name
                attributes = {
                    'generated_by_process': process_name,
                    'generated_by_parameter': parameter}
            else:
                attributes = {}
            editable_fixed = self.parameter_attributes.get(parameter, ([], {}))
            editable_attributes, fixed_attibute_values = editable_fixed
            for ea in editable_attributes:
                for attribute in ea.user_traits():
                    value = getattr(ea, attribute)
                    attributes[attribute] = value
            attributes.update(fixed_attibute_values)
            if attributes:
                pa[parameter] = attributes
        return pa