github.com/apache/beam/sdks/v2@v2.48.2/python/apache_beam/utils/urns.py (about)

     1  #
     2  # Licensed to the Apache Software Foundation (ASF) under one or more
     3  # contributor license agreements.  See the NOTICE file distributed with
     4  # this work for additional information regarding copyright ownership.
     5  # The ASF licenses this file to You under the Apache License, Version 2.0
     6  # (the "License"); you may not use this file except in compliance with
     7  # the License.  You may obtain a copy of the License at
     8  #
     9  #    http://www.apache.org/licenses/LICENSE-2.0
    10  #
    11  # Unless required by applicable law or agreed to in writing, software
    12  # distributed under the License is distributed on an "AS IS" BASIS,
    13  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  # See the License for the specific language governing permissions and
    15  # limitations under the License.
    16  #
    17  
    18  """For internal use only; no backwards-compatibility guarantees."""
    19  
    20  # pytype: skip-file
    21  
    22  # TODO(https://github.com/apache/beam/issues/18399): Issue with dill + local
    23  # classes + abc metaclass
    24  # import abc
    25  import inspect
    26  from typing import TYPE_CHECKING
    27  from typing import Any
    28  from typing import Callable
    29  from typing import Dict
    30  from typing import Optional
    31  from typing import Tuple
    32  from typing import Type
    33  from typing import TypeVar
    34  from typing import Union
    35  from typing import overload
    36  
    37  from google.protobuf import message
    38  from google.protobuf import wrappers_pb2
    39  
    40  from apache_beam.internal import pickler
    41  from apache_beam.utils import proto_utils
    42  
    43  if TYPE_CHECKING:
    44    from apache_beam.portability.api import beam_runner_api_pb2
    45    from apache_beam.runners.pipeline_context import PipelineContext
    46  
    47  T = TypeVar('T')
    48  RunnerApiFnT = TypeVar('RunnerApiFnT', bound='RunnerApiFn')
    49  ConstructorFn = Callable[[Union['message.Message', bytes], 'PipelineContext'],
    50                           Any]
    51  
    52  
    53  class RunnerApiFn(object):
    54    """Abstract base class that provides urn registration utilities.
    55  
    56    A class that inherits from this class will get a registration-based
    57    from_runner_api and to_runner_api method that convert to and from
    58    beam_runner_api_pb2.FunctionSpec.
    59  
    60    Additionally, register_pickle_urn can be called from the body of a class
    61    to register serialization via pickling.
    62    """
    63  
    64    # TODO(https://github.com/apache/beam/issues/18399): Issue with dill + local
    65    # classes + abc metaclass
    66    # __metaclass__ = abc.ABCMeta
    67  
    68    _known_urns = {}  # type: Dict[str, Tuple[Optional[type], ConstructorFn]]
    69  
    70    # @abc.abstractmethod is disabled here to avoid an error with mypy. mypy
    71    # performs abc.abtractmethod/property checks even if a class does
    72    # not use abc.ABCMeta, however, functions like `register_pickle_urn`
    73    # dynamically patch `to_runner_api_parameter`, which mypy cannot track, so
    74    # mypy incorrectly infers that this method has not been overridden with a
    75    # concrete implementation.
    76    # @abc.abstractmethod
    77    def to_runner_api_parameter(self, unused_context):
    78      # type: (PipelineContext) -> Tuple[str, Any]
    79  
    80      """Returns the urn and payload for this Fn.
    81  
    82      The returned urn(s) should be registered with `register_urn`.
    83      """
    84      raise NotImplementedError
    85  
    86    @classmethod
    87    @overload
    88    def register_urn(
    89        cls,
    90        urn,  # type: str
    91        parameter_type,  # type: Type[T]
    92    ):
    93      # type: (...) -> Callable[[Callable[[T, PipelineContext], Any]], Callable[[T, PipelineContext], Any]]
    94      pass
    95  
    96    @classmethod
    97    @overload
    98    def register_urn(
    99        cls,
   100        urn,  # type: str
   101        parameter_type,  # type: None
   102    ):
   103      # type: (...) -> Callable[[Callable[[bytes, PipelineContext], Any]], Callable[[bytes, PipelineContext], Any]]
   104      pass
   105  
   106    @classmethod
   107    @overload
   108    def register_urn(cls,
   109                     urn,  # type: str
   110                     parameter_type,  # type: Type[T]
   111                     fn  # type: Callable[[T, PipelineContext], Any]
   112                    ):
   113      # type: (...) -> None
   114      pass
   115  
   116    @classmethod
   117    @overload
   118    def register_urn(cls,
   119                     urn,  # type: str
   120                     parameter_type,  # type: None
   121                     fn  # type: Callable[[bytes, PipelineContext], Any]
   122                    ):
   123      # type: (...) -> None
   124      pass
   125  
   126    @classmethod
   127    def register_urn(cls, urn, parameter_type, fn=None):
   128      """Registers a urn with a constructor.
   129  
   130      For example, if 'beam:fn:foo' had parameter type FooPayload, one could
   131      write `RunnerApiFn.register_urn('bean:fn:foo', FooPayload, foo_from_proto)`
   132      where foo_from_proto took as arguments a FooPayload and a PipelineContext.
   133      This function can also be used as a decorator rather than passing the
   134      callable in as the final parameter.
   135  
   136      A corresponding to_runner_api_parameter method would be expected that
   137      returns the tuple ('beam:fn:foo', FooPayload)
   138      """
   139      def register(fn):
   140        cls._known_urns[urn] = parameter_type, fn
   141        return fn
   142  
   143      if fn:
   144        # Used as a statement.
   145        register(fn)
   146      else:
   147        # Used as a decorator.
   148        return register
   149  
   150    @classmethod
   151    def register_pickle_urn(cls, pickle_urn):
   152      """Registers and implements the given urn via pickling.
   153      """
   154      inspect.currentframe().f_back.f_locals['to_runner_api_parameter'] = (
   155          lambda self,
   156          context:
   157          (pickle_urn, wrappers_pb2.BytesValue(value=pickler.dumps(self))))
   158      cls.register_urn(
   159          pickle_urn,
   160          wrappers_pb2.BytesValue,
   161          lambda proto,
   162          unused_context: pickler.loads(proto.value))
   163  
   164    def to_runner_api(self, context):
   165      # type: (PipelineContext) -> beam_runner_api_pb2.FunctionSpec
   166  
   167      """Returns an FunctionSpec encoding this Fn.
   168  
   169      Prefer overriding self.to_runner_api_parameter.
   170      """
   171      from apache_beam.portability.api import beam_runner_api_pb2
   172      urn, typed_param = self.to_runner_api_parameter(context)
   173      return beam_runner_api_pb2.FunctionSpec(
   174          urn=urn,
   175          payload=typed_param.SerializeToString() if isinstance(
   176              typed_param, message.Message) else typed_param)
   177  
   178    @classmethod
   179    def from_runner_api(cls, fn_proto, context):
   180      # type: (Type[RunnerApiFnT], beam_runner_api_pb2.FunctionSpec, PipelineContext) -> RunnerApiFnT
   181  
   182      """Converts from an FunctionSpec to a Fn object.
   183  
   184      Prefer registering a urn with its parameter type and constructor.
   185      """
   186      parameter_type, constructor = cls._known_urns[fn_proto.urn]
   187      return constructor(
   188          proto_utils.parse_Bytes(fn_proto.payload, parameter_type), context)