2424from typing import Callable
2525from typing import Collection
2626from typing import Dict
27+ from typing import List
2728from typing import Mapping
2829from typing import NamedTuple
2930from typing import Optional
4243from apache_beam .typehints import row_type
4344from apache_beam .typehints import schemas
4445from apache_beam .typehints import trivial_inference
46+ from apache_beam .typehints import typehints
4547from apache_beam .typehints .row_type import RowTypeConstraint
4648from apache_beam .typehints .schemas import named_fields_from_element_type
4749from apache_beam .utils import python_callable
@@ -569,6 +571,86 @@ def extract_expr(name, v):
569571 return pcoll | sql_transform_constructor (query )
570572
571573
574+ @beam .ptransform .ptransform_fn
575+ def _Partition (
576+ pcoll ,
577+ by : Union [str , Dict [str , str ]],
578+ outputs : List [str ],
579+ unknown_output : Optional [str ] = None ,
580+ error_handling : Optional [Mapping [str , Any ]] = None ,
581+ language : Optional [str ] = 'generic' ):
582+ """Splits an input into several distinct outputs.
583+
584+ Each input element will go to a distinct output based on the field or
585+ function given in the `by` configuration parameter.
586+
587+ Args:
588+ by: A field, callable, or expression giving the destination output for
589+ this element. Should return a string that is a member of the `outputs`
590+ parameter. If `unknown_output` is also set, other returns values are
591+ accepted as well, otherwise an error will be raised.
592+ outputs: The set of outputs into which this input is being partitioned.
593+ unknown_output: (Optional) If set, indicates a destination output for any
594+ elements that are not assigned an output listed in the `outputs`
595+ parameter.
596+ error_handling: (Optional) Whether and how to handle errors during
597+ partitioning.
598+ language: (Optional) The language of the `by` expression.
599+ """
600+ split_fn = _as_callable_for_pcoll (pcoll , by , 'by' , language )
601+ try :
602+ split_fn_output_type = trivial_inference .infer_return_type (
603+ split_fn , [pcoll .element_type ])
604+ except (TypeError , ValueError ):
605+ pass
606+ else :
607+ if not typehints .is_consistent_with (split_fn_output_type ,
608+ typehints .Optional [str ]):
609+ raise ValueError (
610+ f'Partition function "{ by } " must return a string type '
611+ f'not { split_fn_output_type } ' )
612+ error_output = error_handling ['output' ] if error_handling else None
613+ if error_output in outputs :
614+ raise ValueError (
615+ f'Error handling output "{ error_output } " '
616+ f'cannot be among the listed outputs { outputs } ' )
617+ T = TypeVar ('T' )
618+
619+ def split (element ):
620+ tag = split_fn (element )
621+ if tag is None :
622+ tag = unknown_output
623+ if not isinstance (tag , str ):
624+ raise ValueError (
625+ f'Returned output name "{ tag } " of type { type (tag )} '
626+ f'from "{ by } " must be a string.' )
627+ if tag not in outputs :
628+ if unknown_output :
629+ tag = unknown_output
630+ else :
631+ raise ValueError (f'Unknown output name "{ tag } " from { by } ' )
632+ return beam .pvalue .TaggedOutput (tag , element )
633+
634+ output_set = set (outputs )
635+ if unknown_output :
636+ output_set .add (unknown_output )
637+ if error_output :
638+ output_set .add (error_output )
639+ mapping_transform = beam .Map (split )
640+ if error_output :
641+ mapping_transform = mapping_transform .with_exception_handling (
642+ ** exception_handling_args (error_handling ))
643+ else :
644+ mapping_transform = mapping_transform .with_outputs (* output_set )
645+ splits = pcoll | mapping_transform .with_input_types (T ).with_output_types (T )
646+ result = {out : getattr (splits , out ) for out in output_set }
647+ if error_output :
648+ result [
649+ error_output ] = result [error_output ] | _map_errors_to_standard_format (
650+ pcoll .element_type )
651+ return result
652+
653+
572654@beam .ptransform .ptransform_fn
573655@maybe_with_exception_handling_transform_fn
574656def _AssignTimestamps (
@@ -588,7 +670,8 @@ def _AssignTimestamps(
588670 Args:
589671 timestamp: A field, callable, or expression giving the new timestamp.
590672 language: The language of the timestamp expression.
591- error_handling: Whether and how to handle errors during iteration.
673+ error_handling: Whether and how to handle errors during timestamp
674+ evaluation.
592675 """
593676 timestamp_fn = _as_callable_for_pcoll (pcoll , timestamp , 'timestamp' , language )
594677 T = TypeVar ('T' )
@@ -611,6 +694,9 @@ def create_mapping_providers():
611694 'MapToFields-python' : _PyJsMapToFields ,
612695 'MapToFields-javascript' : _PyJsMapToFields ,
613696 'MapToFields-generic' : _PyJsMapToFields ,
697+ 'Partition-python' : _Partition ,
698+ 'Partition-javascript' : _Partition ,
699+ 'Partition-generic' : _Partition ,
614700 }),
615701 yaml_provider .SqlBackedProvider ({
616702 'Filter-sql' : _SqlFilterTransform ,
0 commit comments