示例#1
0
    def add_dependency_input_connection(self, node_name, node_input_name,
                                        dependency_name,
                                        dependency_output_name):
        """Adds a connection from a dependency output to the input of a node

        :param node_name: The name of the node whose input is being connected to
        :type node_name: string
        :param node_input_name: The name of the node's input
        :type node_input_name: string
        :param dependency_name: The name of the dependency node whose output is being connected to the input
        :type dependency_name: string
        :param dependency_output_name: The name of the dependency node's output
        :type dependency_output_name: string

        :raises :class:`recipe.definition.exceptions.InvalidDefinition`: If either node is unknown, the dependency node
            is the wrong type, or the connection is a duplicate
        """

        if dependency_name not in self.graph:
            raise InvalidDefinition(
                'UNKNOWN_NODE', 'Node \'%s\' is not defined' % dependency_name)

        if self.graph[
                dependency_name].node_type == RecipeNodeDefinition.NODE_TYPE:
            msg = 'Node \'%s\' cannot have a connection to a recipe node' % node_name
            raise InvalidDefinition('CONNECTION_INVALID_NODE', msg)

        connection = DependencyInputConnection(node_input_name,
                                               dependency_name,
                                               dependency_output_name)
        self._add_connection(node_name, connection)
示例#2
0
    def add_dependency(self, parent_name, child_name, acceptance=True):
        """Adds a dependency that one node has upon another node

        :param parent_name: The name of the parent node
        :type parent_name: string
        :param child_name: The name of the child node
        :type child_name: string
        :param acceptance: Whether the child node should run when the parent is accepted or when it is not accepted
        :type acceptance: bool

        :raises :class:`recipe.definition.exceptions.InvalidDefinition`: If either node is unknown
        """

        if child_name not in self.graph:
            raise InvalidDefinition('UNKNOWN_NODE',
                                    'Node \'%s\' is not defined' % child_name)
        if parent_name not in self.graph:
            raise InvalidDefinition('UNKNOWN_NODE',
                                    'Node \'%s\' is not defined' % parent_name)

        child_node = self.graph[child_name]
        parent_node = self.graph[parent_name]
        child_node.add_dependency(parent_node, acceptance)

        self._topological_order = None  # Invalidate cache
示例#3
0
    def __init__(self, definition=None, do_validate=False):
        """Creates a v1 recipe definition JSON object from the given dictionary

        :param definition: The recipe definition JSON dict
        :type definition: dict
        :param do_validate: Whether to perform validation on the JSON schema
        :type do_validate: bool

        :raises :class:`recipe.definition.exceptions.InvalidDdefinition`: If the given definition is invalid
        """

        if not definition:
            definition = {}
        self._definition = definition

        if 'version' not in self._definition:
            self._definition['version'] = DEFAULT_VERSION

        if self._definition['version'] != DEFAULT_VERSION:
            msg = '%s is an unsupported version number'
            raise InvalidDefinition('INVALID_VERSION',
                                    msg % self._definition['version'])

        self._populate_default_values()

        try:
            if do_validate:
                validate(definition, RECIPE_DEFINITION_SCHEMA)
        except ValidationError as ex:
            raise InvalidDefinition(
                'INVALID_DEFINITION',
                'Invalid recipe definition: %s' % unicode(ex))
示例#4
0
    def _topological_order_visit(self, node, results, perm_set, temp_set):
        """Recursive depth-first search algorithm for determining a topological ordering of the recipe nodes

        :param node: The current node
        :type node: :class:`recipe.definition.node.NodeDefinition`
        :param results: The list of node names in topological order
        :type results: :func:`list`
        :param perm_set: A permanent set of visited nodes (node names)
        :type perm_set: set
        :param temp_set: A temporary set of visited nodes (node names)
        :type temp_set: set
        :returns: A list of nodes in topological order
        :rtype: :func:`list`

        :raises :class:`recipe.definition.exceptions.InvalidDefinition`: If the definition contains a circular
            dependency
        """

        if node.name in temp_set:
            msg = 'Recipe node \'%s\' has a circular dependency on itself' % node.name
            raise InvalidDefinition('CIRCULAR_DEPENDENCY', msg)

        if node.name not in perm_set:
            temp_set.add(node.name)
            for child_node in node.children.values():
                self._topological_order_visit(child_node, results, perm_set,
                                              temp_set)
            perm_set.add(node.name)
            temp_set.remove(node.name)
            results.insert(0, node.name)

        return results
示例#5
0
    def validate(self, node_input_interfaces, node_output_interfaces):
        """Validates this recipe definition

        :param node_input_interfaces: The input interface for each job/recipe node stored by node name
        :type node_input_interfaces: dict
        :param node_output_interfaces: The output interface for each job node stored by node name
        :type node_output_interfaces: dict
        :returns: A list of warnings discovered during validation
        :rtype: :func:`list`

        :raises :class:`recipe.definition.exceptions.InvalidDefinition`: If the definition is invalid
        """

        try:
            warnings = self.input_interface.validate()
        except InvalidInterface as ex:
            raise InvalidDefinition('INPUT_INTERFACE', ex.error.description)

        # Processing nodes in topological order will also detect any circular dependencies
        for node_name in self.get_topological_order():
            node = self.graph[node_name]
            # Grab input and output interfaces from condition nodes
            if node.node_type == ConditionNodeDefinition.NODE_TYPE:
                node_input_interfaces[node_name] = node.input_interface
                node_output_interfaces[node_name] = node.output_interface
            warnings.extend(
                node.validate(self.input_interface, node_input_interfaces,
                              node_output_interfaces))

        return warnings
示例#6
0
    def __init__(self, definition=None, do_validate=False):
        """Creates a v6 recipe definition JSON object from the given dictionary

        :param definition: The recipe definition JSON dict
        :type definition: dict
        :param do_validate: Whether to perform validation on the JSON schema
        :type do_validate: bool

        :raises :class:`recipe.definition.exceptions.InvalidDdefinition`: If the given definition is invalid
        """

        if not definition:
            definition = {}
        self._definition = copy.deepcopy(definition)

        if 'version' not in self._definition:
            self._definition['version'] = SCHEMA_VERSION

        if self._definition['version'] != SCHEMA_VERSION:
            self._convert_from_v1()

        self._populate_default_values()

        try:
            if do_validate:
                validate(self._definition, RECIPE_DEFINITION_SCHEMA)
        except ValidationError as ex:
            raise InvalidDefinition(
                'INVALID_DEFINITION',
                'Invalid recipe definition: %s' % unicode(ex))
示例#7
0
    def validate(self, recipe_input_interface, node_input_interfaces,
                 node_output_interfaces):
        """Validates this node

        :param recipe_input_interface: The interface for the recipe input
        :type recipe_input_interface: :class:`data.interface.interface.Interface`
        :param node_input_interfaces: The input interface for each node stored by node name
        :type node_input_interfaces: dict
        :param node_output_interfaces: The output interface for each node stored by node name
        :type node_output_interfaces: dict
        :returns: A list of warnings discovered during validation
        :rtype: :func:`list`

        :raises :class:`recipe.definition.exceptions.InvalidDefinition`: If the definition is invalid
        """

        warnings = []
        input_interface = node_input_interfaces[self.name]
        connecting_interface = Interface()

        # Generate complete dependency set for this node
        all_dependencies = set()
        dependency_list = list(self.parents.values())
        while dependency_list:
            dependency = dependency_list.pop()
            if dependency.name not in all_dependencies:
                all_dependencies.add(dependency.name)
                dependency_list.extend(list(dependency.parents.values()))

        try:
            for connection in self.connections.values():
                # Validate each connection
                warnings.extend(connection.validate(all_dependencies))
                # Combine all connections into a connecting interface
                warnings.extend(
                    connection.add_parameter_to_interface(
                        connecting_interface, recipe_input_interface,
                        node_output_interfaces))
            # Validate that connecting interface can be passed to this interface
            warnings.extend(
                input_interface.validate_connection(connecting_interface))
        except InvalidInterfaceConnection as ex:
            error_desc = ex.error.description

            # Parse error message to try and give the user a better message.
            if error_desc.startswith("Parameter") and error_desc.endswith(
                    "is required"):
                msg = 'input \'%s\' is missing for %s' % (
                    error_desc.split("\'")[1], self.name)
            else:
                msg = 'Node \'%s\' interface error: %s' % (self.name,
                                                           error_desc)

            raise InvalidDefinition('NODE_INTERFACE', msg)

        return warnings
示例#8
0
    def __init__(self, definition=None, do_validate=False):
        """Creates a v6 recipe definition JSON object from the given dictionary

        :param definition: The recipe definition JSON dict
        :type definition: dict
        :param do_validate: Whether to perform validation on the JSON schema
        :type do_validate: bool

        :raises :class:`recipe.definition.exceptions.InvalidDefinition`: If the given definition is invalid
        """

        if not definition:
            definition = {}
        self._definition = copy.deepcopy(definition)

        if 'version' not in self._definition:
            self._definition['version'] = SCHEMA_VERSION

        if self._definition['version'] not in SCHEMA_VERSIONS:
            self._convert_from_v1()

        self._populate_default_values()

        try:
            if do_validate:
                validate(self._definition, RECIPE_DEFINITION_SCHEMA)
        except ValidationError as ex:
            if type(ex.instance) == dict:
                node_name = ex.absolute_path[-2]
            else:
                node_name = ex.instance

            if len(ex.context) > 0:
                # Get the list of all sub-errors. Raise this error as a 'JSON' so that
                # the UI can display each error in their own error box.
                error_msg = json.dumps(['Issue with %s: %s' % (node_name, filter_out_us(m.message))
                                        for m in ex.context])
                raise InvalidDefinition('INVALID_DEFINITION_JSON', error_msg)
            else:
                raise InvalidDefinition('INVALID_DEFINITION', 'Issue with %s: %s' % (node_name,
                        filter_out_us(ex.message)))
示例#9
0
    def _add_node(self, node):
        """Adds a node to the recipe graph

        :param node: The node
        :type node: :class:`recipe.definition.node.NodeDefinition`

        :raises :class:`recipe.definition.exceptions.InvalidDefinition`: If the node is duplicated
        """

        if node.name in self.graph:
            raise InvalidDefinition('DUPLICATE_NODE', 'Node \'%s\' is already defined' % node.name)

        self.graph[node.name] = node
        self._topological_order = None  # Invalidate cache
示例#10
0
    def _add_connection(self, node_name, connection):
        """Adds a connection to the input of the node

        :param node_name: The name of the node whose input is being connected to
        :type node_name: string
        :param connection: The connection to the node input
        :type connection: :class:`recipe.definition.connection.InputConnection`

        :raises :class:`recipe.definition.exceptions.InvalidDefinition`: If the node is unknown or the connection is a
            duplicate
        """

        if node_name not in self.graph:
            raise InvalidDefinition('UNKNOWN_NODE', 'Node \'%s\' is not defined' % node_name)

        node = self.graph[node_name]
        node.add_connection(connection)
示例#11
0
文件: node.py 项目: sau29/scale
    def add_connection(self, connection):
        """Adds a connection that connects a parameter to one of this node's inputs

        :param connection: The connection to add
        :type connection: :class:`recipe.definition.connection.InputConnection`

        :raises :class:`recipe.definition.exceptions.InvalidDefinition`: If the definition is invalid
        """

        try:
            if connection.input_name in self.connections:
                msg = 'Input \'%s\' has more than one parameter connected to it' % connection.input_name
                raise InvalidInterfaceConnection('DUPLICATE_INPUT', msg)

            self.connections[connection.input_name] = connection
        except InvalidInterfaceConnection as ex:
            msg = 'Node \'%s\' interface error: %s' % (self.name, ex.error.description)
            raise InvalidDefinition('NODE_INTERFACE', msg)
示例#12
0
    def add_recipe_input_connection(self, node_name, node_input_name, recipe_input_name):
        """Adds a connection from a recipe input to the input of a node

        :param node_name: The name of the node whose input is being connected to
        :type node_name: string
        :param node_input_name: The name of the node's input
        :type node_input_name: string
        :param recipe_input_name: The name of the recipe input being connected to the node's input
        :type recipe_input_name: string

        :raises :class:`recipe.definition.exceptions.InvalidDefinition`: If the node or recipe input is unknown or the
            connection is a duplicate
        """

        if recipe_input_name not in self.input_interface.parameters:
            raise InvalidDefinition('UNKNOWN_INPUT', 'Recipe input \'%s\' is not defined' % recipe_input_name)

        connection = RecipeInputConnection(node_input_name, recipe_input_name)
        self._add_connection(node_name, connection)