Skip to content

Commit b9d48fa

Browse files
Fix type checking failure with Python 3.10+ union pipe syntax (int | None) (#37645)
* Fix type checking failure with Python 3.10+ union pipe syntax (int | None) The normalize() function did not handle types.UnionType, causing is_consistent_with() to fall through to issubclass() with a non-class argument. Route types.UnionType through convert_to_beam_type() which already knows how to convert it to Beam's UnionConstraint. Fixes #36592 * Fix yapf formatting in test file * Address review: merge UnionType check into existing elif, remove issue link comments
1 parent 872c671 commit b9d48fa

2 files changed

Lines changed: 28 additions & 3 deletions

File tree

sdks/python/apache_beam/typehints/typehints.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,9 +1462,10 @@ def normalize(x, none_as_type=False):
14621462
# Convert bare builtin types to correct type hints directly
14631463
elif x in _KNOWN_PRIMITIVE_TYPES:
14641464
return _KNOWN_PRIMITIVE_TYPES[x]
1465-
elif getattr(x, '__module__',
1466-
None) in ('typing', 'collections', 'collections.abc') or getattr(
1467-
x, '__origin__', None) in _KNOWN_PRIMITIVE_TYPES:
1465+
elif isinstance(x, types.UnionType) or getattr(
1466+
x, '__module__',
1467+
None) in ('typing', 'collections', 'collections.abc') or getattr(
1468+
x, '__origin__', None) in _KNOWN_PRIMITIVE_TYPES:
14681469
beam_type = native_type_compatibility.convert_to_beam_type(x)
14691470
if beam_type != x:
14701471
# We were able to do the conversion.

sdks/python/apache_beam/typehints/typehints_test.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,6 +1596,22 @@ def test_hint_helper(self):
15961596
self.assertFalse(is_consistent_with(Union[str, int], str))
15971597
self.assertFalse(is_consistent_with(str, NonBuiltInGeneric[str]))
15981598

1599+
def test_hint_helper_pipe_union(self):
1600+
pipe_union = int | None # pylint: disable=unsupported-binary-operation
1601+
typing_union = Union[int, None]
1602+
self.assertTrue(is_consistent_with(int, pipe_union))
1603+
self.assertTrue(is_consistent_with(type(None), pipe_union))
1604+
self.assertFalse(is_consistent_with(str, pipe_union))
1605+
self.assertTrue(
1606+
is_consistent_with(int, pipe_union) == is_consistent_with(
1607+
int, typing_union))
1608+
self.assertTrue(
1609+
is_consistent_with(str, pipe_union) == is_consistent_with(
1610+
str, typing_union))
1611+
pipe_union_2 = int | float # pylint: disable=unsupported-binary-operation
1612+
self.assertTrue(is_consistent_with(int, pipe_union_2))
1613+
self.assertTrue(is_consistent_with(float, pipe_union_2))
1614+
15991615
def test_positional_arg_hints(self):
16001616
self.assertEqual(typehints.Any, _positional_arg_hints('x', {}))
16011617
self.assertEqual(int, _positional_arg_hints('x', {'x': int}))
@@ -1934,6 +1950,14 @@ def test_pipe_operator_as_union(self):
19341950
native_type_compatibility.convert_to_beam_type(type_a),
19351951
native_type_compatibility.convert_to_beam_type(type_b))
19361952

1953+
def test_normalize_pipe_union(self):
1954+
pipe_union = int | None # pylint: disable=unsupported-binary-operation
1955+
normalized = typehints.normalize(pipe_union)
1956+
self.assertIsInstance(normalized, typehints.UnionConstraint)
1957+
pipe_union_2 = int | float # pylint: disable=unsupported-binary-operation
1958+
normalized_2 = typehints.normalize(pipe_union_2)
1959+
self.assertIsInstance(normalized_2, typehints.UnionConstraint)
1960+
19371961

19381962
class TestNonBuiltInGenerics(unittest.TestCase):
19391963
def test_no_error_thrown(self):

0 commit comments

Comments
 (0)