Skip to content

Commit 582a48f

Browse files
committed
Implement batch support for copy
1 parent 2344499 commit 582a48f

File tree

3 files changed

+63
-15
lines changed

3 files changed

+63
-15
lines changed

src/gcp_storage_emulator/handlers/objects.py

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -367,36 +367,48 @@ def ls(request, response, storage, *args, **kwargs):
367367

368368
def copy(request, response, storage, *args, **kwargs):
369369
try:
370-
obj = storage.get_file_obj(
371-
request.params["bucket_name"], request.params["object_id"]
372-
)
370+
dest_obj = _copy(
371+
request.base_url,
372+
storage,
373+
request.params["bucket_name"],
374+
request.params["object_id"],
375+
request.params["dest_bucket_name"],
376+
request.params["dest_object_id"])
377+
if dest_obj is None:
378+
response.status = HTTPStatus.NOT_FOUND
379+
else:
380+
response.json(dest_obj)
381+
except Conflict as err:
382+
_handle_conflict(response, err)
383+
384+
385+
def _copy(base_url, storage, bucket_name, object_id, dest_bucket_name, dest_object_id):
386+
try:
387+
obj = storage.get_file_obj(bucket_name, object_id)
373388
except NotFound:
374-
response.status = HTTPStatus.NOT_FOUND
375-
return
389+
return None
376390

377391
dest_obj = _make_object_resource(
378-
request.base_url,
379-
request.params["dest_bucket_name"],
380-
request.params["dest_object_id"],
392+
base_url,
393+
dest_bucket_name,
394+
dest_object_id,
381395
obj["contentType"],
382396
obj["size"],
383397
obj,
384398
)
385399

386-
file = storage.get_file(request.params["bucket_name"], request.params["object_id"])
400+
file = storage.get_file(bucket_name, object_id)
387401
try:
388402
dest_obj = _checksums(file, dest_obj)
389403
storage.create_file(
390-
request.params["dest_bucket_name"],
391-
request.params["dest_object_id"],
404+
dest_bucket_name,
405+
dest_object_id,
392406
file,
393407
dest_obj,
394408
)
395-
response.json(dest_obj)
409+
return dest_obj
396410
except NotFound:
397-
response.status = HTTPStatus.NOT_FOUND
398-
except Conflict as err:
399-
_handle_conflict(response, err)
411+
return None
400412

401413

402414
def compose(request, response, storage, *args, **kwargs):
@@ -526,6 +538,22 @@ def batch(request, response, storage, *args, **kwargs):
526538
if resp_data:
527539
response.write("HTTP/1.1 204 No Content\r\n")
528540
response.write("Content-Type: application/json; charset=UTF-8\r\n")
541+
if method == "POST": # kludgy heuristics, currently only supports COPY
542+
if object_id:
543+
resp_data = _copy(
544+
request.base_url,
545+
storage,
546+
bucket_name,
547+
object_id,
548+
item["dest_bucket_name"],
549+
item["dest_object_id"],
550+
)
551+
if resp_data:
552+
response.write("HTTP/1.1 200 OK\r\n")
553+
response.write("Content-Type: application/json; charset=UTF-8\r\n")
554+
response.write(json.dumps(resp_data))
555+
response.write("\r\n\r\n")
556+
529557
if not resp_data:
530558
msg = "No such object: {}/{}".format(bucket_name, object_id)
531559
resp_data = deepcopy(NOT_FOUND)

src/gcp_storage_emulator/server.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ def _health_check(req, res, storage):
9999
r"^(?P<method>[\w]+).*{}/b/(?P<bucket_name>[-.\w]+)([\?].*)?$".format(
100100
settings.API_ENDPOINT
101101
),
102+
r"^(?P<method>[\w]+).*{}/b/(?P<bucket_name>[-.\w]+)/o/(?P<object_id>[^\?]+[^/])/copyTo/b/(?P<dest_bucket_name>[-.\w]+)/o/(?P<dest_object_id>[^\?]+[^/])([\?].*)?$".format(
103+
settings.API_ENDPOINT
104+
),
102105
r"^Content-Type:\s*(?P<content_type>[-.\w/]+)$",
103106
)
104107

tests/test_server.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,23 @@ def test_batch_delete_buckets(self):
758758
with self.assertRaises(NotFound):
759759
self._client.get_bucket("batchbucket2")
760760

761+
def test_batch_copy_existing(self):
762+
bucket = self._client.create_bucket("bucket_name")
763+
764+
source = []
765+
target = []
766+
for i in range(2):
767+
source.append(bucket.blob("a/{}.txt".format(i)))
768+
source[-1].upload_from_string("text {}".format(i))
769+
target.append(bucket.blob("b/{}.txt".format(i)))
770+
771+
with self._client.batch():
772+
for i in range(2):
773+
bucket.copy_blob(source[i], bucket, target[i].name)
774+
775+
blobs = self._client.list_blobs(bucket)
776+
self._assert_blob_list(blobs, source + target)
777+
761778
def test_resumable_upload_small_chunk_size(self):
762779
content = b"a" * 10000000
763780
bucket = self._client.create_bucket("testbucket")

0 commit comments

Comments
 (0)