Skip to content

Commit 730388a

Browse files
authored
Fix beam.Filter type hints for case where filter_fn specifies output type hint but not input type hint (#35283)
* remove unnecessary output_hint inferring logic * extract input_hint and set it as the output_hint * only pull out input hint if it's defined * only conditionally set output hint if input hitn is not Any or None * tidy up test
1 parent 71073a0 commit 730388a

2 files changed

Lines changed: 47 additions & 9 deletions

File tree

sdks/python/apache_beam/pipeline_test.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,35 @@ def mux_input(x):
639639
self.assertNotIn(multi.letters, visitor.visited)
640640
self.assertNotIn(multi.numbers, visitor.visited)
641641

642+
def test_filter_typehint(self):
643+
# Check input type hint and output type hint are both specified.
644+
def always_true_with_all_typehints(x: int) -> bool:
645+
return True
646+
647+
# Check only input type hint is specified.
648+
def always_true_only_inptype(x: int):
649+
return True
650+
651+
# Check only output type hint is specified.
652+
def always_true_only_outptype(x) -> bool:
653+
return True
654+
655+
# Check if inp type hint is Any that we can still infer
656+
# from the input pcollection type
657+
def always_true_any_inptype(x: typehints.Any) -> bool:
658+
return True
659+
660+
for filter_fn in [always_true_with_all_typehints,
661+
always_true_only_inptype,
662+
always_true_only_outptype,
663+
always_true_any_inptype]:
664+
with TestPipeline() as p:
665+
pcoll = (
666+
p
667+
| beam.Create([1, 2, 3]).with_input_types(int)
668+
| beam.Filter(filter_fn))
669+
self.assertEqual(pcoll.element_type, int)
670+
642671
def test_kv_ptransform_honor_type_hints(self):
643672

644673
# The return type of this DoFn cannot be inferred by the default

sdks/python/apache_beam/transforms/core.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2681,18 +2681,27 @@ def Filter(fn, *args, **kwargs): # pylint: disable=invalid-name
26812681

26822682
# Proxy the type-hint information from the function being wrapped, setting the
26832683
# output type to be the same as the input type.
2684-
if type_hints.input_types is not None:
2684+
def has_simple_input_type(th):
2685+
return (
2686+
th.input_types is not None and len(th.input_types[0]) == 1 and
2687+
not th.input_types[1])
2688+
2689+
def simple_input_type(th):
2690+
return th.input_types[0][0] if has_simple_input_type(th) else None
2691+
2692+
if type_hints.input_types is not None and simple_input_type(
2693+
type_hints) is not typehints.Any:
26852694
wrapper = with_input_types(
26862695
*type_hints.input_types[0], **type_hints.input_types[1])(
26872696
wrapper)
2688-
output_hint = type_hints.simple_output_type(label)
2689-
if (output_hint is None and get_type_hints(wrapper).input_types and
2690-
get_type_hints(wrapper).input_types[0]):
2691-
output_hint = get_type_hints(wrapper).input_types[0][0]
2692-
if output_hint:
2693-
wrapper = with_output_types(
2694-
typehints.Iterable[_strip_output_annotations(output_hint)])(
2695-
wrapper)
2697+
output_hint = type_hints.simple_output_type(label)
2698+
if (output_hint is None and get_type_hints(wrapper).input_types and
2699+
get_type_hints(wrapper).input_types[0]):
2700+
output_hint = get_type_hints(wrapper).input_types[0][0]
2701+
if output_hint:
2702+
wrapper = with_output_types(
2703+
typehints.Iterable[_strip_output_annotations(output_hint)])(
2704+
wrapper)
26962705
# pylint: disable=protected-access
26972706
wrapper._argspec_fn = fn
26982707
# pylint: enable=protected-access

0 commit comments

Comments
 (0)