Repository: googleapis/python-bigquery

Issue:
PollingFuture default timeout value `object()` incompatible with QueryJob typeguard in `_reload_query_results`
`google.api_core.future.polling.PollingFuture` default timeout value `object()` incompatible with `google.cloud.bigquery.job.query.QueryJob` typeguard in `_reload_query_results`.

A refactor in `google-api-core` introduced the global `_DEFAULT_VALUE` in the PollingFuture class ->
[here](https://github.com/googleapis/python-api-core/blob/a152ed60eeb876f9b655716f0d720a85c5680a23/google/api_core/future/polling.py#L89).
This change is incompatible with google-cloud-bigquery typeguard [here](https://github.com/googleapis/python-bigquery/blob/b8502a6641b653610643aeb38992d330823feb94/google/cloud/bigquery/job/query.py#L1327) and becomes invalid and enters the `if` context which results in a error on this [line](https://github.com/googleapis/python-bigquery/blob/b8502a6641b653610643aeb38992d330823feb94/google/cloud/bigquery/job/query.py#L1329).

```
TypeError: unsupported operand type(s) for -: 'object' and 'float'
```
The typeguard is based on the fact that the default value is None and not object().

As discussed [here](https://github.com/googleapis/python-api-core/issues/479) with @vam-google, 

> The default value cannot be None and the previous behavior of api-core was a bug. The gist of it is the need to distinguish between infinite timeout (None) and not specified timeout (_DEFAULT_TIMEOUT).

Stack trace
```
/usr/local/lib/python3.9/site-packages/feast/infra/offline_stores/bigquery.py:583: in block_until_done
--
  | if bq_job.exception():
  | /usr/local/lib/python3.9/site-packages/google/api_core/future/polling.py:282: in exception
  | self._blocking_poll(timeout=timeout)
  | /usr/local/lib/python3.9/site-packages/google/cloud/bigquery/job/query.py:1245: in _blocking_poll
  | super(QueryJob, self)._blocking_poll(timeout=timeout, **kwargs)
  | /usr/local/lib/python3.9/site-packages/google/api_core/future/polling.py:137: in _blocking_poll
  | polling(self._done_or_raise)(retry=retry)
  | /usr/local/lib/python3.9/site-packages/google/api_core/retry.py:349: in retry_wrapped_func
  | return retry_target(
  | /usr/local/lib/python3.9/site-packages/google/api_core/retry.py:191: in retry_target
  | return target()
  | /usr/local/lib/python3.9/site-packages/google/cloud/bigquery/job/query.py:1358: in _done_or_raise
  | self._reload_query_results(retry=retry, timeout=transport_timeout)
  | _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
  |  
  | self = QueryJob<project=..., location=US, id=...>
  | retry = None, timeout = <object object at 0x7fa46677c5c0>
  |  
  | def _reload_query_results(
  | self, retry: "retries.Retry" = DEFAULT_RETRY, timeout: float = None
  | ):
  | """Refresh the cached query results.
  |  
  | Args:
  | retry (Optional[google.api_core.retry.Retry]):
  | How to retry the call that retrieves query results.
  | timeout (Optional[float]):
  | The number of seconds to wait for the underlying HTTP transport
  | before using ``retry``.
  | """
  | if self._query_results and self._query_results.complete:
  | return
  |  
  | # Since the API to getQueryResults can hang up to the timeout value
  | # (default of 10 seconds), set the timeout parameter to ensure that
  | # the timeout from the futures API is respected. See:
  | # https://github.com/GoogleCloudPlatform/google-cloud-python/issues/4135
  | timeout_ms = None
  | if self._done_timeout is not None:
  | # Subtract a buffer for context switching, network latency, etc.
  | >           api_timeout = self._done_timeout - _TIMEOUT_BUFFER_SECS
  | E           TypeError: unsupported operand type(s) for -: 'object' and 'float'
  |  
  | /usr/local/lib/python3.9/site-packages/google/cloud/bigquery/job/query.py:1329: TypeError
```

Thanks!



Discussion:

Resolving patch:
diff --git a/google/cloud/bigquery/job/query.py b/google/cloud/bigquery/job/query.py
--- a/google/cloud/bigquery/job/query.py
+++ b/google/cloud/bigquery/job/query.py
@@ -764,7 +764,6 @@ def __init__(self, job_id, query, client, job_config=None):
             _helpers._set_sub_prop(
                 self._properties, ["configuration", "query", "query"], query
             )
-
         self._query_results = None
         self._done_timeout = None
         self._transport_timeout = None
@@ -1332,6 +1331,15 @@ def _reload_query_results(
         # the timeout from the futures API is respected. See:
         # https://github.com/GoogleCloudPlatform/google-cloud-python/issues/4135
         timeout_ms = None
+
+        # Python_API_core, as part of a major rewrite of the deadline, timeout,
+        # retry process sets the timeout value as a Python object().
+        # Our system does not natively handle that and instead expects
+        # either none or a numeric value. If passed a Python object, convert to
+        # None.
+        if type(self._done_timeout) == object:  # pragma: NO COVER
+            self._done_timeout = None
+
         if self._done_timeout is not None:
             # Subtract a buffer for context switching, network latency, etc.
             api_timeout = self._done_timeout - _TIMEOUT_BUFFER_SECS


Test patch: