Skip to content

Commit

Permalink
Merge pull request #32516 from hjtran/permissive_num_types
Browse files Browse the repository at this point in the history
[Python] Allow ints to be considered as floats/complexes and floats as complexes
  • Loading branch information
liferoad committed Sep 27, 2024
2 parents c2c640f + 22ce9fe commit 4309675
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 9 deletions.
6 changes: 3 additions & 3 deletions sdks/python/apache_beam/transforms/ptransform_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 5 additions & 5 deletions sdks/python/apache_beam/typehints/typed_pipeline_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,19 +422,19 @@ 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

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):
Expand Down
6 changes: 6 additions & 0 deletions sdks/python/apache_beam/typehints/typehints.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
10 changes: 9 additions & 1 deletion sdks/python/apache_beam/typehints/typehints_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down Expand Up @@ -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])
Expand Down

0 comments on commit 4309675

Please sign in to comment.