# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""A class for information about remote calls."""

# In its own module to avoid circular import between _datastore_api and
# tasklets modules.
import grpc

from google.cloud.ndb import exceptions


class RemoteCall(object):
    """Represents a remote call.

    This is primarily a wrapper for futures returned by gRPC. This holds some
    information about the call to make debugging easier. Can be used for
    anything that returns a future for something running outside of our own
    event loop.

    Arguments:
        future (Union[grpc.Future, tasklets.Future]): The future handed back
            from initiating the call.
        info (str): Helpful human readable string about the call. This string
            will be handed back verbatim by calls to :meth:`__repr__`.
    """

    def __init__(self, future, info):
        self.future = future
        self.info = info
        self._callbacks = []

        future.add_done_callback(self._finish)

    def __repr__(self):
        return self.info

    def exception(self):
        """Calls :meth:`grpc.Future.exception` on attr:`future`."""
        # GRPC will actually raise FutureCancelledError.
        # We'll translate that to our own Cancelled exception and *return* it,
        # which is far more polite for a method that *returns exceptions*.
        try:
            return self.future.exception()
        except grpc.FutureCancelledError:
            return exceptions.Cancelled()

    def result(self):
        """Calls :meth:`grpc.Future.result` on attr:`future`."""
        return self.future.result()

    def add_done_callback(self, callback):
        """Add a callback function to be run upon task completion. Will run
        immediately if task has already finished.

        Args:
            callback (Callable): The function to execute.
        """
        if self.future.done():
            callback(self)
        else:
            self._callbacks.append(callback)

    def cancel(self):
        """Calls :meth:`grpc.Future.cancel` on attr:`cancel`."""
        return self.future.cancel()

    def _finish(self, rpc):
        """Called when remote future is finished.

        Used to call our own done callbacks.
        """
        for callback in self._callbacks:
            callback(self)

@@ -1454,85 +1454,8 @@
 info
-%0A        self._callbacks = %5B%5D%0A%0A        future.add_done_callback(self._finish)
 %0A%0A  
@@ -1573,24 +1573,25 @@
 ception%60 on 
+:
 attr:%60future
@@ -2013,24 +2013,25 @@
 .result%60 on 
+:
 attr:%60future
@@ -2339,28 +2339,46 @@
     
-if self.future.done(
+remote = self%0A%0A        def wrapper(rpc
 ):%0A 
@@ -2380,32 +2380,39 @@
 c):%0A            
+return 
 callback(self)%0A 
@@ -2408,37 +2408,25 @@
 ack(
-self)%0A        else:%0A 
+remote)%0A%0A
         
    s
@@ -2421,24 +2421,36 @@
         
-   
 self.
+future.add_done
 _callbac
@@ -2454,25 +2454,16 @@
 back
-s.
+(wr
 appe
-nd(callback
+r
 )%0A%0A 
@@ -2588,209 +2588,4 @@
 l()%0A
-%0A    def _finish(self, rpc):%0A        %22%22%22Called when remote future is finished.%0A%0A        Used to call our own done callbacks.%0A        %22%22%22%0A        for callback in self._callbacks:%0A            callback(self)%0A

fix race condition in remote calls (#329)