Skip to content

Commit 3bb280e

Browse files
authored
Merge branch 'main' into refactor-load-table-methods
2 parents 4d71547 + 7cad6cf commit 3bb280e

25 files changed

+400
-111
lines changed

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,31 @@
55
[1]: https://pypi.org/project/google-cloud-bigquery/#history
66

77

8+
## [3.38.0](https://github.com/googleapis/python-bigquery/compare/v3.37.0...v3.38.0) (2025-09-15)
9+
10+
11+
### Features
12+
13+
* Add additional query stats ([#2270](https://github.com/googleapis/python-bigquery/issues/2270)) ([7b1b718](https://github.com/googleapis/python-bigquery/commit/7b1b718123afd80c0f68212946e4179bcd6db67f))
14+
15+
## [3.37.0](https://github.com/googleapis/python-bigquery/compare/v3.36.0...v3.37.0) (2025-09-08)
16+
17+
18+
### Features
19+
20+
* Updates to fastpath query execution ([#2268](https://github.com/googleapis/python-bigquery/issues/2268)) ([ef2740a](https://github.com/googleapis/python-bigquery/commit/ef2740a158199633b5543a7b6eb19587580792cd))
21+
22+
23+
### Bug Fixes
24+
25+
* Remove deepcopy while setting properties for _QueryResults ([#2280](https://github.com/googleapis/python-bigquery/issues/2280)) ([33ea296](https://github.com/googleapis/python-bigquery/commit/33ea29616c06a2e2a106a785d216e784737ae386))
26+
27+
28+
### Documentation
29+
30+
* Clarify that the presence of `XyzJob.errors` doesn't necessarily mean that the job has not completed or was unsuccessful ([#2278](https://github.com/googleapis/python-bigquery/issues/2278)) ([6e88d7d](https://github.com/googleapis/python-bigquery/commit/6e88d7dbe42ebfc35986da665d656b49ac481db4))
31+
* Clarify the api_method arg for client.query() ([#2277](https://github.com/googleapis/python-bigquery/issues/2277)) ([8a13c12](https://github.com/googleapis/python-bigquery/commit/8a13c12905ffcb3dbb6086a61df37556f0c2cd31))
32+
833
## [3.36.0](https://github.com/googleapis/python-bigquery/compare/v3.35.1...v3.36.0) (2025-08-20)
934

1035

google/cloud/bigquery/_job_helpers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,9 @@ def _supported_by_jobs_query(request_body: Dict[str, Any]) -> bool:
658658
"requestId",
659659
"createSession",
660660
"writeIncrementalResults",
661+
"jobTimeoutMs",
662+
"reservation",
663+
"maxSlots",
661664
}
662665

663666
unsupported_keys = request_keys - keys_allowlist

google/cloud/bigquery/client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3524,7 +3524,8 @@ def query(
35243524
specified here becomes the default ``job_retry`` for
35253525
``result()``, where it can also be specified.
35263526
api_method (Union[str, enums.QueryApiMethod]):
3527-
Method with which to start the query job.
3527+
Method with which to start the query job. By default,
3528+
the jobs.insert API is used for starting a query.
35283529
35293530
See :class:`google.cloud.bigquery.enums.QueryApiMethod` for
35303531
details on the difference between the query start methods.

google/cloud/bigquery/job/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from google.cloud.bigquery.job.query import QueryPlanEntryStep
4040
from google.cloud.bigquery.job.query import ScriptOptions
4141
from google.cloud.bigquery.job.query import TimelineEntry
42+
from google.cloud.bigquery.job.query import IncrementalResultStats
4243
from google.cloud.bigquery.enums import Compression
4344
from google.cloud.bigquery.enums import CreateDisposition
4445
from google.cloud.bigquery.enums import DestinationFormat
@@ -84,4 +85,5 @@
8485
"SourceFormat",
8586
"TransactionInfo",
8687
"WriteDisposition",
88+
"IncrementalResultStats",
8789
]

google/cloud/bigquery/job/base.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,37 @@ def job_timeout_ms(self, value):
224224
else:
225225
self._properties.pop("jobTimeoutMs", None)
226226

227+
@property
228+
def max_slots(self) -> Optional[int]:
229+
"""The maximum rate of slot consumption to allow for this job.
230+
231+
If set, the number of slots used to execute the job will be throttled
232+
to try and keep its slot consumption below the requested rate.
233+
This feature is not generally available.
234+
"""
235+
236+
max_slots = self._properties.get("maxSlots")
237+
if max_slots is not None:
238+
if isinstance(max_slots, str):
239+
return int(max_slots)
240+
if isinstance(max_slots, int):
241+
return max_slots
242+
return None
243+
244+
@max_slots.setter
245+
def max_slots(self, value):
246+
try:
247+
value = _int_or_none(value)
248+
except ValueError as err:
249+
raise ValueError("Pass an int for max slots, e.g. 100").with_traceback(
250+
err.__traceback__
251+
)
252+
253+
if value is not None:
254+
self._properties["maxSlots"] = str(value)
255+
else:
256+
self._properties.pop("maxSlots", None)
257+
227258
@property
228259
def reservation(self):
229260
"""str: Optional. The reservation that job would use.
@@ -662,7 +693,12 @@ def transaction_info(self) -> Optional[TransactionInfo]:
662693

663694
@property
664695
def error_result(self):
665-
"""Error information about the job as a whole.
696+
"""Output only. Final error result of the job.
697+
698+
If present, indicates that the job has completed and was unsuccessful.
699+
700+
See:
701+
https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobStatus.FIELDS.error_result
666702
667703
Returns:
668704
Optional[Mapping]: the error information (None until set from the server).
@@ -673,7 +709,13 @@ def error_result(self):
673709

674710
@property
675711
def errors(self):
676-
"""Information about individual errors generated by the job.
712+
"""Output only. The first errors encountered during the running of the job.
713+
714+
The final message includes the number of errors that caused the process to stop.
715+
Errors here do not necessarily mean that the job has not completed or was unsuccessful.
716+
717+
See:
718+
https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobStatus.FIELDS.errors
677719
678720
Returns:
679721
Optional[List[Mapping]]:
@@ -685,7 +727,12 @@ def errors(self):
685727

686728
@property
687729
def state(self):
688-
"""Status of the job.
730+
"""Output only. Running state of the job.
731+
732+
Valid states include 'PENDING', 'RUNNING', and 'DONE'.
733+
734+
See:
735+
https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobStatus.FIELDS.state
689736
690737
Returns:
691738
Optional[str]:

google/cloud/bigquery/job/query.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,66 @@ def from_api_repr(cls, stats: Dict[str, str]) -> "DmlStats":
197197
return cls(*args)
198198

199199

200+
class IncrementalResultStats:
201+
"""IncrementalResultStats provides information about incremental query execution."""
202+
203+
def __init__(self):
204+
self._properties = {}
205+
206+
@classmethod
207+
def from_api_repr(cls, resource) -> "IncrementalResultStats":
208+
"""Factory: construct instance from the JSON repr.
209+
210+
Args:
211+
resource(Dict[str: object]):
212+
IncrementalResultStats representation returned from API.
213+
214+
Returns:
215+
google.cloud.bigquery.job.IncrementalResultStats:
216+
stats parsed from ``resource``.
217+
"""
218+
entry = cls()
219+
entry._properties = resource
220+
return entry
221+
222+
@property
223+
def disabled_reason(self):
224+
"""Optional[string]: Reason why incremental results were not
225+
written by the query.
226+
"""
227+
return _helpers._str_or_none(self._properties.get("disabledReason"))
228+
229+
@property
230+
def result_set_last_replace_time(self):
231+
"""Optional[datetime]: The time at which the result table's contents
232+
were completely replaced. May be absent if no results have been written
233+
or the query has completed."""
234+
from google.cloud._helpers import _rfc3339_nanos_to_datetime
235+
236+
value = self._properties.get("resultSetLastReplaceTime")
237+
if value:
238+
try:
239+
return _rfc3339_nanos_to_datetime(value)
240+
except ValueError:
241+
pass
242+
return None
243+
244+
@property
245+
def result_set_last_modify_time(self):
246+
"""Optional[datetime]: The time at which the result table's contents
247+
were modified. May be absent if no results have been written or the
248+
query has completed."""
249+
from google.cloud._helpers import _rfc3339_nanos_to_datetime
250+
251+
value = self._properties.get("resultSetLastModifyTime")
252+
if value:
253+
try:
254+
return _rfc3339_nanos_to_datetime(value)
255+
except ValueError:
256+
pass
257+
return None
258+
259+
200260
class IndexUnusedReason(typing.NamedTuple):
201261
"""Reason about why no search index was used in the search query (or sub-query).
202262
@@ -1339,6 +1399,13 @@ def bi_engine_stats(self) -> Optional[BiEngineStats]:
13391399
else:
13401400
return BiEngineStats.from_api_repr(stats)
13411401

1402+
@property
1403+
def incremental_result_stats(self) -> Optional[IncrementalResultStats]:
1404+
stats = self._job_statistics().get("incrementalResultStats")
1405+
if stats is None:
1406+
return None
1407+
return IncrementalResultStats.from_api_repr(stats)
1408+
13421409
def _blocking_poll(self, timeout=None, **kwargs):
13431410
self._done_timeout = timeout
13441411
self._transport_timeout = timeout

google/cloud/bigquery/query.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1400,7 +1400,7 @@ def _set_properties(self, api_response):
14001400
api_response (Dict): Response returned from an API call
14011401
"""
14021402
self._properties.clear()
1403-
self._properties.update(copy.deepcopy(api_response))
1403+
self._properties.update(api_response)
14041404

14051405

14061406
def _query_param_from_api_repr(resource):

google/cloud/bigquery/table.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3574,9 +3574,9 @@ def __init__(
35743574
def __eq__(self, other):
35753575
if not isinstance(other, TableConstraints) and other is not None:
35763576
raise TypeError("The value provided is not a BigQuery TableConstraints.")
3577-
return (
3578-
self.primary_key == other.primary_key if other.primary_key else None
3579-
) and (self.foreign_keys == other.foreign_keys if other.foreign_keys else None)
3577+
return self.primary_key == (
3578+
other.primary_key if other.primary_key else None
3579+
) and self.foreign_keys == (other.foreign_keys if other.foreign_keys else None)
35803580

35813581
@classmethod
35823582
def from_api_repr(cls, resource: Dict[str, Any]) -> "TableConstraints":

google/cloud/bigquery/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
__version__ = "3.36.0"
15+
__version__ = "3.38.0"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
google-cloud-testutils==1.6.4
2-
pytest==8.4.1
2+
pytest==8.4.2
33
mock==5.2.0
44
pytest-xdist==3.8.0

0 commit comments

Comments
 (0)