Skip to content

Commit de77f6c

Browse files
authored
feat(storage): implement compose method (#912)
1 parent da4750e commit de77f6c

File tree

4 files changed

+94
-2
lines changed

4 files changed

+94
-2
lines changed

storage/gcloud/aio/storage/storage.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,43 @@ async def upload_from_filename(
484484
contents = await file_object.read()
485485
return await self.upload(bucket, object_name, contents, **kwargs)
486486

487+
# https://cloud.google.com/storage/docs/json_api/v1/objects/compose
488+
async def compose(
489+
self, bucket: str, object_name: str,
490+
source_object_names: List[str], *,
491+
content_type: Optional[str] = None,
492+
params: Optional[Dict[str, str]] = None,
493+
headers: Optional[Dict[str, Any]] = None,
494+
session: Optional[Session] = None,
495+
timeout: int = DEFAULT_TIMEOUT,
496+
) -> Dict[str, Any]:
497+
url = (
498+
f'{self._api_root_read}/{bucket}/o/'
499+
f'{quote(object_name, safe="")}/compose'
500+
)
501+
headers = headers or {}
502+
headers.update(await self._headers())
503+
params = params or {}
504+
505+
payload: Dict[str, Any] = {
506+
'sourceObjects': [{'name': name} for name in source_object_names],
507+
}
508+
if content_type:
509+
payload['destination'] = {'contentType': content_type}
510+
body = json.dumps(payload).encode('utf-8')
511+
headers.update({
512+
'Content-Length': str(len(body)),
513+
'Content-Type': 'application/json; charset=UTF-8',
514+
})
515+
516+
s = AioSession(session) if session else self.session
517+
resp = await s.post(
518+
url, headers=headers, params=params, timeout=timeout,
519+
data=body,
520+
)
521+
data: Dict[str, Any] = await resp.json(content_type=None)
522+
return data
523+
487524
@staticmethod
488525
def _get_stream_len(stream: IO[AnyStr]) -> int:
489526
current = stream.tell()

storage/pyproject.rest.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "gcloud-rest-storage"
3-
version = "9.4.0"
3+
version = "9.5.0"
44
description = "Python Client for Google Cloud Storage"
55
readme = "README.rst"
66

storage/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "gcloud-aio-storage"
3-
version = "9.4.0"
3+
version = "9.5.0"
44
description = "Python Client for Google Cloud Storage"
55
readme = "README.rst"
66

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import uuid
2+
3+
import pytest
4+
from gcloud.aio.auth import BUILD_GCLOUD_REST # pylint: disable=no-name-in-module
5+
from gcloud.aio.storage import Storage
6+
7+
# Selectively load libraries based on the package
8+
if BUILD_GCLOUD_REST:
9+
from requests import Session
10+
else:
11+
from aiohttp import ClientSession as Session
12+
13+
14+
@pytest.mark.asyncio
15+
@pytest.mark.parametrize(
16+
'shard_data,expected_data,content_type,file_extension', [
17+
(['foo ', 'bar'], b'foo bar', 'text/plain', 'txt'),
18+
(['{"foo":', '1,', '"bar":2}'],
19+
b'{"foo":1,"bar":2}', 'application/json', 'json'),
20+
],
21+
)
22+
async def test_compose(
23+
bucket_name, creds, shard_data,
24+
expected_data, content_type, file_extension,
25+
):
26+
def random_name():
27+
return f'{uuid.uuid4().hex}/{uuid.uuid4().hex}.{file_extension}'
28+
29+
shard_names = [random_name() for _ in shard_data]
30+
object_name = random_name()
31+
32+
async with Session() as session:
33+
storage = Storage(service_file=creds, session=session)
34+
35+
for shard_name, shard_datum in zip(shard_names, shard_data):
36+
await storage.upload(
37+
bucket_name,
38+
shard_name,
39+
shard_datum,
40+
metadata={
41+
'Content-Disposition': 'inline',
42+
},
43+
)
44+
res = await storage.compose(
45+
bucket_name,
46+
object_name,
47+
shard_names,
48+
content_type=content_type,
49+
)
50+
51+
assert res['name'] == object_name
52+
assert res['contentType'] == content_type
53+
54+
downloaded_data = await storage.download(bucket_name, res['name'])
55+
assert downloaded_data == expected_data

0 commit comments

Comments
 (0)