def _get_methods( self, methods: Sequence[descriptor_pb2.MethodDescriptorProto], address: metadata.Address, path: Tuple[int, ...], ) -> Mapping[str, wrappers.Method]: """Return a dictionary of wrapped methods for the given service. Args: methods (Sequence[~.descriptor_pb2.MethodDescriptorProto]): A sequence of protobuf method objects. address (~.metadata.Address): An address object denoting the location of these methods. path (Tuple[int]): The source location path thus far, as understood by ``SourceCodeInfo.Location``. Returns: Mapping[str, ~.wrappers.Method]: A ordered mapping of :class:`~.wrappers.Method` objects. """ # Iterate over the methods and collect them into a dictionary. answer: Dict[str, wrappers.Method] = collections.OrderedDict() for meth_pb, i in zip(methods, range(0, sys.maxsize)): lro = None # If the output type is google.longrunning.Operation, we use # a specialized object in its place. if meth_pb.output_type.endswith('google.longrunning.Operation'): op = meth_pb.options.Extensions[operations_pb2.operation_info] if not op.response_type or not op.metadata_type: raise TypeError( f'rpc {meth_pb.name} returns a google.longrunning.' 'Operation, but is missing a response type or ' 'metadata type.', ) lro = wrappers.OperationInfo( response_type=self.api_messages[address.resolve( op.response_type, )], metadata_type=self.api_messages[address.resolve( op.metadata_type, )], ) # Create the method wrapper object. answer[meth_pb.name] = wrappers.Method( input=self.api_messages[meth_pb.input_type.lstrip('.')], lro=lro, method_pb=meth_pb, meta=metadata.Metadata( address=address.child(meth_pb.name, path + (i, )), documentation=self.docs.get(path + (i, ), self.EMPTY), ), output=self.api_messages[meth_pb.output_type.lstrip('.')], ) # Done; return the answer. return answer
def _get_methods( self, methods: List[descriptor_pb2.MethodDescriptorProto], address: metadata.Address, path: Tuple[int], ) -> Mapping[str, wrappers.Method]: """Return a dictionary of wrapped methods for the given service. Args: methods (Sequence[~.descriptor_pb2.MethodDescriptorProto]): A sequence of protobuf method objects. address (~.metadata.Address): An address object denoting the location of these methods. path (Tuple[int]): The source location path thus far, as understood by ``SourceCodeInfo.Location``. Returns: Mapping[str, ~.wrappers.Method]: A ordered mapping of :class:`~.wrappers.Method` objects. """ # Iterate over the methods and collect them into a dictionary. answer = collections.OrderedDict() for meth_pb, i in zip(methods, range(0, sys.maxsize)): types = meth_pb.options.Extensions[operations_pb2.operation_types] # If the output type is google.longrunning.Operation, we use # a specialized object in its place. output_type = self.all_messages[meth_pb.output_type.lstrip('.')] if meth_pb.output_type.endswith('google.longrunning.Operation'): output_type = self._get_operation_type( response_type=self.all_messages[address.resolve( types.response)], metadata_type=self.all_messages.get( address.resolve(types.metadata), ), ) # Create the method wrapper object. answer[meth_pb.name] = wrappers.Method( input=self.all_messages[meth_pb.input_type.lstrip('.')], method_pb=meth_pb, meta=metadata.Metadata( address=address, documentation=self.docs.get(path + (i, ), self.EMPTY), ), output=output_type, ) # Done; return the answer. return answer
def _maybe_get_lro( self, service_address: metadata.Address, meth_pb: descriptor_pb2.MethodDescriptorProto ) -> Optional[wrappers.OperationInfo]: """Determines whether a method is a Long Running Operation (aka LRO) and, if it is, return an OperationInfo that includes the response and metadata types. Args: service_address (~.metadata.Address): An address object for the service, denoting the location of these methods. meth_pb (~.descriptor_pb2.MethodDescriptorProto): A protobuf method objects. Returns: Optional[~.wrappers.OperationInfo]: The info for the long-running operation, if the passed method is an LRO. """ lro = None # If the output type is google.longrunning.Operation, we use # a specialized object in its place. if meth_pb.output_type.endswith('google.longrunning.Operation'): if not meth_pb.options.HasExtension(operations_pb2.operation_info): # This is not a long running operation even though it returns # an Operation. return None op = meth_pb.options.Extensions[operations_pb2.operation_info] if not op.response_type or not op.metadata_type: raise TypeError( f'rpc {meth_pb.name} returns a google.longrunning.' 'Operation, but is missing a response type or ' 'metadata type.', ) response_key = service_address.resolve(op.response_type) metadata_key = service_address.resolve(op.metadata_type) lro = wrappers.OperationInfo( response_type=self.api_messages[response_key], metadata_type=self.api_messages[metadata_key], ) return lro
def _get_methods( self, methods: Sequence[descriptor_pb2.MethodDescriptorProto], service_address: metadata.Address, path: Tuple[int, ...], ) -> Mapping[str, wrappers.Method]: """Return a dictionary of wrapped methods for the given service. Args: methods (Sequence[~.descriptor_pb2.MethodDescriptorProto]): A sequence of protobuf method objects. service_address (~.metadata.Address): An address object for the service, denoting the location of these methods. path (Tuple[int]): The source location path thus far, as understood by ``SourceCodeInfo.Location``. Returns: Mapping[str, ~.wrappers.Method]: A ordered mapping of :class:`~.wrappers.Method` objects. """ # Iterate over the methods and collect them into a dictionary. answer: Dict[str, wrappers.Method] = collections.OrderedDict() for meth_pb, i in zip(methods, range(0, sys.maxsize)): lro = None # If the output type is google.longrunning.Operation, we use # a specialized object in its place. if meth_pb.output_type.endswith('google.longrunning.Operation'): op = meth_pb.options.Extensions[operations_pb2.operation_info] if not op.response_type or not op.metadata_type: raise TypeError( f'rpc {meth_pb.name} returns a google.longrunning.' 'Operation, but is missing a response type or ' 'metadata type.', ) lro = wrappers.OperationInfo( response_type=self.api_messages[service_address.resolve( op.response_type, )], metadata_type=self.api_messages[service_address.resolve( op.metadata_type, )], ) # If we got a gRPC service config, get the appropriate retry # and timeout information from it. retry = None timeout = None # This object should be a dictionary that conforms to the # gRPC service config proto: # Repo: https://github.com/grpc/grpc-proto/ # Filename: grpc/service_config/service_config.proto # # We only care about a small piece, so we are just leaving # it as a dictionary and parsing accordingly. if self.opts.retry: # The gRPC service config uses a repeated `name` field # with a particular format, which we match against. # This defines the expected selector for *this* method. selector = { 'service': '{package}.{service_name}'.format( package='.'.join(service_address.package), service_name=service_address.name, ), 'method': meth_pb.name, } # Find the method config that applies to us, if any. mc = next((i for i in self.opts.retry.get('methodConfig', []) if selector in i.get('name')), None) if mc: # Set the timeout according to this method config. if mc.get('timeout'): timeout = self._to_float(mc['timeout']) # Set the retry according to this method config. if 'retryPolicy' in mc: r = mc['retryPolicy'] retry = wrappers.RetryInfo( max_attempts=r.get('maxAttempts', 0), initial_backoff=self._to_float( r.get('initialBackoff', '0s'), ), max_backoff=self._to_float( r.get('maxBackoff', '0s'), ), backoff_multiplier=r.get('backoffMultiplier', 0.0), retryable_exceptions=frozenset( exceptions.exception_class_for_grpc_status( getattr(grpc.StatusCode, code), ) for code in r.get('retryableStatusCodes', [])), ) # Create the method wrapper object. answer[meth_pb.name] = wrappers.Method( input=self.api_messages[meth_pb.input_type.lstrip('.')], lro=lro, method_pb=meth_pb, meta=metadata.Metadata( address=service_address.child(meth_pb.name, path + (i, )), documentation=self.docs.get(path + (i, ), self.EMPTY), ), output=self.api_messages[meth_pb.output_type.lstrip('.')], retry=retry, timeout=timeout, ) # Done; return the answer. return answer
def _maybe_get_extended_lro( self, service_address: metadata.Address, meth_pb: descriptor_pb2.MethodDescriptorProto, ) -> Optional[wrappers.ExtendedOperationInfo]: op_service_name = meth_pb.options.Extensions[ex_ops_pb2.operation_service] if not op_service_name: return None # Manual lookups because services and methods haven't been loaded. # Note: this assumes that the operation service lives in the same proto file. # This is a reasonable assumption as of March '22, but it may (unlikely) # change in the future. op_service_pb = next( (s for s in self.file_descriptor.service if s.name == op_service_name), None, ) if not op_service_pb: raise ValueError( f"Could not find custom operation service: {op_service_name}" ) operation_polling_method_pb = next( ( m for m in op_service_pb.method if m.options.Extensions[ex_ops_pb2.operation_polling_method] ), None, ) if not operation_polling_method_pb: raise ValueError( f"Could not find operation polling method for custom operation service: {op_service_name}" ) operation_request_key = service_address.resolve( operation_polling_method_pb.input_type.lstrip(".") ) operation_request_message = self.api_messages[operation_request_key] operation_type = service_address.resolve( operation_polling_method_pb.output_type.lstrip(".") ) method_output_type = service_address.resolve( meth_pb.output_type.lstrip(".") ) if operation_type != method_output_type: raise ValueError( f"Inconsistent return types between extended lro method '{meth_pb.name}'" f" and extended lro polling method '{operation_polling_method_pb.name}':" f" '{method_output_type}' and '{operation_type}'" ) operation_message = self.api_messages[operation_type] if not operation_message.is_extended_operation: raise ValueError( f"Message is not an extended operation: {operation_type}" ) return wrappers.ExtendedOperationInfo( request_type=operation_request_message, operation_type=operation_message, )