def add_parameter_from_output_interface(self, input_name, output_name, output_interface): """Adds an output parameter from the given output interface to this interface with the given input name. This is used to create a connecting interface that can be validated for passing to another interface. :param input_name: The name of the input parameter to add :type input_name: string :param output_name: The name of the output parameter in the output interface :type output_name: string :param output_interface: The output interface :type output_interface: :class:`data.interface.interface.Interface` :returns: A list of warnings discovered during validation :rtype: :func:`list` :raises :class:`data.interface.exceptions.InvalidInterfaceConnection`: If the interface connection is invalid """ if input_name in self.parameters: msg = 'Input \'%s\' has more than one parameter connected to it' % input_name raise InvalidInterfaceConnection('DUPLICATE_INPUT', msg) try: new_param = output_interface.parameters[output_name].copy() except KeyError: msg = 'Input %s cannot be connected to output %s; no output exists with that name' % ( input_name, output_name) raise InvalidInterfaceConnection('MISSING_OUTPUT_NAME', msg) new_param.name = input_name self.add_parameter(new_param) return []
def validate_connection(self, connecting_interface): """Validates that the given connecting interface can be accepted by this interface :param connecting_interface: The interface attempting to connect to this interface :type connecting_interface: :class:`data.interface.interface.Interface` :returns: A list of warnings discovered during validation :rtype: :func:`list` :raises :class:`data.interface.exceptions.InvalidInterfaceConnection`: If the interface connection is invalid """ warnings = [] for parameter in self.parameters.values(): if parameter.name in connecting_interface.parameters: connecting_parameter = connecting_interface.parameters[ parameter.name] warnings.extend( parameter.validate_connection(connecting_parameter)) elif parameter.required: raise InvalidInterfaceConnection( 'PARAM_REQUIRED', 'Parameter \'%s\' is required' % parameter.name) return warnings
def test_validate_connection(self): """Tests calling Interface.validate_connection()""" interface = Interface() connecting_interface = Interface() file_param = FileParameter('input_1', ['application/json']) interface.add_parameter(file_param) connecting_interface.add_parameter(file_param) json_param = JsonParameter('input_2', 'integer') interface.add_parameter(json_param) connecting_interface.add_parameter(json_param) # Valid connection interface.validate_connection(connecting_interface) new_file_param = FileParameter('input_3', ['image/gif'], required=True) interface.add_parameter(new_file_param) # Connection is missing required input 3 with self.assertRaises(InvalidInterfaceConnection) as context: interface.validate_connection(connecting_interface) self.assertEqual(context.exception.error.name, 'PARAM_REQUIRED') connecting_interface.add_parameter(new_file_param) mock_param = MagicMock() mock_param.name = 'input_4' mock_param.validate_connection.side_effect = InvalidInterfaceConnection('MOCK', '') interface.add_parameter(mock_param) connecting_interface.add_parameter(mock_param) # Invalid connection with self.assertRaises(InvalidInterfaceConnection) as context: interface.validate_connection(connecting_interface) self.assertEqual(context.exception.error.name, 'MOCK')
def validate(self, all_dependencies): """See :meth:`recipe.handlers.connection.InputConnection.validate` """ # Check that the connection's dependency is met if self.node_name not in all_dependencies: msg = 'Cannot get output \'%s\' without dependency on node \'%s\'' raise InvalidInterfaceConnection( 'MISSING_DEPENDENCY', msg % (self.output_name, self.node_name)) return []
def validate_connection(self, connecting_parameter): """Validates that the given connecting parameter can be accepted by this parameter :param connecting_parameter: The parameter attempting to connect to this parameter :type connecting_parameter: :class:`data.interface.parameter.Parameter` :returns: A list of warnings discovered during validation :rtype: :func:`list` :raises :class:`data.interface.exceptions.InvalidInterfaceConnection`: If the interface connection is invalid """ if self.param_type != connecting_parameter.param_type: msg = 'Parameter \'%s\' of type \'%s\' cannot accept type \'%s\'' msg = msg % (self.name, self.param_type, connecting_parameter.param_type) raise InvalidInterfaceConnection('MISMATCHED_PARAM_TYPE', msg) if self.required and not connecting_parameter.required: msg = 'Parameter \'%s\' is required and cannot accept an optional value' % self.name raise InvalidInterfaceConnection('PARAM_REQUIRED', msg) return []
def validate_connection(self, connecting_parameter): """See :meth:`data.interface.parameter.Parameter.validate_connection` """ warnings = super(JsonParameter, self).validate_connection(connecting_parameter) if self.json_type != connecting_parameter.json_type: msg = 'Parameter \'%s\' of JSON type \'%s\' cannot accept JSON type \'%s\'' msg = msg % (self.name, self.json_type, connecting_parameter.json_type) raise InvalidInterfaceConnection('MISMATCHED_JSON_TYPE', msg) return warnings
def test_validate_invalid_connection(self): """Tests calling RecipeDefinition.validate() with an invalid connection to a node's input interface""" input_interface = Interface() definition = RecipeDefinition(input_interface) definition.add_job_node('A', 'job_type_1', '1.0', 1) definition.add_recipe_node('B', 'recipe_type_1', 1) definition.add_dependency('A', 'B') definition.add_dependency_input_connection('B', 'input_1', 'A', 'output_1') mocked_interfaces = {'A': MagicMock(), 'B': MagicMock()} mocked_interfaces[ 'B'].validate_connection.side_effect = InvalidInterfaceConnection( '', '') with self.assertRaises(InvalidDefinition) as context: definition.validate(mocked_interfaces, mocked_interfaces) self.assertEqual(context.exception.error.name, 'NODE_INTERFACE')
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)
def validate_connection(self, connecting_parameter): """See :meth:`data.interface.parameter.Parameter.validate_connection` """ warnings = super(FileParameter, self).validate_connection(connecting_parameter) if not self.multiple and connecting_parameter.multiple: msg = 'Parameter \'%s\' cannot accept multiple files' % self.name raise InvalidInterfaceConnection('NO_MULTIPLE_FILES', msg) mismatched_media_types = [] for media_type in connecting_parameter.media_types: if media_type not in self.media_types: mismatched_media_types.append(media_type) if mismatched_media_types: msg = 'Parameter \'%s\' might not accept [%s]' % (self.name, ', '.join(mismatched_media_types)) warnings.append(ValidationWarning('MISMATCHED_MEDIA_TYPES', msg)) return warnings