Skip to content

Commit ba02f24

Browse files
author
Jim Fulton
authored
feat: set the X-Server-Timeout header when timeout is set (#927)
Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [x] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/python-bigquery/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [x] Ensure the tests and linter pass - [x] Code coverage does not decrease (if any source code was changed) - [x] Appropriate docs were updated (if necessary) Fixes #919 🦕
1 parent b289076 commit ba02f24

File tree

3 files changed

+64
-9
lines changed

3 files changed

+64
-9
lines changed

google/cloud/bigquery/client.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@
131131
# https://github.com/googleapis/python-bigquery/issues/781#issuecomment-883497414
132132
_PYARROW_BAD_VERSIONS = frozenset([packaging.version.Version("2.0.0")])
133133

134+
TIMEOUT_HEADER = "X-Server-Timeout"
135+
134136

135137
class Project(object):
136138
"""Wrapper for resource describing a BigQuery project.
@@ -742,16 +744,26 @@ def create_table(
742744
return self.get_table(table.reference, retry=retry)
743745

744746
def _call_api(
745-
self, retry, span_name=None, span_attributes=None, job_ref=None, **kwargs
747+
self,
748+
retry,
749+
span_name=None,
750+
span_attributes=None,
751+
job_ref=None,
752+
headers: Optional[Dict[str, str]] = None,
753+
**kwargs,
746754
):
755+
kwargs = _add_server_timeout_header(headers, kwargs)
747756
call = functools.partial(self._connection.api_request, **kwargs)
757+
748758
if retry:
749759
call = retry(call)
760+
750761
if span_name is not None:
751762
with create_span(
752763
name=span_name, attributes=span_attributes, client=self, job_ref=job_ref
753764
):
754765
return call()
766+
755767
return call()
756768

757769
def get_dataset(
@@ -4045,3 +4057,16 @@ def _get_upload_headers(user_agent):
40454057
"User-Agent": user_agent,
40464058
"content-type": "application/json",
40474059
}
4060+
4061+
4062+
def _add_server_timeout_header(headers: Optional[Dict[str, str]], kwargs):
4063+
timeout = kwargs.get("timeout")
4064+
if timeout is not None:
4065+
if headers is None:
4066+
headers = {}
4067+
headers[TIMEOUT_HEADER] = str(timeout)
4068+
4069+
if headers:
4070+
kwargs["headers"] = headers
4071+
4072+
return kwargs

tests/unit/conftest.py

+19
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import mock
1516
import pytest
1617

1718
from .helpers import make_client
@@ -35,3 +36,21 @@ def DS_ID():
3536
@pytest.fixture
3637
def LOCATION():
3738
yield "us-central"
39+
40+
41+
def noop_add_server_timeout_header(headers, kwargs):
42+
if headers:
43+
kwargs["headers"] = headers
44+
return kwargs
45+
46+
47+
@pytest.fixture(autouse=True)
48+
def disable_add_server_timeout_header(request):
49+
if "enable_add_server_timeout_header" in request.keywords:
50+
yield
51+
else:
52+
with mock.patch(
53+
"google.cloud.bigquery.client._add_server_timeout_header",
54+
noop_add_server_timeout_header,
55+
):
56+
yield

tests/unit/test_client.py

+19-8
Original file line numberDiff line numberDiff line change
@@ -1806,7 +1806,6 @@ def test_update_dataset(self):
18061806
"access": ACCESS,
18071807
},
18081808
path="/" + PATH,
1809-
headers=None,
18101809
timeout=7.5,
18111810
)
18121811
self.assertEqual(ds2.description, ds.description)
@@ -1850,7 +1849,6 @@ def test_update_dataset_w_custom_property(self):
18501849
method="PATCH",
18511850
data={"newAlphaProperty": "unreleased property"},
18521851
path=path,
1853-
headers=None,
18541852
timeout=DEFAULT_TIMEOUT,
18551853
)
18561854

@@ -1909,7 +1907,7 @@ def test_update_model(self):
19091907
"labels": {"x": "y"},
19101908
}
19111909
conn.api_request.assert_called_once_with(
1912-
method="PATCH", data=sent, path="/" + path, headers=None, timeout=7.5
1910+
method="PATCH", data=sent, path="/" + path, timeout=7.5
19131911
)
19141912
self.assertEqual(updated_model.model_id, model.model_id)
19151913
self.assertEqual(updated_model.description, model.description)
@@ -1982,7 +1980,6 @@ def test_update_routine(self):
19821980
method="PUT",
19831981
data=sent,
19841982
path="/projects/routines-project/datasets/test_routines/routines/updated_routine",
1985-
headers=None,
19861983
timeout=7.5,
19871984
)
19881985
self.assertEqual(actual_routine.arguments, routine.arguments)
@@ -2090,7 +2087,7 @@ def test_update_table(self):
20902087
"labels": {"x": "y"},
20912088
}
20922089
conn.api_request.assert_called_once_with(
2093-
method="PATCH", data=sent, path="/" + path, headers=None, timeout=7.5
2090+
method="PATCH", data=sent, path="/" + path, timeout=7.5
20942091
)
20952092
self.assertEqual(updated_table.description, table.description)
20962093
self.assertEqual(updated_table.friendly_name, table.friendly_name)
@@ -2140,7 +2137,6 @@ def test_update_table_w_custom_property(self):
21402137
method="PATCH",
21412138
path="/%s" % path,
21422139
data={"newAlphaProperty": "unreleased property"},
2143-
headers=None,
21442140
timeout=DEFAULT_TIMEOUT,
21452141
)
21462142
self.assertEqual(
@@ -2175,7 +2171,6 @@ def test_update_table_only_use_legacy_sql(self):
21752171
method="PATCH",
21762172
path="/%s" % path,
21772173
data={"view": {"useLegacySql": True}},
2178-
headers=None,
21792174
timeout=DEFAULT_TIMEOUT,
21802175
)
21812176
self.assertEqual(updated_table.view_use_legacy_sql, table.view_use_legacy_sql)
@@ -2273,7 +2268,6 @@ def test_update_table_w_query(self):
22732268
"expirationTime": str(_millis(exp_time)),
22742269
"schema": schema_resource,
22752270
},
2276-
headers=None,
22772271
timeout=DEFAULT_TIMEOUT,
22782272
)
22792273

@@ -8173,3 +8167,20 @@ def transmit_next_chunk(transport):
81738167

81748168
chunk_size = RU.call_args_list[0][0][1]
81758169
assert chunk_size == 100 * (1 << 20)
8170+
8171+
8172+
@pytest.mark.enable_add_server_timeout_header
8173+
@pytest.mark.parametrize("headers", [None, {}])
8174+
def test__call_api_add_server_timeout_w_timeout(client, headers):
8175+
client._connection = make_connection({})
8176+
client._call_api(None, method="GET", path="/", headers=headers, timeout=42)
8177+
client._connection.api_request.assert_called_with(
8178+
method="GET", path="/", timeout=42, headers={"X-Server-Timeout": "42"}
8179+
)
8180+
8181+
8182+
@pytest.mark.enable_add_server_timeout_header
8183+
def test__call_api_no_add_server_timeout_wo_timeout(client):
8184+
client._connection = make_connection({})
8185+
client._call_api(None, method="GET", path="/")
8186+
client._connection.api_request.assert_called_with(method="GET", path="/")

0 commit comments

Comments
 (0)