Skip to content

Commit e77c29b

Browse files
authored
Merge branch 'master' into renovate/pyarrow-2.x
2 parents 1b4f96d + f9480dc commit e77c29b

File tree

8 files changed

+173
-74
lines changed

8 files changed

+173
-74
lines changed

google/cloud/bigquery/client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1534,7 +1534,7 @@ def _get_query_results(
15341534
A new ``_QueryResults`` instance.
15351535
"""
15361536

1537-
extra_params = {"maxResults": 0}
1537+
extra_params = {}
15381538

15391539
if project is None:
15401540
project = self.project
@@ -3187,6 +3187,7 @@ def _list_rows_from_query_results(
31873187
page_size=None,
31883188
retry=DEFAULT_RETRY,
31893189
timeout=None,
3190+
first_page_response=None,
31903191
):
31913192
"""List the rows of a completed query.
31923193
See
@@ -3247,6 +3248,7 @@ def _list_rows_from_query_results(
32473248
table=destination,
32483249
extra_params=params,
32493250
total_rows=total_rows,
3251+
first_page_response=first_page_response,
32503252
)
32513253
return row_iterator
32523254

google/cloud/bigquery/dataset.py

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,38 +79,47 @@ class AccessEntry(object):
7979
"""Represents grant of an access role to an entity.
8080
8181
An entry must have exactly one of the allowed :attr:`ENTITY_TYPES`. If
82-
anything but ``view`` is set, a ``role`` is also required. ``role`` is
83-
omitted for a ``view``, because ``view`` s are always read-only.
82+
anything but ``view`` or ``routine`` are set, a ``role`` is also required.
83+
``role`` is omitted for ``view`` and ``routine``, because they are always
84+
read-only.
8485
8586
See https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets.
8687
8788
Args:
8889
role (str):
8990
Role granted to the entity. The following string values are
9091
supported: `'READER'`, `'WRITER'`, `'OWNER'`. It may also be
91-
:data:`None` if the ``entity_type`` is ``view``.
92+
:data:`None` if the ``entity_type`` is ``view`` or ``routine``.
9293
9394
entity_type (str):
9495
Type of entity being granted the role. One of :attr:`ENTITY_TYPES`.
9596
9697
entity_id (Union[str, Dict[str, str]]):
97-
If the ``entity_type`` is not 'view', the ``entity_id`` is the
98-
``str`` ID of the entity being granted the role. If the
99-
``entity_type`` is 'view', the ``entity_id`` is a ``dict``
100-
representing the view from a different dataset to grant access to
101-
in the following format::
98+
If the ``entity_type`` is not 'view' or 'routine', the ``entity_id``
99+
is the ``str`` ID of the entity being granted the role. If the
100+
``entity_type`` is 'view' or 'routine', the ``entity_id`` is a ``dict``
101+
representing the view or routine from a different dataset to grant
102+
access to in the following format for views::
102103
103104
{
104105
'projectId': string,
105106
'datasetId': string,
106107
'tableId': string
107108
}
108109
110+
For routines::
111+
112+
{
113+
'projectId': string,
114+
'datasetId': string,
115+
'routineId': string
116+
}
117+
109118
Raises:
110119
ValueError:
111120
If the ``entity_type`` is not among :attr:`ENTITY_TYPES`, or if a
112-
``view`` has ``role`` set, or a non ``view`` **does not** have a
113-
``role`` set.
121+
``view`` or a ``routine`` has ``role`` set, or a non ``view`` and
122+
non ``routine`` **does not** have a ``role`` set.
114123
115124
Examples:
116125
>>> entry = AccessEntry('OWNER', 'userByEmail', '[email protected]')
@@ -124,7 +133,15 @@ class AccessEntry(object):
124133
"""
125134

126135
ENTITY_TYPES = frozenset(
127-
["userByEmail", "groupByEmail", "domain", "specialGroup", "view", "iamMember"]
136+
[
137+
"userByEmail",
138+
"groupByEmail",
139+
"domain",
140+
"specialGroup",
141+
"view",
142+
"iamMember",
143+
"routine",
144+
]
128145
)
129146
"""Allowed entity types."""
130147

@@ -135,10 +152,11 @@ def __init__(self, role, entity_type, entity_id):
135152
", ".join(self.ENTITY_TYPES),
136153
)
137154
raise ValueError(message)
138-
if entity_type == "view":
155+
if entity_type in ("view", "routine"):
139156
if role is not None:
140157
raise ValueError(
141-
"Role must be None for a view. Received " "role: %r" % (role,)
158+
"Role must be None for a %r. Received "
159+
"role: %r" % (entity_type, role)
142160
)
143161
else:
144162
if role is None:
@@ -409,7 +427,7 @@ def access_entries(self):
409427
entries.
410428
411429
``role`` augments the entity type and must be present **unless** the
412-
entity type is ``view``.
430+
entity type is ``view`` or ``routine``.
413431
414432
Raises:
415433
TypeError: If 'value' is not a sequence

google/cloud/bigquery/job/query.py

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -990,48 +990,22 @@ def done(self, retry=DEFAULT_RETRY, timeout=None, reload=True):
990990
Returns:
991991
bool: True if the job is complete, False otherwise.
992992
"""
993-
is_done = (
994-
# Only consider a QueryJob complete when we know we have the final
995-
# query results available.
996-
self._query_results is not None
997-
and self._query_results.complete
998-
and self.state == _DONE_STATE
999-
)
1000993
# Do not refresh if the state is already done, as the job will not
1001994
# change once complete.
995+
is_done = self.state == _DONE_STATE
1002996
if not reload or is_done:
1003997
return is_done
1004998

1005-
# Since the API to getQueryResults can hang up to the timeout value
1006-
# (default of 10 seconds), set the timeout parameter to ensure that
1007-
# the timeout from the futures API is respected. See:
1008-
# https://github.com/GoogleCloudPlatform/google-cloud-python/issues/4135
1009-
timeout_ms = None
1010-
if self._done_timeout is not None:
1011-
# Subtract a buffer for context switching, network latency, etc.
1012-
api_timeout = self._done_timeout - _TIMEOUT_BUFFER_SECS
1013-
api_timeout = max(min(api_timeout, 10), 0)
1014-
self._done_timeout -= api_timeout
1015-
self._done_timeout = max(0, self._done_timeout)
1016-
timeout_ms = int(api_timeout * 1000)
999+
self._reload_query_results(retry=retry, timeout=timeout)
10171000

10181001
# If an explicit timeout is not given, fall back to the transport timeout
10191002
# stored in _blocking_poll() in the process of polling for job completion.
10201003
transport_timeout = timeout if timeout is not None else self._transport_timeout
10211004

1022-
self._query_results = self._client._get_query_results(
1023-
self.job_id,
1024-
retry,
1025-
project=self.project,
1026-
timeout_ms=timeout_ms,
1027-
location=self.location,
1028-
timeout=transport_timeout,
1029-
)
1030-
10311005
# Only reload the job once we know the query is complete.
10321006
# This will ensure that fields such as the destination table are
10331007
# correctly populated.
1034-
if self._query_results.complete and self.state != _DONE_STATE:
1008+
if self._query_results.complete:
10351009
self.reload(retry=retry, timeout=transport_timeout)
10361010

10371011
return self.state == _DONE_STATE
@@ -1098,6 +1072,45 @@ def _begin(self, client=None, retry=DEFAULT_RETRY, timeout=None):
10981072
exc.query_job = self
10991073
raise
11001074

1075+
def _reload_query_results(self, retry=DEFAULT_RETRY, timeout=None):
1076+
"""Refresh the cached query results.
1077+
1078+
Args:
1079+
retry (Optional[google.api_core.retry.Retry]):
1080+
How to retry the call that retrieves query results.
1081+
timeout (Optional[float]):
1082+
The number of seconds to wait for the underlying HTTP transport
1083+
before using ``retry``.
1084+
"""
1085+
if self._query_results and self._query_results.complete:
1086+
return
1087+
1088+
# Since the API to getQueryResults can hang up to the timeout value
1089+
# (default of 10 seconds), set the timeout parameter to ensure that
1090+
# the timeout from the futures API is respected. See:
1091+
# https://github.com/GoogleCloudPlatform/google-cloud-python/issues/4135
1092+
timeout_ms = None
1093+
if self._done_timeout is not None:
1094+
# Subtract a buffer for context switching, network latency, etc.
1095+
api_timeout = self._done_timeout - _TIMEOUT_BUFFER_SECS
1096+
api_timeout = max(min(api_timeout, 10), 0)
1097+
self._done_timeout -= api_timeout
1098+
self._done_timeout = max(0, self._done_timeout)
1099+
timeout_ms = int(api_timeout * 1000)
1100+
1101+
# If an explicit timeout is not given, fall back to the transport timeout
1102+
# stored in _blocking_poll() in the process of polling for job completion.
1103+
transport_timeout = timeout if timeout is not None else self._transport_timeout
1104+
1105+
self._query_results = self._client._get_query_results(
1106+
self.job_id,
1107+
retry,
1108+
project=self.project,
1109+
timeout_ms=timeout_ms,
1110+
location=self.location,
1111+
timeout=transport_timeout,
1112+
)
1113+
11011114
def result(
11021115
self,
11031116
page_size=None,
@@ -1144,6 +1157,11 @@ def result(
11441157
"""
11451158
try:
11461159
super(QueryJob, self).result(retry=retry, timeout=timeout)
1160+
1161+
# Since the job could already be "done" (e.g. got a finished job
1162+
# via client.get_job), the superclass call to done() might not
1163+
# set the self._query_results cache.
1164+
self._reload_query_results(retry=retry, timeout=timeout)
11471165
except exceptions.GoogleAPICallError as exc:
11481166
exc.message += self._format_for_exception(self.query, self.job_id)
11491167
exc.query_job = self
@@ -1158,10 +1176,14 @@ def result(
11581176
if self._query_results.total_rows is None:
11591177
return _EmptyRowIterator()
11601178

1179+
first_page_response = None
1180+
if max_results is None and page_size is None and start_index is None:
1181+
first_page_response = self._query_results._properties
1182+
11611183
rows = self._client._list_rows_from_query_results(
1162-
self._query_results.job_id,
1184+
self.job_id,
11631185
self.location,
1164-
self._query_results.project,
1186+
self.project,
11651187
self._query_results.schema,
11661188
total_rows=self._query_results.total_rows,
11671189
destination=self.destination,
@@ -1170,6 +1192,7 @@ def result(
11701192
start_index=start_index,
11711193
retry=retry,
11721194
timeout=timeout,
1195+
first_page_response=first_page_response,
11731196
)
11741197
rows._preserve_order = _contains_order_by(self.query)
11751198
return rows

google/cloud/bigquery/table.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1308,7 +1308,9 @@ class RowIterator(HTTPIterator):
13081308
A subset of columns to select from this table.
13091309
total_rows (Optional[int]):
13101310
Total number of rows in the table.
1311-
1311+
first_page_response (Optional[dict]):
1312+
API response for the first page of results. These are returned when
1313+
the first page is requested.
13121314
"""
13131315

13141316
def __init__(
@@ -1324,6 +1326,7 @@ def __init__(
13241326
table=None,
13251327
selected_fields=None,
13261328
total_rows=None,
1329+
first_page_response=None,
13271330
):
13281331
super(RowIterator, self).__init__(
13291332
client,
@@ -1346,6 +1349,7 @@ def __init__(
13461349
self._selected_fields = selected_fields
13471350
self._table = table
13481351
self._total_rows = total_rows
1352+
self._first_page_response = first_page_response
13491353

13501354
def _get_next_page_response(self):
13511355
"""Requests the next page from the path provided.
@@ -1354,6 +1358,11 @@ def _get_next_page_response(self):
13541358
Dict[str, object]:
13551359
The parsed JSON response of the next page's contents.
13561360
"""
1361+
if self._first_page_response:
1362+
response = self._first_page_response
1363+
self._first_page_response = None
1364+
return response
1365+
13571366
params = self._get_query_params()
13581367
if self._page_size is not None:
13591368
if self.page_number and "startIndex" in params:

0 commit comments

Comments
 (0)