Skip to content
This repository was archived by the owner on Mar 6, 2026. It is now read-only.

Commit 46f43a5

Browse files
authored
Merge branch 'main' into job-rate-limit
2 parents ffd6e38 + 6559dde commit 46f43a5

4 files changed

Lines changed: 108 additions & 20 deletions

File tree

google/cloud/bigquery/_job_helpers.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,14 @@ def do_query():
166166
return future
167167

168168

169+
def _validate_job_config(request_body: Dict[str, Any], invalid_key: str):
170+
"""Catch common mistakes, such as passing in a *JobConfig object of the
171+
wrong type.
172+
"""
173+
if invalid_key in request_body:
174+
raise ValueError(f"got unexpected key {repr(invalid_key)} in job_config")
175+
176+
169177
def _to_query_request(
170178
job_config: Optional[job.QueryJobConfig] = None,
171179
*,
@@ -179,17 +187,15 @@ def _to_query_request(
179187
QueryRequest. If any configuration property is set that is not available in
180188
jobs.query, it will result in a server-side error.
181189
"""
182-
request_body = {}
183-
job_config_resource = job_config.to_api_repr() if job_config else {}
184-
query_config_resource = job_config_resource.get("query", {})
190+
request_body = copy.copy(job_config.to_api_repr()) if job_config else {}
185191

186-
request_body.update(query_config_resource)
192+
_validate_job_config(request_body, job.CopyJob._JOB_TYPE)
193+
_validate_job_config(request_body, job.ExtractJob._JOB_TYPE)
194+
_validate_job_config(request_body, job.LoadJob._JOB_TYPE)
187195

188-
# These keys are top level in job resource and query resource.
189-
if "labels" in job_config_resource:
190-
request_body["labels"] = job_config_resource["labels"]
191-
if "dryRun" in job_config_resource:
192-
request_body["dryRun"] = job_config_resource["dryRun"]
196+
# Move query.* properties to top-level.
197+
query_config_resource = request_body.pop("query", {})
198+
request_body.update(query_config_resource)
193199

194200
# Default to standard SQL.
195201
request_body.setdefault("useLegacySql", False)

google/cloud/bigquery/magics/magics.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,15 @@ def _create_dataset_if_necessary(client, dataset_id):
508508
"Defaults to use tqdm_notebook. Install the ``tqdm`` package to use this feature."
509509
),
510510
)
511+
@magic_arguments.argument(
512+
"--location",
513+
type=str,
514+
default=None,
515+
help=(
516+
"Set the location to execute query."
517+
"Defaults to location set in query setting in console."
518+
),
519+
)
511520
def _cell_magic(line, query):
512521
"""Underlying function for bigquery cell magic
513522
@@ -551,6 +560,7 @@ def _cell_magic(line, query):
551560
category=DeprecationWarning,
552561
)
553562
use_bqstorage_api = not args.use_rest_api
563+
location = args.location
554564

555565
params = []
556566
if params_option_value:
@@ -579,6 +589,7 @@ def _cell_magic(line, query):
579589
default_query_job_config=context.default_query_job_config,
580590
client_info=client_info.ClientInfo(user_agent=IPYTHON_USER_AGENT),
581591
client_options=bigquery_client_options,
592+
location=location,
582593
)
583594
if context._connection:
584595
client._connection = context._connection

tests/unit/test__job_helpers.py

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323

2424
from google.cloud.bigquery.client import Client
2525
from google.cloud.bigquery import _job_helpers
26+
from google.cloud.bigquery.job import copy_ as job_copy
27+
from google.cloud.bigquery.job import extract as job_extract
28+
from google.cloud.bigquery.job import load as job_load
2629
from google.cloud.bigquery.job import query as job_query
2730
from google.cloud.bigquery.query import ConnectionProperty, ScalarQueryParameter
2831

@@ -57,9 +60,34 @@ def make_query_response(
5760
@pytest.mark.parametrize(
5861
("job_config", "expected"),
5962
(
60-
(None, make_query_request()),
61-
(job_query.QueryJobConfig(), make_query_request()),
62-
(
63+
pytest.param(
64+
None,
65+
make_query_request(),
66+
id="job_config=None-default-request",
67+
),
68+
pytest.param(
69+
job_query.QueryJobConfig(),
70+
make_query_request(),
71+
id="job_config=QueryJobConfig()-default-request",
72+
),
73+
pytest.param(
74+
job_query.QueryJobConfig.from_api_repr(
75+
{
76+
"unknownTopLevelProperty": "some-test-value",
77+
"query": {
78+
"unknownQueryProperty": "some-other-value",
79+
},
80+
},
81+
),
82+
make_query_request(
83+
{
84+
"unknownTopLevelProperty": "some-test-value",
85+
"unknownQueryProperty": "some-other-value",
86+
}
87+
),
88+
id="job_config-with-unknown-properties-includes-all-properties-in-request",
89+
),
90+
pytest.param(
6391
job_query.QueryJobConfig(default_dataset="my-project.my_dataset"),
6492
make_query_request(
6593
{
@@ -69,17 +97,24 @@ def make_query_response(
6997
}
7098
}
7199
),
100+
id="job_config-with-default_dataset",
72101
),
73-
(job_query.QueryJobConfig(dry_run=True), make_query_request({"dryRun": True})),
74-
(
102+
pytest.param(
103+
job_query.QueryJobConfig(dry_run=True),
104+
make_query_request({"dryRun": True}),
105+
id="job_config-with-dry_run",
106+
),
107+
pytest.param(
75108
job_query.QueryJobConfig(use_query_cache=False),
76109
make_query_request({"useQueryCache": False}),
110+
id="job_config-with-use_query_cache",
77111
),
78-
(
112+
pytest.param(
79113
job_query.QueryJobConfig(use_legacy_sql=True),
80114
make_query_request({"useLegacySql": True}),
115+
id="job_config-with-use_legacy_sql",
81116
),
82-
(
117+
pytest.param(
83118
job_query.QueryJobConfig(
84119
query_parameters=[
85120
ScalarQueryParameter("named_param1", "STRING", "param-value"),
@@ -103,8 +138,9 @@ def make_query_response(
103138
],
104139
}
105140
),
141+
id="job_config-with-query_parameters-named",
106142
),
107-
(
143+
pytest.param(
108144
job_query.QueryJobConfig(
109145
query_parameters=[
110146
ScalarQueryParameter(None, "STRING", "param-value"),
@@ -126,8 +162,9 @@ def make_query_response(
126162
],
127163
}
128164
),
165+
id="job_config-with-query_parameters-positional",
129166
),
130-
(
167+
pytest.param(
131168
job_query.QueryJobConfig(
132169
connection_properties=[
133170
ConnectionProperty(key="time_zone", value="America/Chicago"),
@@ -142,14 +179,17 @@ def make_query_response(
142179
]
143180
}
144181
),
182+
id="job_config-with-connection_properties",
145183
),
146-
(
184+
pytest.param(
147185
job_query.QueryJobConfig(labels={"abc": "def"}),
148186
make_query_request({"labels": {"abc": "def"}}),
187+
id="job_config-with-labels",
149188
),
150-
(
189+
pytest.param(
151190
job_query.QueryJobConfig(maximum_bytes_billed=987654),
152191
make_query_request({"maximumBytesBilled": "987654"}),
192+
id="job_config-with-maximum_bytes_billed",
153193
),
154194
),
155195
)
@@ -159,6 +199,19 @@ def test__to_query_request(job_config, expected):
159199
assert result == expected
160200

161201

202+
@pytest.mark.parametrize(
203+
("job_config", "invalid_key"),
204+
(
205+
pytest.param(job_copy.CopyJobConfig(), "copy", id="copy"),
206+
pytest.param(job_extract.ExtractJobConfig(), "extract", id="extract"),
207+
pytest.param(job_load.LoadJobConfig(), "load", id="load"),
208+
),
209+
)
210+
def test__to_query_request_raises_for_invalid_config(job_config, invalid_key):
211+
with pytest.raises(ValueError, match=f"{repr(invalid_key)} in job_config"):
212+
_job_helpers._to_query_request(job_config, query="SELECT 1")
213+
214+
162215
def test__to_query_job_defaults():
163216
mock_client = mock.create_autospec(Client)
164217
response = make_query_response(

tests/unit/test_magics.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2053,3 +2053,21 @@ def test_bigquery_magic_create_dataset_fails():
20532053
)
20542054

20552055
assert close_transports.called
2056+
2057+
2058+
@pytest.mark.usefixtures("ipython_interactive")
2059+
def test_bigquery_magic_with_location():
2060+
ip = IPython.get_ipython()
2061+
ip.extension_manager.load_extension("google.cloud.bigquery")
2062+
magics.context.credentials = mock.create_autospec(
2063+
google.auth.credentials.Credentials, instance=True
2064+
)
2065+
2066+
run_query_patch = mock.patch(
2067+
"google.cloud.bigquery.magics.magics._run_query", autospec=True
2068+
)
2069+
with run_query_patch as run_query_mock:
2070+
ip.run_cell_magic("bigquery", "--location=us-east1", "SELECT 17 AS num")
2071+
2072+
client_options_used = run_query_mock.call_args_list[0][0][0]
2073+
assert client_options_used.location == "us-east1"

0 commit comments

Comments
 (0)