def test_packages_to_install_feature(self): task_factory = comp.func_to_container_op(dummy_in_0_out_0, packages_to_install=['six', 'pip']) self.helper_test_component_using_local_call(task_factory, arguments={}, expected_output_values={}) task_factory2 = comp.func_to_container_op(dummy_in_0_out_0, packages_to_install=['bad-package-0ee7cf93f396cd5072603dec154425cd53bf1c681c7c7605c60f8faf7799b901']) with self.assertRaises(Exception): self.helper_test_component_using_local_call(task_factory2, arguments={}, expected_output_values={})
def test_func_to_container_op_output_component_file(self): func = add_two_numbers with tempfile.TemporaryDirectory() as temp_dir_name: component_path = str(Path(temp_dir_name) / 'component.yaml') comp.func_to_container_op(func, output_component_file=component_path) op = comp.load_component_from_file(component_path) self.helper_test_2_in_1_out_component_using_local_call(func, op)
def test_handling_default_value_of_none(self): def assert_is_none(arg=None): assert arg is None func = assert_is_none op = comp.func_to_container_op(func) self.helper_test_component_using_local_call(op)
def test_end_to_end_python_component_pipeline(self): #Defining the Python function def add(a: float, b: float) -> float: '''Returns sum of two arguments''' return a + b with tempfile.TemporaryDirectory() as temp_dir_name: add_component_file = str(Path(temp_dir_name).joinpath('add.component.yaml')) #Converting the function to a component. Instantiate it to create a pipeline task (ContaineOp instance) add_op = comp.func_to_container_op(add, base_image='python:3.5', output_component_file=add_component_file) #Checking that the component artifact is usable: add_op2 = comp.load_component_from_file(add_component_file) #Building the pipeline def calc_pipeline( a1, a2='7', a3='17', ): task_1 = add_op(a1, a2) task_2 = add_op2(a1, a2) task_3 = add_op(task_1.outputs['Output'], task_2.outputs['Output']) task_4 = add_op2(task_3.outputs['Output'], a3) #Instantiating the pipleine: calc_pipeline(42)
def test_legacy_python_component_name_description_overrides(self): # Deprecated feature expected_name = 'Sum component name' expected_description = 'Sum component description' expected_image = 'org/image' def add_two_numbers_decorated( a: float, b: float, ) -> float: '''Returns sum of two arguments''' return a + b # Deprecated features add_two_numbers_decorated._component_human_name = expected_name add_two_numbers_decorated._component_description = expected_description add_two_numbers_decorated._component_base_image = expected_image func = add_two_numbers_decorated op = comp.func_to_container_op(func) component_spec = op.component_spec self.assertEqual(component_spec.name, expected_name) self.assertEqual(component_spec.description.strip(), expected_description.strip()) self.assertEqual(component_spec.implementation.container.image, expected_image)
def test_func_to_container_op_multiple_named_typed_outputs_using_list_syntax(self): def add_multiply_two_numbers(a: float, b: float) -> [('sum', float), ('product', float)]: '''Returns sum and product of two arguments''' return (a + b, a * b) func = add_multiply_two_numbers op = comp.func_to_container_op(func) self.helper_test_2_in_2_out_component_using_local_call(func, op, output_names=['sum', 'product'])
def test_handling_same_input_default_output_names(self): def add_two_numbers_indented(a: float, Output: float) -> float: '''Returns sum of two arguments''' return a + Output func = add_two_numbers_indented op = comp.func_to_container_op(func) self.helper_test_2_in_1_out_component_using_local_call(func, op)
def test_func_to_container_op_with_imported_func2(self): from .test_data.module2_which_depends_on_module1 import module2_func_with_deps as module2_func_with_deps func = module2_func_with_deps op = comp.func_to_container_op(func, use_code_pickling=True, modules_to_capture=[ 'tests.components.test_data.module1', 'tests.components.test_data.module2_which_depends_on_module1' ]) self.helper_test_2_in_1_out_component_using_local_call(func, op)
def test_indented_func_to_container_op_local_call(self): def add_two_numbers_indented(a: float, b: float) -> float: '''Returns sum of two arguments''' return a + b func = add_two_numbers_indented op = comp.func_to_container_op(func) self.helper_test_2_in_1_out_component_using_local_call(func, op)
def test_handling_same_input_output_names(self): from typing import NamedTuple def add_multiply_two_numbers(a: float, b: float) -> NamedTuple('DummyName', [('a', float), ('b', float)]): '''Returns sum and product of two arguments''' return (a + b, a * b) func = add_multiply_two_numbers op = comp.func_to_container_op(func) self.helper_test_2_in_2_out_component_using_local_call(func, op, output_names=['a', 'b'])
def test_func_to_container_op_named_typed_outputs_with_spaces(self): from typing import NamedTuple def add_two_numbers_name3(a: float, b: float) -> NamedTuple('DummyName', [('Output data', float)]): '''Returns sum of two arguments''' return (a + b,) func = add_two_numbers_name3 op = comp.func_to_container_op(func) self.helper_test_2_in_1_out_component_using_local_call(func, op)
def test_input_path(self): def consume_file_path(number_file_path: InputPath(int)) -> int: with open(number_file_path) as f: string_data = f.read() return int(string_data) task_factory = comp.func_to_container_op(consume_file_path) self.assertEqual(task_factory.component_spec.inputs[0].type, 'Integer') self.helper_test_component_using_local_call(task_factory, arguments={'number': "42"}, expected_output_values={'Output': '42'})
def test_input_binary_file(self): def consume_file_path(number_file: InputBinaryFile(int)) -> int: bytes_data = number_file.read() assert isinstance(bytes_data, bytes) return int(bytes_data) task_factory = comp.func_to_container_op(consume_file_path) self.assertEqual(task_factory.component_spec.inputs[0].type, 'Integer') self.helper_test_component_using_local_call(task_factory, arguments={'number': "42"}, expected_output_values={'Output': '42'})
def test_output_binary_file(self): def write_to_file_path(number_file: OutputBinaryFile(int)): number_file.write(b'42') task_factory = comp.func_to_container_op(write_to_file_path) self.assertFalse(task_factory.component_spec.inputs) self.assertEqual(len(task_factory.component_spec.outputs), 1) self.assertEqual(task_factory.component_spec.outputs[0].type, 'Integer') self.helper_test_component_using_local_call(task_factory, arguments={}, expected_output_values={'number': '42'})
def test_handling_list_dict_output_values(self): def produce_list() -> list: return ["string", 1, 2.2, True, False, None, [3, 4], {'s': 5}] # ! JSON map keys are always strings. Python converts all keys to strings without warnings task_factory = comp.func_to_container_op(produce_list) import json expected_output = json.dumps(["string", 1, 2.2, True, False, None, [3, 4], {'s': 5}]) self.helper_test_component_using_local_call(task_factory, arguments={}, expected_output_values={'Output': expected_output})
def test_output_path(self): def write_to_file_path(number_file_path: OutputPath(int)): with open(number_file_path, 'w') as f: f.write(str(42)) task_factory = comp.func_to_container_op(write_to_file_path) self.assertFalse(task_factory.component_spec.inputs) self.assertEqual(len(task_factory.component_spec.outputs), 1) self.assertEqual(task_factory.component_spec.outputs[0].type, 'Integer') self.helper_test_component_using_local_call(task_factory, arguments={}, expected_output_values={'number': '42'})
def test_handling_boolean_arguments(self): def assert_values_are_true_false( bool1 : bool, bool2 : bool, ) -> int: assert bool1 is True assert bool2 is False return 1 func = assert_values_are_true_false op = comp.func_to_container_op(func) self.helper_test_2_in_1_out_component_using_local_call(func, op, arguments=[True, False])
def test_handling_complex_default_values(self): def assert_values_are_default( singleton_param=None, function_param=ascii, dict_param: dict = {'b': [2, 3, 4]}, func_call_param='_'.join(['a', 'b', 'c']), ): assert singleton_param is None assert function_param is ascii assert dict_param == {'b': [2, 3, 4]} assert func_call_param == '_'.join(['a', 'b', 'c']) func = assert_values_are_default op = comp.func_to_container_op(func) self.helper_test_component_using_local_call(op)
def test_handling_list_dict_arguments(self): def assert_values_are_same( list_param: list, dict_param: dict, ) -> int: import unittest unittest.TestCase().assertEqual(list_param, ["string", 1, 2.2, True, False, None, [3, 4], {'s': 5}]) unittest.TestCase().assertEqual(dict_param, {'str': "string", 'int': 1, 'float': 2.2, 'false': False, 'true': True, 'none': None, 'list': [3, 4], 'dict': {'s': 4}}) return 1 # ! JSON map keys are always strings. Python converts all keys to strings without warnings func = assert_values_are_same op = comp.func_to_container_op(func) self.helper_test_2_in_1_out_component_using_local_call(func, op, arguments=[ ["string", 1, 2.2, True, False, None, [3, 4], {'s': 5}], {'str': "string", 'int': 1, 'float': 2.2, 'false': False, 'true': True, 'none': None, 'list': [3, 4], 'dict': {'s': 4}}, ])
def test_func_to_container_op_call_other_func(self): extra_variable = 10 class ExtraClass: def class_method(self, x): return x * extra_variable def extra_func(a: float) -> float: return a * 5 def main_func(a: float, b: float) -> float: return ExtraClass().class_method(a) + extra_func(b) func = main_func op = comp.func_to_container_op(func, use_code_pickling=True) self.helper_test_2_in_1_out_component_using_local_call(func, op)
def test_func_to_container_op_check_nothing_extra_captured(self): def f1(): pass def f2(): pass def main_func(a: float, b: float) -> float: f1() try: eval('f2()') except: return a + b raise AssertionError("f2 should not be captured, because it's not a dependency.") expected_func = lambda a, b: a + b op = comp.func_to_container_op(main_func, use_code_pickling=True) self.helper_test_2_in_1_out_component_using_local_call(expected_func, op)
def test_handling_base64_pickle_arguments(self): def assert_values_are_same( obj1: 'Base64Pickle', # noqa: F821 obj2: 'Base64Pickle', # noqa: F821 ) -> int: import unittest unittest.TestCase().assertEqual(obj1['self'], obj1) unittest.TestCase().assertEqual(obj2, open) return 1 func = assert_values_are_same op = comp.func_to_container_op(func) recursive_obj = {} recursive_obj['self'] = recursive_obj self.helper_test_2_in_1_out_component_using_local_call(func, op, arguments=[ recursive_obj, open, ])
def test_all_data_passing_ways(self): def write_to_file_path( file_input1_path: InputPath(str), file_input2_file: InputTextFile(str), file_output1_path: OutputPath(str), file_output2_file: OutputTextFile(str), value_input1: str = 'foo', value_input2: str = 'foo', ) -> NamedTuple( 'Outputs', [ ('return_output1', str), ('return_output2', str), ] ): with open(file_input1_path, 'r') as file_input1_file: with open(file_output1_path, 'w') as file_output1_file: file_output1_file.write(file_input1_file.read()) file_output2_file.write(file_input2_file.read()) return (value_input1, value_input2) task_factory = comp.func_to_container_op(write_to_file_path) self.assertEqual(set(input.name for input in task_factory.component_spec.inputs), {'file_input1', 'file_input2', 'value_input1', 'value_input2'}) self.assertEqual(set(output.name for output in task_factory.component_spec.outputs), {'file_output1', 'file_output2', 'return_output1', 'return_output2'}) self.helper_test_component_using_local_call( task_factory, arguments={ 'file_input1': 'file_input1_value', 'file_input2': 'file_input2_value', 'value_input1': 'value_input1_value', 'value_input2': 'value_input2_value', }, expected_output_values={ 'file_output1': 'file_input1_value', 'file_output2': 'file_input2_value', 'return_output1': 'value_input1_value', 'return_output2': 'value_input2_value', }, )
def helper_test_component_created_from_func_using_local_call(self, func: Callable, arguments: dict): task_factory = comp.func_to_container_op(func) self.helper_test_component_against_func_using_local_call(func, task_factory, arguments)
def test_func_to_container_op_call_other_func_global(self): func = module_func_with_deps op = comp.func_to_container_op(func, use_code_pickling=True) self.helper_test_2_in_1_out_component_using_local_call(func, op)
def test_file_input_name_conversion(self): # Checking the input name conversion rules for file inputs: # For InputPath, the "_path" suffix is removed # For Input*, the "_file" suffix is removed def consume_file_path( number: int, number_1a_path: str, number_1b_file: str, number_1c_file_path: str, number_1d_path_file: str, number_2a_path: InputPath(str), number_2b_file: InputPath(str), number_2c_file_path: InputPath(str), number_2d_path_file: InputPath(str), number_3a_path: InputTextFile(str), number_3b_file: InputTextFile(str), number_3c_file_path: InputTextFile(str), number_3d_path_file: InputTextFile(str), number_4a_path: InputBinaryFile(str), number_4b_file: InputBinaryFile(str), number_4c_file_path: InputBinaryFile(str), number_4d_path_file: InputBinaryFile(str), output_number_2a_path: OutputPath(str), output_number_2b_file: OutputPath(str), output_number_2c_file_path: OutputPath(str), output_number_2d_path_file: OutputPath(str), output_number_3a_path: OutputTextFile(str), output_number_3b_file: OutputTextFile(str), output_number_3c_file_path: OutputTextFile(str), output_number_3d_path_file: OutputTextFile(str), output_number_4a_path: OutputBinaryFile(str), output_number_4b_file: OutputBinaryFile(str), output_number_4c_file_path: OutputBinaryFile(str), output_number_4d_path_file: OutputBinaryFile(str), ): pass task_factory = comp.func_to_container_op(consume_file_path) actual_input_names = [input.name for input in task_factory.component_spec.inputs] actual_output_names = [output.name for output in task_factory.component_spec.outputs] self.assertEqual( [ 'number', 'number_1a_path', 'number_1b_file', 'number_1c_file_path', 'number_1d_path_file', 'number_2a', 'number_2b', 'number_2c', 'number_2d_path', 'number_3a_path', 'number_3b', 'number_3c_file_path', 'number_3d_path', 'number_4a_path', 'number_4b', 'number_4c_file_path', 'number_4d_path', ], actual_input_names ) self.assertEqual( [ 'output_number_2a', 'output_number_2b', 'output_number_2c', 'output_number_2d_path', 'output_number_3a_path', 'output_number_3b', 'output_number_3c_file_path', 'output_number_3d_path', 'output_number_4a_path', 'output_number_4b', 'output_number_4c_file_path', 'output_number_4d_path', ], actual_output_names )
def test_func_to_container_op_with_imported_func(self): from .test_data.module1 import module_func_with_deps as module1_func_with_deps func = module1_func_with_deps op = comp.func_to_container_op(func, use_code_pickling=True) self.helper_test_2_in_1_out_component_using_local_call(func, op)
def test_func_to_container_op_local_call(self): func = add_two_numbers op = comp.func_to_container_op(func) self.helper_test_2_in_1_out_component_using_local_call(func, op)