class GrpcService(IOpenable, IConfigurable, IRegisterable, IUnreferenceable):
    """
    Abstract service that receives remove calls via GRPC protocol.

    ### Configuration parameters ###
        - dependencies:
          - endpoint:              override for GRPC Endpoint dependency
          - controller:            override for Controller dependency
        - connection(s):
          - discovery_key:         (optional) a key to retrieve the connection from :class:`IDiscovery <pip_services3_components.connect.IDiscovery.IDiscovery>`
          - protocol:              connection protocol: http or https
          - host:                  host name or IP address
          - port:                  port number
          - uri:                   resource URI or connection string with all parameters in it
        - credential - the HTTPS credentials:
          - ssl_key_file:         the SSL private key in PEM
          - ssl_crt_file:         the SSL certificate in PEM
          - ssl_ca_file:          the certificate authorities (root cerfiticates) in PEM


    .. code-block:: python

        class MyGrpcService(GrpcService):
            __controller: IMyController
            ...
            def __init__(self):
                suoer().__init__('... path to proto ...', '.. service name ...')
                self._dependency_resolver.put(
                    "controller",
                    Descriptor("mygroup","controller","*","*","1.0")
                )


            def set_references(self, references):
                super().set_references(references)
                self._controller = this._dependency_resolver.get_required("controller")


            def register(self):
                def method(correlation_id, args, getted_method):
                    correlationId = call.request.correlationId;
                    id = call.request.id;
                    self._controller.getMyData(correlationId, id, callback);

                self.register_commadable_method("get_mydata", None, method)
                ...



        service = MyGrpcService()
        service.configure(ConfigParams.from_tuples(
            "connection.protocol", "http",
            "connection.host", "localhost",
            "connection.port", 8080
        ))

        service.set_references(References.from_tuples(
           Descriptor("mygroup","controller","default","default","1.0"), controller
        ))

        service.open("123")

    """

    __default_config = ConfigParams.from_tuples("dependencies.endpoint",
                                                "*:endpoint:grpc:*:1.0")

    def __init__(self, service_name: str = None):
        """
        Creates a new instance of the service.

        :param service_name: a service name.
        """
        self.__config: ConfigParams = None
        self.__references: IReferences = None
        self.__local_endpoint: bool = None
        self.__implementation: Any = {}
        self.__interceptors: List[Any] = []
        self.__opened: bool = None

        # The GRPC endpoint that exposes this service.
        self._endpoint: GrpcEndpoint = None

        # The dependency resolver.
        self._dependency_resolver = DependencyResolver(
            GrpcService.__default_config)

        # The logger.
        self._logger = CompositeLogger()

        # The performance counters.
        self._counters = CompositeCounters()

        self.__service_name = service_name
        self.__registrable = lambda implementation: self._register_service(
            implementation)

    def configure(self, config: ConfigParams):
        """
        Configures component by passing configuration parameters.
        :param config: configuration parameters to be set.
        """

        config = config.set_defaults(GrpcService.__default_config)
        self.__config = config
        self._dependency_resolver.configure(config)

    def set_references(self, references: IReferences):
        """
        Sets references to this endpoint's logger, counters, and connection resolver.
        
        ### References ###
            - logger: **"\*:logger:\*:\*:1.0"**
            - counters: **"\*:counters:\*:\*:1.0"**
            - discovery: **"\*:discovery:\*:\*:1.0"** (for the connection resolver)

        :param references: an IReferences object, containing references to a logger, counters, and a connection resolver.
        """
        self._logger.set_references(references)
        self._counters.set_references(references)
        self._dependency_resolver.set_references(references)

        # Get endpoint
        self._endpoint = self._dependency_resolver.get_one_optional('endpoint')

        # Or create a local one
        if self._endpoint is None:
            self._endpoint = self.__create_endpoint()
            self.__local_endpoint = True
        else:
            self.__local_endpoint = False

        #  Add registration callback to the endpoint
        self._endpoint.register(self)

    def unset_references(self):
        """
        Unsets (clears) previously set references to dependent components.
        """
        # Remove registration callback from endpoint
        if self._endpoint is not None:
            self._endpoint.unregister(self.__registrable)
            self._endpoint = None

    def __create_endpoint(self) -> GrpcEndpoint:
        endpoint = GrpcEndpoint()

        if self.__config:
            endpoint.configure(self.__config)
        if self.__references:
            endpoint.set_references(self.__references)

        return endpoint

    def _instrument(self, correlation_id: Optional[str],
                    name: str) -> CounterTiming:
        """
        Adds instrumentation to log calls and measure call time.
        It returns a CounterTiming object that is used to end the time measurement.

        :param correlation_id: (optional) transaction id to trace execution through call chain.
        :param name: a method name.
        :return: CounterTiming object to end the time measurement.
        """
        self._logger.trace(correlation_id, 'Executing {} method'.format(name))
        self._counters.increment_one(name + '.exec_time')
        return self._counters.begin_timing(name + '.exec_time')

    def _instrument_error(self,
                          correlation_id: Optional[str],
                          name: str,
                          err: Exception,
                          reerror=False):
        """
        Adds instrumentation to error handling.

        :param correlation_id: (optional) transaction id to trace execution through call chain.
        :param name: a method name.
        :param err: an occured error
        :param reerror: if true - throw error
        """
        if err is not None:
            self._logger.error(correlation_id, err,
                               'Failed to execute {} method'.format(name))
            self._counters.increment_one(name + '.exec_errors')

        if reerror:
            raise err

    def is_open(self) -> bool:
        """
        Checks if the component is opened.

        :return: true if the component has been opened and false otherwise.
        """
        return self.__opened

    def open(self, correlation_id: Optional[str]):
        """
        Opens the component.

        :param correlation_id: (optional) transaction id to trace execution through call chain.
        """
        # TODO maybe need add async

        if self.__opened:
            return None

        if self._endpoint is None:
            self._endpoint = self.__create_endpoint()
            self._endpoint.register(self)
            self.__local_endpoint = True

        if self.__local_endpoint:
            try:
                self._endpoint.open(correlation_id)
                self.__opened = True
            except Exception as ex:
                self.__opened = False
                raise ex
        else:
            self.__opened = True

    def close(self, correlation_id: Optional[str]):
        """
        Closes component and frees used resources.

        :param correlation_id: (optional) transaction id to trace execution through call chain.
        """
        if not self.__opened:
            return None

        if self._endpoint is None:
            raise InvalidStateException(correlation_id, 'NO_ENDPOINT',
                                        'HTTP endpoint is missing')

        if self.__local_endpoint:
            self._endpoint.close(correlation_id)

        self.__opened = False

    def register_commadable_method(
            self, method: str, schema: Schema,
            action: Callable[[Optional[str], Optional[str], Parameters],
                             None]):
        """
        Registers a commandable method in this objects GRPC server (service) by the given name.

        :param method: the GRPC method name.
        :param schema: the schema to use for parameter validation.
        :param action: the action to perform at the given route.
        """
        self._endpoint._register_commandable_method(method, schema, action)

    def _register_interceptor(self, action: Callable):
        """
        Registers a middleware for methods in GRPC endpoint.

        :param action: an action function that is called when middleware is invoked.
        """
        if self._endpoint is not None:
            self._endpoint._register_interceptor(action)

    def _register_service(self, implementation: 'GrpcService'):
        # self.register()

        if self._endpoint is not None:
            self._endpoint.register_service(implementation)

    @abstractmethod
    def register(self):
        """
示例#2
0
class GrpcClient(IOpenable, IReferenceable, IConfigurable):
    """
    Abstract client that calls remove endpoints using GRPC protocol.

    ### Configuration parameters ###
        - connection(s):
          - discovery_key:         (optional) a key to retrieve the connection from :func:`link`
          - protocol:              connection protocol: http or https
          - host:                  host name or IP address
          - port:                  port number
          - uri:                   resource URI or connection string with all parameters in it
        - options:
          - retries:               number of retries (default: 3)
          - connect_timeout:       connection timeout in milliseconds (default: 10 sec)
          - timeout:               invocation timeout in milliseconds (default: 10 sec)

    .. code-block:: python

        class MyGrpcClient(GrpcClient, IMyClient):
            ...
            def get_data(self, correlation_id, id ):
                timing = self.instrument(correlation_id, 'myclient.get_data')
                result = self.call("get_data", correlation_id, { id: id })
                timing.end_timing()
                return result
            ...

        client = MyGrpcClient()
        client.configure(ConfigParams.from_tuples(
            "connection.protocol", "http",
            "connection.host", "localhost",
            "connection.port", 8080
        ))
        result = client.get_data("123", "1")
    """

    _default_config = ConfigParams.from_tuples(
        "connection.protocol", "http", "connection.host", "0.0.0.0",
        "connection.port", 3000, "options.request_max_size", 1024 * 1024,
        "options.connect_timeout", 10000, "options.timeout", 10000,
        "options.retries", 3, "options.debug", True)

    def __init__(self, client_name: str):
        """
        Creates a new instance of the client.

        :param client_name: a client name.
        """
        self.__client = None
        self.__client_name = None

        # The GRPC client channel
        self._channel = None

        # The connection resolver.
        self._connection_resolver = HttpConnectionResolver()

        # The logger.
        self._logger = CompositeLogger()

        # The performance counters.
        self._counters = CompositeCounters()

        # The configuration options.
        self._options = ConfigParams()

        # The connection timeout in milliseconds.
        self._connection_timeout = 100000

        # The invocation timeout in milliseconds.
        self._timeout = 100000

        # The remote service uri which is calculated on open.
        self._uri: str = None

        self.__client_name = client_name

    def configure(self, config: ConfigParams):
        """
        Configures component by passing configuration parameters.

        :param config: configuration parameters to be set.
        """
        config = config.set_defaults(GrpcClient._default_config)
        self._connection_resolver.configure(config)
        self._options = self._options.override(config.get_section('options'))

        self._connection_timeout = config.get_as_integer_with_default(
            'options.connect_timeout', self._connection_timeout)
        self._timeout = config.get_as_integer_with_default(
            'options.timeout', self._timeout)

    def set_references(self, references: IReferences):
        """
        Sets references to dependent components.

        :param references: references to locate the component dependencies.
        """
        self._logger.set_references(references)
        self._counters.set_references(references)
        self._connection_resolver.set_references(references)

    def _instrument(self, correlation_id: Optional[str],
                    name: str) -> CounterTiming:
        """
        Adds instrumentation to log calls and measure call time.
        It returns a CounterTiming object that is used to end the time measurement.

        :param correlation_id: (optional) transaction id to trace execution through call chain.
        :param name: a method name.
        :return: CounterTiming object to end the time measurement.
        """
        self._logger.trace(correlation_id, 'Executing {} method'.format(name))
        self._counters.increment_one(name + '.call_count')
        return self._counters.begin_timing(name + '.call_time')

    def _instrument_error(self,
                          correlation_id: Optional[str],
                          name: str,
                          err: Exception,
                          reerror=False):
        """
        Adds instrumentation to error handling.

        :param correlation_id: (optional) transaction id to trace execution through call chain.
        :param name: a method name.
        :param err: an occured error
        :param reerror: if true - throw error
        """
        if err is not None:
            self._logger.error(correlation_id, err,
                               'Failed to call {} method'.format(name))
            self._counters.increment_one(name + '.call_errors')
            if reerror is not None and reerror is True:
                raise err

    def is_open(self) -> bool:
        """
        Checks if the component is opened.

        :return: Returns true if the component has been opened and false otherwise.
        """
        return self._channel is not None

    def open(self, correlation_id: Optional[str]):
        """
        Opens the component.

        :param correlation_id: (optional) transaction id to trace execution through call chain.
        """
        if self.is_open():
            return None

        try:
            connection = self._connection_resolver.resolve(correlation_id)
            self._uri = connection.get_as_string('uri')

            options = [('grpc.max_connection_idle_ms',
                        self._connection_timeout),
                       ('grpc.client_idle_timeout_ms', self._timeout)]

            if connection.get_as_string_with_default('protocol',
                                                     'http') == 'https':
                ssl_ca_file = connection.get_as_nullable_string('ssl_ca_file')
                with open(ssl_ca_file, 'rb') as file:
                    trusted_root = file.read()
                credentials = grpc.ssl_channel_credentials(trusted_root)
                channel = grpc.secure_channel(
                    str(connection.get_as_string('host')) + ':' +
                    str(connection.get_as_string('port')),
                    credentials=credentials,
                    options=options)
            else:
                channel = grpc.insecure_channel(
                    str(connection.get_as_string('host')) + ':' +
                    str(connection.get_as_string('port')),
                    options=options)
            self._channel = channel

        except Exception as ex:
            raise ConnectionException(
                correlation_id, 'CANNOT_CONNECT',
                'Opening GRPC client failed').wrap(ex).with_details(
                    'url', self._uri)

    def close(self, correlation_id: Optional[str]):
        """
        Closes component and frees used resources.

        :param correlation_id: (optional) transaction id to trace execution through call chain.
        """
        if self._channel is not None:
            # Eat exceptions
            try:
                self._logger.debug(
                    correlation_id,
                    'Closed GRPC service at {}'.format(self._uri))
            except Exception as ex:
                self._logger.warn(
                    correlation_id,
                    'Failed while closing GRPC service: {}'.format(ex))
            # if self.__client is not None:
            #     self.__client = None
            self._channel.close()
            self._channel = None
            self._uri = None
            GrpcClient._connection_resolver = HttpConnectionResolver()

    def call(self, method: str, client: Any, request: Any) -> Future:
        """
        Calls a remote method via GRPC protocol.

        :param method: name of the calling method
        :param client: current client
        :param request: (optional) request object.
        :return: (optional) future that receives result object or error.
        """

        client = client(self._channel)
        executor = futures.ThreadPoolExecutor(max_workers=1)
        response = executor.submit(client.__dict__[method], request)

        return response

    def _add_filter_params(self, params: Any, filter: Any) -> Any:
        """
        AddFilterParams method are adds filter parameters (with the same name as they defined)
        to invocation parameter map.

        :param params: invocation parameters.
        :param filter: (optional) filter parameters
        :return: invocation parameters with added filter parameters.
        """
        params = StringValueMap() if params is None else params

        if filter is not None:
            for k in filter.keys():
                params.put(k, filter[k])

        return params

    def _add_paging_params(self, params: Any, paging: Any) -> Any:
        """
        AddPagingParams method are adds paging parameters (skip, take, total) to invocation parameter map.

        :param params: invocation parameters.
        :param paging: (optional) paging parameters
        :return: invocation parameters with added paging parameters.
        """
        params = StringValueMap() if params is None else params

        if paging is not None:
            params.put('total', paging.total)
            if paging.skip is not None:
                params.put('skip', paging.skip)
            if paging.take is not None:
                params.put('take', paging.take)

        return params