diff --git a/sdks/python/apache_beam/transforms/ptransform_test.py b/sdks/python/apache_beam/transforms/ptransform_test.py index a51d5cd83d26..2fdec14651f1 100644 --- a/sdks/python/apache_beam/transforms/ptransform_test.py +++ b/sdks/python/apache_beam/transforms/ptransform_test.py @@ -1495,17 +1495,17 @@ def test_filter_does_not_type_check_using_type_hints_decorator(self): def more_than_half(a): return a > 0.50 - # Func above was hinted to only take a float, yet an int will be passed. + # Func above was hinted to only take a float, yet a str will be passed. with self.assertRaises(typehints.TypeCheckError) as e: ( self.p - | 'Ints' >> beam.Create([1, 2, 3, 4]).with_output_types(int) + | 'Ints' >> beam.Create(['1', '2', '3', '4']).with_output_types(str) | 'Half' >> beam.Filter(more_than_half)) self.assertStartswith( e.exception.args[0], "Type hint violation for 'Half': " - "requires {} but got {} for a".format(float, int)) + "requires {} but got {} for a".format(float, str)) def test_filter_type_checks_using_type_hints_decorator(self): @with_input_types(b=int) diff --git a/sdks/python/apache_beam/typehints/typed_pipeline_test.py b/sdks/python/apache_beam/typehints/typed_pipeline_test.py index 9cb3fcdbb91d..72aed46f5e78 100644 --- a/sdks/python/apache_beam/typehints/typed_pipeline_test.py +++ b/sdks/python/apache_beam/typehints/typed_pipeline_test.py @@ -422,7 +422,7 @@ def test_typed_ptransform_fn_conflicting_hints(self): # In this case, both MyMap and its contained ParDo have separate type # checks (that disagree with each other). @beam.ptransform_fn - @typehints.with_input_types(int) + @typehints.with_input_types(str) def MyMap(pcoll): def fn(element: float): yield element @@ -430,11 +430,11 @@ def fn(element: float): return pcoll | beam.ParDo(fn) with self.assertRaisesRegex(typehints.TypeCheckError, - r'ParDo.*requires.*float.*got.*int'): - _ = [1, 2, 3] | MyMap() + r'ParDo.*requires.*float.*got.*str'): + _ = ['1', '2', '3'] | MyMap() with self.assertRaisesRegex(typehints.TypeCheckError, - r'MyMap.*expected.*int.*got.*str'): - _ = ['a'] | MyMap() + r'MyMap.*expected.*str.*got.*bytes'): + _ = [b'a'] | MyMap() def test_typed_dofn_string_literals(self): class MyDoFn(beam.DoFn): diff --git a/sdks/python/apache_beam/typehints/typehints.py b/sdks/python/apache_beam/typehints/typehints.py index b368f0abdf3d..912cb78dc095 100644 --- a/sdks/python/apache_beam/typehints/typehints.py +++ b/sdks/python/apache_beam/typehints/typehints.py @@ -1309,6 +1309,12 @@ def is_consistent_with(sub, base): return True if isinstance(sub, AnyTypeConstraint) or isinstance(base, AnyTypeConstraint): return True + # Per PEP484, ints are considered floats and complexes and + # floats are considered complexes. + if sub is int and base in (float, complex): + return True + if sub is float and base is complex: + return True sub = normalize(sub, none_as_type=True) base = normalize(base, none_as_type=True) if isinstance(sub, UnionConstraint): diff --git a/sdks/python/apache_beam/typehints/typehints_test.py b/sdks/python/apache_beam/typehints/typehints_test.py index c395893a23ba..843c1498cac5 100644 --- a/sdks/python/apache_beam/typehints/typehints_test.py +++ b/sdks/python/apache_beam/typehints/typehints_test.py @@ -166,6 +166,14 @@ def test_any_compatibility(self): self.assertCompatible(object, typehints.Any) self.assertCompatible(typehints.Any, object) + def test_int_float_complex_compatibility(self): + self.assertCompatible(float, int) + self.assertCompatible(complex, int) + self.assertCompatible(complex, float) + self.assertNotCompatible(int, float) + self.assertNotCompatible(int, complex) + self.assertNotCompatible(float, complex) + def test_repr(self): self.assertEqual('Any', repr(typehints.Any)) @@ -218,7 +226,7 @@ def test_union_hint_compatibility(self): typehints.Union[int, str], typehints.Union[str, typehints.Union[int, str]]) - self.assertNotCompatible( + self.assertCompatible( typehints.Union[float, bool], typehints.Union[int, bool]) self.assertNotCompatible( typehints.Union[bool, str], typehints.Union[float, bool, int])