Repository: googleapis/google-cloud-python

Issue:
Pub/Sub: wrong inherited documentation for StreamingPullFuture
The [documentation](https://googleapis.github.io/google-cloud-python/latest/pubsub/subscriber/api/futures.html) for `StreamingPullFuture`'s methods like [`result()`](https://googleapis.github.io/google-cloud-python/latest/pubsub/subscriber/api/futures.html#google.cloud.pubsub_v1.subscriber.futures.StreamingPullFuture.result) and [`exception()`](https://googleapis.github.io/google-cloud-python/latest/pubsub/subscriber/api/futures.html#google.cloud.pubsub_v1.subscriber.futures.StreamingPullFuture.exception) doesn't make sense because it describes Publish futures not streaming pull futures. 

[`StreamingPullFuture`](https://github.com/googleapis/google-cloud-python/blob/a42b0f1abe83ce981c9573f4c56acc72e4344a2f/pubsub/google/cloud/pubsub_v1/subscriber/futures.py#L20) inherits [`futures.Future`](https://github.com/googleapis/google-cloud-python/blob/a42b0f1abe83ce981c9573f4c56acc72e4344a2f/pubsub/google/cloud/pubsub_v1/futures.py#L24), that's where the "wrong" documentation got carried over. 



Discussion:

Resolving patch:
diff --git a/pubsub/google/cloud/pubsub_v1/futures.py b/pubsub/google/cloud/pubsub_v1/futures.py
--- a/pubsub/google/cloud/pubsub_v1/futures.py
+++ b/pubsub/google/cloud/pubsub_v1/futures.py
@@ -87,18 +87,12 @@ def done(self):
         return self._exception != self._SENTINEL or self._result != self._SENTINEL
 
     def result(self, timeout=None):
-        """Return the message ID, or raise an exception.
-
-        This blocks until the message has successfully been published, and
-        returns the message ID.
+        """Resolve the future and return a value where appropriate.
 
         Args:
             timeout (Union[int, float]): The number of seconds before this call
                 times out and raises TimeoutError.
 
-        Returns:
-            str: The message ID.
-
         Raises:
             ~.pubsub_v1.TimeoutError: If the request times out.
             Exception: For undefined exceptions in the underlying
@@ -115,9 +109,6 @@ def result(self, timeout=None):
     def exception(self, timeout=None):
         """Return the exception raised by the call, if any.
 
-        This blocks until the message has successfully been published, and
-        returns the exception. If the call succeeded, return None.
-
         Args:
             timeout (Union[int, float]): The number of seconds before this call
                 times out and raises TimeoutError.
@@ -139,15 +130,21 @@ def exception(self, timeout=None):
         # Okay, this batch had an error; this should return it.
         return self._exception
 
-    def add_done_callback(self, fn):
+    def add_done_callback(self, callback):
         """Attach the provided callable to the future.
 
         The provided function is called, with this future as its only argument,
         when the future finishes running.
+
+        Args:
+            callback (Callable): The function to call.
+
+        Returns:
+            None
         """
         if self.done():
-            return fn(self)
-        self._callbacks.append(fn)
+            return callback(self)
+        self._callbacks.append(callback)
 
     def set_result(self, result):
         """Set the result of the future to the provided result.
diff --git a/pubsub/google/cloud/pubsub_v1/publisher/futures.py b/pubsub/google/cloud/pubsub_v1/publisher/futures.py
--- a/pubsub/google/cloud/pubsub_v1/publisher/futures.py
+++ b/pubsub/google/cloud/pubsub_v1/publisher/futures.py
@@ -36,13 +36,28 @@ class Future(futures.Future):
             :class:`threading.Event` will be created and used.
     """
 
-    # The publishing-side subclass does not need any special behavior
-    # at this time.
-    #
-    # However, there is still a subclass so that if someone attempts
-    # isinstance checks against a publisher-returned or subscriber-returned
-    # future, trying either one against the other returns False.
-    pass
-
-
-__all__ = ("Future",)
+    def result(self, timeout=None):
+        """Return the message ID or raise an exception.
+
+        This blocks until the message has been published successfully and
+        returns the message ID unless an exception is raised.
+
+        Args:
+            timeout (Union[int, float]): The number of seconds before this call
+                times out and raises TimeoutError.
+
+        Returns:
+            str: The message ID.
+
+        Raises:
+            ~.pubsub_v1.TimeoutError: If the request times out.
+            Exception: For undefined exceptions in the underlying
+                call execution.
+        """
+        # Attempt to get the exception if there is one.
+        # If there is not one, then we know everything worked, and we can
+        # return an appropriate value.
+        err = self.exception(timeout=timeout)
+        if err is None:
+            return self._result
+        raise err
diff --git a/pubsub/google/cloud/pubsub_v1/subscriber/futures.py b/pubsub/google/cloud/pubsub_v1/subscriber/futures.py
--- a/pubsub/google/cloud/pubsub_v1/subscriber/futures.py
+++ b/pubsub/google/cloud/pubsub_v1/subscriber/futures.py
@@ -46,5 +46,8 @@ def cancel(self):
         return self._manager.close()
 
     def cancelled(self):
-        """bool: True if the subscription has been cancelled."""
+        """
+        returns:
+            bool: ``True`` if the subscription has been cancelled.
+        """
         return self._cancelled


Test patch:
diff --git a/pubsub/tests/unit/pubsub_v1/publisher/test_futures_publisher.py b/pubsub/tests/unit/pubsub_v1/publisher/test_futures_publisher.py
new file mode 100644
--- /dev/null
+++ b/pubsub/tests/unit/pubsub_v1/publisher/test_futures_publisher.py
@@ -0,0 +1,32 @@
+# Copyright 2019, Google LLC All rights reserved.
+#
+# 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
+#
+#     http://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.
+
+from __future__ import absolute_import
+
+import pytest
+
+from google.cloud.pubsub_v1.publisher import futures
+
+
+class TestFuture(object):
+    def test_result_on_success(self):
+        future = futures.Future()
+        future.set_result("570307942214048")
+        assert future.result() == "570307942214048"
+
+    def test_result_on_failure(self):
+        future = futures.Future()
+        future.set_exception(RuntimeError("Something bad happened."))
+        with pytest.raises(RuntimeError):
+            future.result()