Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: QueryJob.exception() *returns* the errors, not raises them #467

Merged
merged 8 commits into from Feb 25, 2021
20 changes: 17 additions & 3 deletions google/cloud/bigquery/job/query.py
Expand Up @@ -988,15 +988,25 @@ def done(self, retry=DEFAULT_RETRY, timeout=None, reload=True):
unfinished jobs before checking. Default ``True``.

Returns:
bool: True if the job is complete, False otherwise.
bool: True if the job is complete, False otherwise. If job status
cannot be determined due to a non-retryable error or too many
retries, the method returns ``True``.
"""
# Do not refresh if the state is already done, as the job will not
# change once complete.
is_done = self.state == _DONE_STATE
if not reload or is_done:
return is_done

self._reload_query_results(retry=retry, timeout=timeout)
try:
self._reload_query_results(retry=retry, timeout=timeout)
except Exception as exc:
# The job state might still be "RUNNING", but if an exception bubbles
# up (either a RetryError or a non-retryable error), we declare that we
# are done for good (and the blocking pull should not continue polling).
# Ditto in a similar try-except block below.
self.set_exception(exc)
return True
plamut marked this conversation as resolved.
Show resolved Hide resolved

# If an explicit timeout is not given, fall back to the transport timeout
# stored in _blocking_poll() in the process of polling for job completion.
Expand All @@ -1006,7 +1016,11 @@ def done(self, retry=DEFAULT_RETRY, timeout=None, reload=True):
# This will ensure that fields such as the destination table are
# correctly populated.
if self._query_results.complete:
self.reload(retry=retry, timeout=transport_timeout)
try:
self.reload(retry=retry, timeout=transport_timeout)
except Exception as exc:
self.set_exception(exc)
return True

return self.state == _DONE_STATE

Expand Down
36 changes: 36 additions & 0 deletions tests/unit/job/test_query.py
Expand Up @@ -356,6 +356,42 @@ def test_done_w_timeout_and_longer_internal_api_timeout(self):
call_args = fake_reload.call_args
self.assertAlmostEqual(call_args.kwargs.get("timeout"), expected_timeout)

def test_done_w_query_results_error(self):
client = _make_client(project=self.PROJECT)
bad_request_error = exceptions.BadRequest("Error in query")
client._get_query_results = mock.Mock(side_effect=bad_request_error)

resource = self._make_resource(ended=False)
job = self._get_target_class().from_api_repr(resource, client)
job.reload = mock.Mock(side_effect=exceptions.RetryError)
job._exception = None

is_done = job.done()

assert is_done
assert job._exception is bad_request_error

def test_done_w_job_reload_error(self):
client = _make_client(project=self.PROJECT)
query_results = google.cloud.bigquery.query._QueryResults(
properties={
"jobComplete": True,
"jobReference": {"projectId": self.PROJECT, "jobId": "12345"},
}
)
client._get_query_results = mock.Mock(return_value=query_results)

resource = self._make_resource(ended=False)
job = self._get_target_class().from_api_repr(resource, client)
retry_error = exceptions.RetryError("Too many retries", cause=TimeoutError)
job.reload = mock.Mock(side_effect=retry_error)
job._exception = None

is_done = job.done()

assert is_done
assert job._exception is retry_error

def test_query_plan(self):
from google.cloud._helpers import _RFC3339_MICROS
from google.cloud.bigquery.job import QueryPlanEntry
Expand Down