| # |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You under the Apache License, Version 2.0 |
| # (the "License"); you may not use this file except in compliance with |
| # the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| import logging |
| import unittest |
| |
| import yaml |
| |
| import apache_beam as beam |
| from apache_beam.yaml import YamlTransform |
| from apache_beam.yaml import yaml_provider |
| from apache_beam.yaml.yaml_provider import InlineProvider |
| from apache_beam.yaml.yaml_transform import SafeLineLoader |
| from apache_beam.yaml.yaml_transform import Scope |
| from apache_beam.yaml.yaml_transform import chain_as_composite |
| from apache_beam.yaml.yaml_transform import ensure_errors_consumed |
| from apache_beam.yaml.yaml_transform import ensure_transforms_have_types |
| from apache_beam.yaml.yaml_transform import expand_composite_transform |
| from apache_beam.yaml.yaml_transform import extract_name |
| from apache_beam.yaml.yaml_transform import identify_object |
| from apache_beam.yaml.yaml_transform import normalize_inputs_outputs |
| from apache_beam.yaml.yaml_transform import normalize_source_sink |
| from apache_beam.yaml.yaml_transform import only_element |
| from apache_beam.yaml.yaml_transform import pipeline_as_composite |
| from apache_beam.yaml.yaml_transform import preprocess |
| from apache_beam.yaml.yaml_transform import preprocess_flattened_inputs |
| from apache_beam.yaml.yaml_transform import preprocess_windowing |
| from apache_beam.yaml.yaml_transform import push_windowing_to_roots |
| |
| |
| class SafeLineLoaderTest(unittest.TestCase): |
| def test_get_line(self): |
| pipeline_yaml = ''' |
| type: composite |
| input: |
| elements: input |
| transforms: |
| - type: PyMap |
| name: Square |
| input: elements |
| config: |
| fn: "lambda x: x * x" |
| - type: PyMap |
| name: Cube |
| input: elements |
| config: |
| fn: "lambda x: x * x * x" |
| output: |
| Flatten |
| ''' |
| spec = yaml.load(pipeline_yaml, Loader=SafeLineLoader) |
| self.assertEqual(SafeLineLoader.get_line(spec['type']), 2) |
| self.assertEqual(SafeLineLoader.get_line(spec['input']), 4) |
| self.assertEqual(SafeLineLoader.get_line(spec['transforms'][0]), 6) |
| self.assertEqual(SafeLineLoader.get_line(spec['transforms'][0]['type']), 6) |
| self.assertEqual(SafeLineLoader.get_line(spec['transforms'][0]['name']), 7) |
| self.assertEqual(SafeLineLoader.get_line(spec['transforms'][1]), 11) |
| self.assertEqual(SafeLineLoader.get_line(spec['output']), 17) |
| self.assertEqual(SafeLineLoader.get_line(spec['transforms']), "unknown") |
| |
| def test_strip_metadata(self): |
| spec_yaml = ''' |
| transforms: |
| - type: PyMap |
| name: Square |
| ''' |
| spec = yaml.load(spec_yaml, Loader=SafeLineLoader) |
| stripped = SafeLineLoader.strip_metadata(spec['transforms']) |
| |
| self.assertFalse(hasattr(stripped[0], '__line__')) |
| self.assertFalse(hasattr(stripped[0], '__uuid__')) |
| |
| def test_strip_metadata_nothing_to_strip(self): |
| spec_yaml = 'prop: 123' |
| spec = yaml.load(spec_yaml, Loader=SafeLineLoader) |
| stripped = SafeLineLoader.strip_metadata(spec['prop']) |
| |
| self.assertFalse(hasattr(stripped, '__line__')) |
| self.assertFalse(hasattr(stripped, '__uuid__')) |
| |
| |
| def new_pipeline(): |
| return beam.Pipeline( |
| options=beam.options.pipeline_options.PipelineOptions( |
| pickle_library='cloudpickle')) |
| |
| |
| class MainTest(unittest.TestCase): |
| def assertYaml(self, expected, result): |
| result = SafeLineLoader.strip_metadata(result) |
| expected = yaml.load(expected, Loader=SafeLineLoader) |
| expected = SafeLineLoader.strip_metadata(expected) |
| |
| self.assertEqual(expected, result) |
| |
| def get_scope_by_spec(self, p, spec, inputs=None): |
| if inputs is None: |
| inputs = {} |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| |
| scope = Scope( |
| beam.pvalue.PBegin(p), |
| inputs, |
| spec['transforms'], |
| yaml_provider.standard_providers(), {}) |
| return scope, spec |
| |
| def test_pipeline_as_composite_with_type_transforms(self): |
| spec = ''' |
| type: composite |
| transforms: |
| - type: Create |
| config: |
| elements: [0,1,2] |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| result = pipeline_as_composite(spec) |
| |
| self.assertEqual(result['type'], 'composite') |
| self.assertEqual(result['name'], None) |
| |
| def test_pipeline_as_composite_with_transforms(self): |
| spec = ''' |
| transforms: |
| - type: Create |
| config: |
| elements: [0,1,2] |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| result = pipeline_as_composite(spec) |
| |
| self.assertEqual(result['type'], 'composite') |
| self.assertEqual(result['name'], None) |
| |
| def test_pipeline_as_composite_list(self): |
| spec = ''' |
| - type: Create |
| config: |
| elements: [0,1,2] |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| result = pipeline_as_composite(spec) |
| |
| expected = ''' |
| type: composite |
| name: null |
| transforms: |
| - type: Create |
| config: |
| elements: [0,1,2] |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| ''' |
| self.assertYaml(expected, result) |
| |
| def test_expand_composite_transform_with_name(self): |
| with new_pipeline() as p: |
| spec = ''' |
| type: composite |
| name: Custom |
| transforms: |
| - type: Create |
| config: |
| elements: [0,1,2] |
| output: |
| Create |
| ''' |
| scope, spec = self.get_scope_by_spec(p, spec) |
| self.assertRegex( |
| str(expand_composite_transform(spec, scope)['output']), |
| r"PCollection.*Custom/Create/Map.*") |
| |
| def test_expand_composite_transform_with_name_input(self): |
| with new_pipeline() as p: |
| spec = ''' |
| type: composite |
| input: elements |
| transforms: |
| - type: LogForTesting |
| input: input |
| output: |
| LogForTesting |
| ''' |
| elements = p | beam.Create(range(3)) |
| scope, spec = self.get_scope_by_spec(p, spec, |
| inputs={'elements': elements}) |
| self.assertRegex( |
| str(expand_composite_transform(spec, scope)['output']), |
| r"PCollection.*Composite/Map.*") |
| |
| def test_expand_composite_transform_root(self): |
| with new_pipeline() as p: |
| spec = ''' |
| type: composite |
| transforms: |
| - type: Create |
| config: |
| elements: [0,1,2] |
| output: |
| Create |
| ''' |
| scope, spec = self.get_scope_by_spec(p, spec) |
| self.assertRegex( |
| str(expand_composite_transform(spec, scope)['output']), |
| r"PCollection.*Create/Map.*") |
| |
| def test_chain_as_composite(self): |
| spec = ''' |
| type: chain |
| transforms: |
| - type: Create |
| config: |
| elements: [0,1,2] |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| result = chain_as_composite(spec) |
| |
| expected = f''' |
| type: composite |
| name: Chain |
| transforms: |
| - type: Create |
| config: |
| elements: [0,1,2] |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| input: {spec['transforms'][0]['__uuid__']} |
| output: {spec['transforms'][1]['__uuid__']} |
| ''' |
| self.assertYaml(expected, result) |
| |
| def test_chain_as_composite_with_wrong_output_type(self): |
| spec = ''' |
| type: chain |
| transforms: |
| - type: Create |
| elements: [0,1,2] |
| - type: PyMap |
| fn: 'lambda x: x*x' |
| output: |
| Create |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| with self.assertRaisesRegex(ValueError, |
| r"Explicit output.*of the chain transform is " |
| r"not an output of the last transform"): |
| chain_as_composite(spec) |
| |
| def test_chain_as_composite_with_wrong_output_name(self): |
| spec = ''' |
| type: chain |
| transforms: |
| - type: Create |
| name: elements |
| elements: [0,1,2] |
| - type: PyMap |
| fn: 'lambda x: x*x' |
| output: |
| elements |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| with self.assertRaisesRegex(ValueError, |
| r"Explicit output.*of the chain transform is " |
| r"not an output of the last transform"): |
| chain_as_composite(spec) |
| |
| def test_chain_as_composite_with_outputs_override(self): |
| spec = ''' |
| type: chain |
| transforms: |
| - type: Create |
| elements: [0,1,2] |
| - type: PyMap |
| fn: 'lambda x: x*x' |
| output: |
| PyMap |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| self.assertEqual( |
| chain_as_composite(spec)['output']['output'], |
| f"{spec['transforms'][1]['__uuid__']}.PyMap") |
| |
| def test_chain_as_composite_with_input(self): |
| spec = ''' |
| type: chain |
| input: |
| elements |
| transforms: |
| - type: PyMap |
| fn: 'lambda x: x*x' |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| self.assertEqual( |
| chain_as_composite(spec)['transforms'][0]['input'], {"input": "input"}) |
| |
| def test_normalize_source_sink(self): |
| spec = ''' |
| source: |
| type: Create |
| config: |
| elements: [0,1,2] |
| transforms: |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| sink: |
| type: PyMap |
| config: |
| fn: "lambda x: x + 41" |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| result = normalize_source_sink(spec) |
| |
| expected = ''' |
| transforms: |
| - type: Create |
| input: {'__explicitly_empty__': null} |
| config: |
| elements: [0,1,2] |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| - type: PyMap |
| config: |
| fn: "lambda x: x + 41" |
| ''' |
| self.assertYaml(expected, result) |
| |
| def test_normalize_source_sink_only_source(self): |
| spec = ''' |
| source: |
| type: Create |
| config: |
| elements: [0,1,2] |
| transforms: |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| result = normalize_source_sink(spec) |
| |
| expected = ''' |
| transforms: |
| - type: Create |
| input: {'__explicitly_empty__': null} |
| config: |
| elements: [0,1,2] |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| ''' |
| self.assertYaml(expected, result) |
| |
| def test_normalize_source_sink_only_sink(self): |
| spec = ''' |
| transforms: |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| sink: |
| type: PyMap |
| config: |
| fn: "lambda x: x + 41" |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| result = normalize_source_sink(spec) |
| expected = ''' |
| transforms: |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| - type: PyMap |
| config: |
| fn: "lambda x: x + 41" |
| ''' |
| self.assertYaml(expected, result) |
| |
| def test_normalize_source_sink_no_source_no_sink(self): |
| spec = ''' |
| transforms: |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| result = normalize_source_sink(spec) |
| |
| expected = ''' |
| transforms: |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| ''' |
| self.assertYaml(expected, result) |
| |
| def test_preprocess_source_sink_composite(self): |
| spec = ''' |
| type: composite |
| source: |
| type: Create |
| config: |
| elements: [0,1,2] |
| transforms: |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| result = normalize_source_sink(spec) |
| |
| expected = ''' |
| type: composite |
| transforms: |
| - type: Create |
| input: {'__explicitly_empty__': null} |
| config: |
| elements: [0,1,2] |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| ''' |
| self.assertYaml(expected, result) |
| |
| def test_preprocess_source_sink_chain(self): |
| spec = ''' |
| type: chain |
| source: |
| type: Create |
| config: |
| elements: [0,1,2] |
| transforms: |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| result = normalize_source_sink(spec) |
| |
| expected = ''' |
| type: chain |
| transforms: |
| - type: Create |
| input: {'__explicitly_empty__': null} |
| config: |
| elements: [0,1,2] |
| - type: PyMap |
| config: |
| fn: 'lambda x: x*x' |
| ''' |
| self.assertYaml(expected, result) |
| |
| def test_preprocess_source_sink_other(self): |
| spec = ''' |
| - type: PyMap |
| fn: 'lambda x: x*x' |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| self.assertEqual(normalize_source_sink(spec), spec) |
| |
| def test_normalize_inputs_outputs(self): |
| spec = ''' |
| type: PyMap |
| input: [Create1, Create2] |
| fn: 'lambda x: x*x' |
| output: Squared |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| result = normalize_inputs_outputs(spec) |
| |
| expected = ''' |
| type: PyMap |
| input: |
| input: [Create1, Create2] |
| fn: 'lambda x: x*x' |
| output: |
| output: Squared |
| ''' |
| self.assertYaml(expected, result) |
| |
| def test_normalize_inputs_outputs_dict(self): |
| spec = ''' |
| type: PyMap |
| input: [Create1, Create2] |
| fn: 'lambda x: x*x' |
| output: |
| out1: Squared1 |
| out2: Squared2 |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| result = normalize_inputs_outputs(spec) |
| |
| expected = ''' |
| type: PyMap |
| input: |
| input: [Create1, Create2] |
| fn: 'lambda x: x*x' |
| output: |
| out1: Squared1 |
| out2: Squared2 |
| ''' |
| self.assertYaml(expected, result) |
| |
| def test_identify_object_with_name(self): |
| spec = ''' |
| type: PyMap |
| fn: 'lambda x: x*x' |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| self.assertRegex(identify_object(spec), r"PyMap.*[0-9]") |
| |
| def test_identify_object(self): |
| spec = ''' |
| argument: PyMap |
| fn: 'lambda x: x*x' |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| self.assertRegex(identify_object(spec), r"at.*[0-9]") |
| |
| def test_extract_name_by_type(self): |
| spec = ''' |
| type: PyMap |
| fn: 'lambda x: x*x' |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| self.assertEqual(extract_name(spec), "PyMap") |
| |
| def test_extract_name_by_id(self): |
| spec = ''' |
| type: PyMap |
| id: PyMapId |
| fn: 'lambda x: x*x' |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| self.assertEqual(extract_name(spec), "PyMapId") |
| |
| def test_extract_name_by_name(self): |
| spec = ''' |
| type: PyMap |
| name: PyMapName |
| fn: 'lambda x: x*x' |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| self.assertEqual(extract_name(spec), "PyMapName") |
| |
| def test_extract_name_no_name(self): |
| spec = ''' |
| transforms: |
| - arg: PyMap |
| fn: 'lambda x: x*x' |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| self.assertEqual(extract_name(spec), "") |
| |
| def test_push_windowing_to_roots(self): |
| spec = ''' |
| type: composite |
| transforms: |
| - type: Create |
| elements: [0,1,2] |
| - type: PyMap |
| fn: 'lambda x: x*x' |
| input: Create |
| windowing: |
| type: fixed |
| size: 2 |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| spec = normalize_inputs_outputs(spec) |
| spec['transforms'] = [ |
| normalize_inputs_outputs(t) for t in spec['transforms'] |
| ] |
| |
| result = push_windowing_to_roots(spec) |
| |
| expected = ''' |
| type: composite |
| transforms: |
| - type: Create |
| elements: [0,1,2] |
| windowing: |
| type: fixed |
| size: 2 |
| __consumed_outputs: |
| - null |
| input: {} |
| output: {} |
| - type: PyMap |
| fn: 'lambda x: x*x' |
| input: |
| input: Create |
| output: {} |
| windowing: |
| type: fixed |
| size: 2 |
| input: {} |
| output: {} |
| ''' |
| self.assertYaml(expected, result) |
| |
| def test_preprocess_windowing_custom_type(self): |
| spec = ''' |
| type: SumGlobally |
| input: Create |
| windowing: |
| type: fixed |
| size: 4 |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| spec = normalize_inputs_outputs(spec) |
| result = preprocess_windowing(spec) |
| |
| expected = f''' |
| type: composite |
| name: SumGlobally |
| input: |
| input: Create |
| transforms: |
| - type: SumGlobally |
| input: |
| input: {result['transforms'][1]['__uuid__']} |
| output: {{}} |
| - type: WindowInto |
| name: WindowInto[input] |
| windowing: |
| type: fixed |
| size: 4 |
| input: input |
| output: {result['transforms'][0]['__uuid__']} |
| ''' |
| self.assertYaml(expected, result) |
| |
| def test_preprocess_windowing_composite_with_windowing_outer(self): |
| spec = ''' |
| type: composite |
| transforms: |
| - type: CreateTimestamped |
| name: Create |
| elements: [0, 2, 4] |
| - type: SumGlobally |
| input: Create |
| windowing: |
| type: fixed |
| size: 4 |
| output: SumGlobally |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| spec = normalize_inputs_outputs(spec) |
| spec['transforms'] = [ |
| normalize_inputs_outputs(t) for t in spec['transforms'] |
| ] |
| |
| result = preprocess_windowing(spec) |
| |
| expected = ''' |
| type: composite |
| input: {} |
| transforms: |
| - type: CreateTimestamped |
| name: Create |
| elements: [0, 2, 4] |
| windowing: |
| type: fixed |
| size: 4 |
| __consumed_outputs: |
| - null |
| input: {} |
| output: {} |
| - type: SumGlobally |
| input: |
| input: Create |
| output: {} |
| output: |
| output: SumGlobally |
| ''' |
| self.assertYaml(expected, result) |
| |
| def test_preprocess_windowing_composite_with_windowing_on_input(self): |
| spec = ''' |
| type: composite |
| transforms: |
| - type: CreateTimestamped |
| name: Create |
| elements: [0, 2, 4] |
| - type: SumGlobally |
| input: Create |
| windowing: |
| type: fixed |
| size: 4 |
| output: SumGlobally |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| spec = normalize_inputs_outputs(spec) |
| spec['transforms'] = [ |
| normalize_inputs_outputs(t) for t in spec['transforms'] |
| ] |
| |
| result = preprocess_windowing(spec) |
| |
| expected = ''' |
| type: composite |
| input: {} |
| transforms: |
| - type: CreateTimestamped |
| name: Create |
| elements: [0, 2, 4] |
| input: {} |
| output: {} |
| - type: SumGlobally |
| input: |
| input: Create |
| windowing: |
| type: fixed |
| size: 4 |
| output: {} |
| output: |
| output: SumGlobally |
| ''' |
| self.assertYaml(expected, result) |
| |
| def test_preprocess_windowing_other_type_with_no_inputs(self): |
| spec = ''' |
| type: CreateTimestamped |
| name: Create |
| elements: [0, 2, 4] |
| windowing: |
| type: fixed |
| size: 4 |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| spec = normalize_inputs_outputs(spec) |
| result = preprocess_windowing(spec) |
| |
| expected = f''' |
| type: composite |
| name: Create |
| transforms: |
| - type: CreateTimestamped |
| name: Create |
| elements: [0, 2, 4] |
| input: {{}} |
| output: {{}} |
| - type: WindowInto |
| name: WindowInto[None] |
| input: |
| input: {result['transforms'][0]["__uuid__"]} |
| windowing: |
| type: fixed |
| size: 4 |
| output: {result['transforms'][1]["__uuid__"]} |
| ''' |
| self.maxDiff = 1e9 |
| |
| self.assertYaml(expected, result) |
| |
| def test_preprocess_flattened_inputs_implicit(self): |
| spec = ''' |
| type: composite |
| transforms: |
| - type: PyMap |
| fn: 'lambda x: x*x' |
| input: [Create1, Create2] |
| output: CreateTimestamped |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| spec['transforms'] = [ |
| normalize_inputs_outputs(t) for t in spec['transforms'] |
| ] |
| result = preprocess_flattened_inputs(spec) |
| |
| expected = f''' |
| type: composite |
| transforms: |
| - type: Flatten |
| name: PyMap-Flatten[input] |
| input: |
| input0: Create1 |
| input1: Create2 |
| - type: PyMap |
| fn: 'lambda x: x*x' |
| input: |
| input: {result['transforms'][0]['__uuid__']} |
| output: {{}} |
| output: CreateTimestamped |
| ''' |
| self.assertYaml(expected, result) |
| |
| def test_preprocess_flattened_inputs_explicit_flatten(self): |
| spec = ''' |
| type: composite |
| transforms: |
| - type: Flatten |
| input: [Create1, Create2] |
| - type: PyMap |
| fn: 'lambda x: x*x' |
| input: Flatten |
| output: CreateTimestamped |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| spec['transforms'] = [ |
| normalize_inputs_outputs(t) for t in spec['transforms'] |
| ] |
| result = preprocess_flattened_inputs(spec) |
| |
| expected = ''' |
| type: composite |
| transforms: |
| - type: Flatten |
| input: |
| input0: Create1 |
| input1: Create2 |
| output: {} |
| - type: PyMap |
| fn: 'lambda x: x*x' |
| input: |
| input: Flatten |
| output: {} |
| output: CreateTimestamped |
| ''' |
| self.assertYaml(expected, result) |
| |
| def test_ensure_transforms_have_types(self): |
| spec = ''' |
| type: PyMap |
| fn: 'lambda x: x*x' |
| input: Flatten |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| result = ensure_transforms_have_types(spec) |
| self.assertEqual(result, spec) |
| |
| def test_ensure_transforms_have_types_error(self): |
| spec = ''' |
| name: PyMap |
| fn: 'lambda x: x*x' |
| input: Flatten |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| with self.assertRaisesRegex(ValueError, r"Missing type .*"): |
| ensure_transforms_have_types(spec) |
| with self.assertRaisesRegex(ValueError, r"Missing type .*"): |
| preprocess(spec) |
| |
| def test_ensure_transforms_have_providers_error(self): |
| spec = ''' |
| type: UnknownType |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| with self.assertRaisesRegex(ValueError, |
| r"Unknown type or missing provider .*"): |
| preprocess(spec, known_transforms=['KnownType']) |
| |
| def test_ensure_errors_consumed_unconsumed(self): |
| spec = ''' |
| type: composite |
| transforms: |
| - type: Create |
| elements: [1,2,3] |
| - type: MyTransform |
| input: Create |
| error_handling: |
| output: errors |
| output: |
| good: MyTransform |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| spec = normalize_inputs_outputs(spec) |
| spec['transforms'] = [ |
| normalize_inputs_outputs(t) for t in spec['transforms'] |
| ] |
| with self.assertRaisesRegex(ValueError, r"Unconsumed error.*"): |
| ensure_errors_consumed(spec) |
| |
| def test_ensure_errors_consumed_in_transform(self): |
| spec = ''' |
| type: composite |
| transforms: |
| - type: Create |
| elements: [1,2,3] |
| - type: MyTransform |
| input: Create |
| error_handling: |
| output: errors |
| - name: SaveToFile |
| type: PyMap |
| input: MyTransform.errors |
| output: |
| good: MyTransform |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| spec = normalize_inputs_outputs(spec) |
| spec['transforms'] = [ |
| normalize_inputs_outputs(t) for t in spec['transforms'] |
| ] |
| result = ensure_errors_consumed(spec) |
| self.assertEqual(result, spec) |
| self.assertEqual(result['transforms'], spec['transforms']) |
| |
| def test_ensure_errors_consumed_no_output_in_error_handling(self): |
| spec = ''' |
| type: composite |
| transforms: |
| - type: Create |
| elements: [1,2,3] |
| - type: MyTransform |
| input: Create |
| error_handling: |
| arg: errors |
| - name: SaveToFile |
| type: PyMap |
| input: MyTransform.errors |
| output: |
| good: MyTransform |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| spec = normalize_inputs_outputs(spec) |
| spec['transforms'] = [ |
| normalize_inputs_outputs(t) for t in spec['transforms'] |
| ] |
| with self.assertRaisesRegex(ValueError, r"Missing output.*"): |
| ensure_errors_consumed(spec) |
| |
| def test_only_element(self): |
| self.assertEqual(only_element((1, )), 1) |
| |
| |
| class YamlTransformTest(unittest.TestCase): |
| def test_init_with_string(self): |
| provider1 = InlineProvider({"MyTransform1": lambda: beam.Map(lambda x: x)}) |
| provider2 = InlineProvider({"MyTransform2": lambda: beam.Map(lambda x: x)}) |
| |
| providers_dict = {"p1": [provider1], "p2": [provider2]} |
| |
| spec = ''' |
| type: chain |
| transforms: |
| - type: Create |
| elements: [1,2,3] |
| - type: LogForTesting |
| ''' |
| result = YamlTransform(spec, providers_dict) |
| self.assertIn('p1', result._providers) # check for custom providers |
| self.assertIn('p2', result._providers) # check for custom providers |
| self.assertIn( |
| 'LogForTesting', result._providers) # check for standard provider |
| self.assertEqual(result._spec['type'], "composite") # preprocessed spec |
| |
| def test_init_with_dict(self): |
| spec = ''' |
| type: chain |
| transforms: |
| - type: Create |
| config: |
| elements: [1,2,3] |
| - type: LogForTesting |
| ''' |
| spec = yaml.load(spec, Loader=SafeLineLoader) |
| result = YamlTransform(spec) |
| self.assertIn( |
| 'LogForTesting', result._providers) # check for standard provider |
| self.assertEqual(result._spec['type'], "composite") # preprocessed spec |
| |
| |
| if __name__ == '__main__': |
| logging.getLogger().setLevel(logging.INFO) |
| unittest.main() |