Skip to content

Commit 627980d

Browse files
authored
feature: add 'command' argument to private upload/download interface (#1082)
* Refactor client.download_blob_to_file * Chore: clean up code * refactor blob and client unit tests * lint reformat * Rename _prep_and_do_download * Refactor blob.upload_from_file * Lint reformat * feature: add 'command' argument to private upload/download interface * lint reformat * reduce duplication and edit docstring
1 parent 666e06d commit 627980d

File tree

3 files changed

+83
-13
lines changed

3 files changed

+83
-13
lines changed

google/cloud/storage/_helpers.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -599,19 +599,30 @@ def _get_default_headers(
599599
user_agent,
600600
content_type="application/json; charset=UTF-8",
601601
x_upload_content_type=None,
602+
command=None,
602603
):
603604
"""Get the headers for a request.
604605
605-
Args:
606-
user_agent (str): The user-agent for requests.
607-
Returns:
608-
Dict: The headers to be used for the request.
606+
:type user_agent: str
607+
:param user_agent: The user-agent for requests.
608+
609+
:type command: str
610+
:param user_agent:
611+
(Optional) Information about which interface for upload/download was used, to be included in the X-Goog-API-Client header for traffic analysis purposes. Please leave as None unless otherwise directed.
612+
613+
:rtype: dict
614+
:returns: The headers to be used for the request.
609615
"""
616+
x_goog_api_client = f"{user_agent} {_get_invocation_id()}"
617+
618+
if command:
619+
x_goog_api_client += f" gccl-gcs-cmd/{command}"
620+
610621
return {
611622
"Accept": "application/json",
612623
"Accept-Encoding": "gzip, deflate",
613624
"User-Agent": user_agent,
614-
"X-Goog-API-Client": f"{user_agent} {_get_invocation_id()}",
625+
"X-Goog-API-Client": x_goog_api_client,
615626
"content-type": content_type,
616627
"x-upload-content-type": x_upload_content_type or content_type,
617628
}

google/cloud/storage/blob.py

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1697,7 +1697,7 @@ def _get_writable_metadata(self):
16971697

16981698
return object_metadata
16991699

1700-
def _get_upload_arguments(self, client, content_type):
1700+
def _get_upload_arguments(self, client, content_type, command=None):
17011701
"""Get required arguments for performing an upload.
17021702
17031703
The content type returned will be determined in order of precedence:
@@ -1709,6 +1709,10 @@ def _get_upload_arguments(self, client, content_type):
17091709
:type content_type: str
17101710
:param content_type: Type of content being uploaded (or :data:`None`).
17111711
1712+
:type command: str
1713+
:param command:
1714+
(Optional) Information about which interface for upload was used, to be included in the X-Goog-API-Client header for traffic analysis purposes. Please leave as None unless otherwise directed.
1715+
17121716
:rtype: tuple
17131717
:returns: A triple of
17141718
@@ -1718,7 +1722,9 @@ def _get_upload_arguments(self, client, content_type):
17181722
"""
17191723
content_type = self._get_content_type(content_type)
17201724
headers = {
1721-
**_get_default_headers(client._connection.user_agent, content_type),
1725+
**_get_default_headers(
1726+
client._connection.user_agent, content_type, command=command
1727+
),
17221728
**_get_encryption_headers(self._encryption_key),
17231729
}
17241730
object_metadata = self._get_writable_metadata()
@@ -1739,6 +1745,7 @@ def _do_multipart_upload(
17391745
timeout=_DEFAULT_TIMEOUT,
17401746
checksum=None,
17411747
retry=None,
1748+
command=None,
17421749
):
17431750
"""Perform a multipart upload.
17441751
@@ -1822,6 +1829,10 @@ def _do_multipart_upload(
18221829
(google.cloud.storage.retry) for information on retry types and how
18231830
to configure them.
18241831
1832+
:type command: str
1833+
:param command:
1834+
(Optional) Information about which interface for upload was used, to be included in the X-Goog-API-Client header for traffic analysis purposes. Please leave as None unless otherwise directed.
1835+
18251836
:rtype: :class:`~requests.Response`
18261837
:returns: The "200 OK" response object returned after the multipart
18271838
upload request.
@@ -1840,7 +1851,7 @@ def _do_multipart_upload(
18401851
transport = self._get_transport(client)
18411852
if "metadata" in self._properties and "metadata" not in self._changes:
18421853
self._changes.add("metadata")
1843-
info = self._get_upload_arguments(client, content_type)
1854+
info = self._get_upload_arguments(client, content_type, command=command)
18441855
headers, object_metadata, content_type = info
18451856

18461857
hostname = _get_host_name(client._connection)
@@ -1910,6 +1921,7 @@ def _initiate_resumable_upload(
19101921
timeout=_DEFAULT_TIMEOUT,
19111922
checksum=None,
19121923
retry=None,
1924+
command=None,
19131925
):
19141926
"""Initiate a resumable upload.
19151927
@@ -2008,6 +2020,10 @@ def _initiate_resumable_upload(
20082020
(google.cloud.storage.retry) for information on retry types and how
20092021
to configure them.
20102022
2023+
:type command: str
2024+
:param command:
2025+
(Optional) Information about which interface for upload was used, to be included in the X-Goog-API-Client header for traffic analysis purposes. Please leave as None unless otherwise directed.
2026+
20112027
:rtype: tuple
20122028
:returns:
20132029
Pair of
@@ -2025,7 +2041,7 @@ def _initiate_resumable_upload(
20252041
transport = self._get_transport(client)
20262042
if "metadata" in self._properties and "metadata" not in self._changes:
20272043
self._changes.add("metadata")
2028-
info = self._get_upload_arguments(client, content_type)
2044+
info = self._get_upload_arguments(client, content_type, command=command)
20292045
headers, object_metadata, content_type = info
20302046
if extra_headers is not None:
20312047
headers.update(extra_headers)
@@ -2103,6 +2119,7 @@ def _do_resumable_upload(
21032119
timeout=_DEFAULT_TIMEOUT,
21042120
checksum=None,
21052121
retry=None,
2122+
command=None,
21062123
):
21072124
"""Perform a resumable upload.
21082125
@@ -2191,6 +2208,10 @@ def _do_resumable_upload(
21912208
(google.cloud.storage.retry) for information on retry types and how
21922209
to configure them.
21932210
2211+
:type command: str
2212+
:param command:
2213+
(Optional) Information about which interface for upload was used, to be included in the X-Goog-API-Client header for traffic analysis purposes. Please leave as None unless otherwise directed.
2214+
21942215
:rtype: :class:`~requests.Response`
21952216
:returns: The "200 OK" response object returned after the final chunk
21962217
is uploaded.
@@ -2209,6 +2230,7 @@ def _do_resumable_upload(
22092230
timeout=timeout,
22102231
checksum=checksum,
22112232
retry=retry,
2233+
command=command,
22122234
)
22132235
while not upload.finished:
22142236
try:
@@ -2234,6 +2256,7 @@ def _do_upload(
22342256
timeout=_DEFAULT_TIMEOUT,
22352257
checksum=None,
22362258
retry=None,
2259+
command=None,
22372260
):
22382261
"""Determine an upload strategy and then perform the upload.
22392262
@@ -2333,6 +2356,10 @@ def _do_upload(
23332356
configuration changes for Retry objects such as delays and deadlines
23342357
are respected.
23352358
2359+
:type command: str
2360+
:param command:
2361+
(Optional) Information about which interface for upload was used, to be included in the X-Goog-API-Client header for traffic analysis purposes. Please leave as None unless otherwise directed.
2362+
23362363
:rtype: dict
23372364
:returns: The parsed JSON from the "200 OK" response. This will be the
23382365
**only** response in the multipart case and it will be the
@@ -2366,6 +2393,7 @@ def _do_upload(
23662393
timeout=timeout,
23672394
checksum=checksum,
23682395
retry=retry,
2396+
command=command,
23692397
)
23702398
else:
23712399
response = self._do_resumable_upload(
@@ -2382,6 +2410,7 @@ def _do_upload(
23822410
timeout=timeout,
23832411
checksum=checksum,
23842412
retry=retry,
2413+
command=command,
23852414
)
23862415

23872416
return response.json()
@@ -2402,6 +2431,7 @@ def _prep_and_do_upload(
24022431
timeout=_DEFAULT_TIMEOUT,
24032432
checksum=None,
24042433
retry=DEFAULT_RETRY_IF_GENERATION_SPECIFIED,
2434+
command=None,
24052435
):
24062436
"""Upload the contents of this blob from a file-like object.
24072437
@@ -2522,6 +2552,10 @@ def _prep_and_do_upload(
25222552
configuration changes for Retry objects such as delays and deadlines
25232553
are respected.
25242554
2555+
:type command: str
2556+
:param command:
2557+
(Optional) Information about which interface for upload was used, to be included in the X-Goog-API-Client header for traffic analysis purposes. Please leave as None unless otherwise directed.
2558+
25252559
:raises: :class:`~google.cloud.exceptions.GoogleCloudError`
25262560
if the upload response returns an error status.
25272561
"""
@@ -2551,6 +2585,7 @@ def _prep_and_do_upload(
25512585
timeout=timeout,
25522586
checksum=checksum,
25532587
retry=retry,
2588+
command=command,
25542589
)
25552590
self._set_properties(created_json)
25562591
except resumable_media.InvalidResponse as exc:
@@ -4108,6 +4143,7 @@ def _prep_and_do_download(
41084143
timeout=_DEFAULT_TIMEOUT,
41094144
checksum="md5",
41104145
retry=DEFAULT_RETRY,
4146+
command=None,
41114147
):
41124148
"""Download the contents of a blob object into a file-like object.
41134149
@@ -4195,6 +4231,10 @@ def _prep_and_do_download(
41954231
predicates in a Retry object. The default will always be used. Other
41964232
configuration changes for Retry objects such as delays and deadlines
41974233
are respected.
4234+
4235+
:type command: str
4236+
:param command:
4237+
(Optional) Information about which interface for download was used, to be included in the X-Goog-API-Client header for traffic analysis purposes. Please leave as None unless otherwise directed.
41984238
"""
41994239
# Handle ConditionalRetryPolicy.
42004240
if isinstance(retry, ConditionalRetryPolicy):
@@ -4224,7 +4264,10 @@ def _prep_and_do_download(
42244264
if_etag_match=if_etag_match,
42254265
if_etag_not_match=if_etag_not_match,
42264266
)
4227-
headers = {**_get_default_headers(client._connection.user_agent), **headers}
4267+
headers = {
4268+
**_get_default_headers(client._connection.user_agent, command=command),
4269+
**headers,
4270+
}
42284271

42294272
transport = client._http
42304273

tests/unit/test_blob.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2251,11 +2251,12 @@ def test__get_upload_arguments(self):
22512251
blob = self._make_one(name, bucket=None, encryption_key=key)
22522252
blob.content_disposition = "inline"
22532253

2254+
COMMAND = "tm.upload_many"
22542255
content_type = "image/jpeg"
22552256
with patch.object(
22562257
_helpers, "_get_invocation_id", return_value=GCCL_INVOCATION_TEST_CONST
22572258
):
2258-
info = blob._get_upload_arguments(client, content_type)
2259+
info = blob._get_upload_arguments(client, content_type, command=COMMAND)
22592260

22602261
headers, object_metadata, new_content_type = info
22612262
header_key_value = "W3BYd0AscEBAQWZCZnJSM3gtMmIyU0NIUiwuP1l3Uk8="
@@ -2264,11 +2265,17 @@ def test__get_upload_arguments(self):
22642265
_helpers, "_get_invocation_id", return_value=GCCL_INVOCATION_TEST_CONST
22652266
):
22662267
expected_headers = {
2267-
**_get_default_headers(client._connection.user_agent, content_type),
2268+
**_get_default_headers(
2269+
client._connection.user_agent, content_type, command=COMMAND
2270+
),
22682271
"X-Goog-Encryption-Algorithm": "AES256",
22692272
"X-Goog-Encryption-Key": header_key_value,
22702273
"X-Goog-Encryption-Key-Sha256": header_key_hash_value,
22712274
}
2275+
self.assertEqual(
2276+
headers["X-Goog-API-Client"],
2277+
f"{client._connection.user_agent} {GCCL_INVOCATION_TEST_CONST} gccl-gcs-cmd/{COMMAND}",
2278+
)
22722279
self.assertEqual(headers, expected_headers)
22732280
expected_metadata = {
22742281
"contentDisposition": blob.content_disposition,
@@ -3184,6 +3191,7 @@ def _do_upload_helper(
31843191
timeout=expected_timeout,
31853192
checksum=None,
31863193
retry=retry,
3194+
command=None,
31873195
)
31883196
blob._do_resumable_upload.assert_not_called()
31893197
else:
@@ -3202,6 +3210,7 @@ def _do_upload_helper(
32023210
timeout=expected_timeout,
32033211
checksum=None,
32043212
retry=retry,
3213+
command=None,
32053214
)
32063215

32073216
def test__do_upload_uses_multipart(self):
@@ -3294,6 +3303,7 @@ def _upload_from_file_helper(self, side_effect=None, **kwargs):
32943303
timeout=expected_timeout,
32953304
checksum=None,
32963305
retry=retry,
3306+
command=None,
32973307
)
32983308
return stream
32993309

@@ -3385,7 +3395,13 @@ def _do_upload_mock_call_helper(
33853395
if not retry:
33863396
retry = DEFAULT_RETRY_IF_GENERATION_SPECIFIED if not num_retries else None
33873397
self.assertEqual(
3388-
kwargs, {"timeout": expected_timeout, "checksum": None, "retry": retry}
3398+
kwargs,
3399+
{
3400+
"timeout": expected_timeout,
3401+
"checksum": None,
3402+
"retry": retry,
3403+
"command": None,
3404+
},
33893405
)
33903406

33913407
return pos_args[1]

0 commit comments

Comments
 (0)