Skip to content

Commit

Permalink
Initial support for uploadType=media (#153)
Browse files Browse the repository at this point in the history
* Initial support for uploadType=media

* Test `contentEncoding` query parameter

* Refactor `insert` error handling
  • Loading branch information
oittaa authored Mar 15, 2022
1 parent 99a10a9 commit f0db83d
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 40 deletions.
100 changes: 64 additions & 36 deletions gcp_storage_emulator/handlers/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,33 +155,58 @@ def _make_object_resource(
return obj


def _content_type_from_request(request, default=None):
if "contentEncoding" in request.query:
return request.query["contentEncoding"][0]
return default


def _media_upload(request, response, storage):
object_id = request.query["name"][0]
content_type = _content_type_from_request(
request, request.get_header("content-type")
)
obj = _make_object_resource(
request.base_url,
request.params["bucket_name"],
object_id,
content_type,
str(len(request.data)),
)
obj = _checksums(request.data, obj)
storage.create_file(
request.params["bucket_name"],
object_id,
request.data,
obj,
)

response.json(obj)


def _multipart_upload(request, response, storage):
object_id = request.data["meta"].get("name")
# Overrides the object metadata's name value, if any.
if "name" in request.query:
object_id = request.query["name"][0]
content_type = _content_type_from_request(request, request.data["content-type"])
obj = _make_object_resource(
request.base_url,
request.params["bucket_name"],
object_id,
request.data["content-type"],
content_type,
str(len(request.data["content"])),
request.data["meta"],
)
try:
obj = _checksums(request.data["content"], obj)
storage.create_file(
request.params["bucket_name"],
object_id,
request.data["content"],
obj,
)
obj = _checksums(request.data["content"], obj)
storage.create_file(
request.params["bucket_name"],
object_id,
request.data["content"],
obj,
)

response.json(obj)
except NotFound:
response.status = HTTPStatus.NOT_FOUND
except Conflict as err:
_handle_conflict(response, err)
response.json(obj)


def _create_resumable_upload(request, response, storage):
Expand All @@ -193,8 +218,8 @@ def _create_resumable_upload(request, response, storage):
# Overrides the object metadata's name value, if any.
if "name" in request.query:
object_id = request.query["name"][0]
content_type = request.get_header(
"x-upload-content-type", "application/octet-stream"
content_type = _content_type_from_request(
request, request.get_header("x-upload-content-type", "application/octet-stream")
)
content_length = request.get_header("x-upload-content-length", None)
obj = _make_object_resource(
Expand All @@ -204,22 +229,17 @@ def _create_resumable_upload(request, response, storage):
content_type,
content_length,
)
try:
id = storage.create_resumable_upload(
request.params["bucket_name"],
object_id,
obj,
)
encoded_id = urllib.parse.urlencode(
{
"upload_id": id,
}
)
response["Location"] = request.full_url + "&{}".format(encoded_id)
except NotFound:
response.status = HTTPStatus.NOT_FOUND
except Conflict as err:
_handle_conflict(response, err)
id = storage.create_resumable_upload(
request.params["bucket_name"],
object_id,
obj,
)
encoded_id = urllib.parse.urlencode(
{
"upload_id": id,
}
)
response["Location"] = request.full_url + "&{}".format(encoded_id)


def _delete(storage, bucket_name, object_id):
Expand Down Expand Up @@ -274,11 +294,19 @@ def insert(request, response, storage, *args, **kwargs):

uploadType = uploadType[0]

if uploadType == "resumable":
return _create_resumable_upload(request, response, storage)
try:
if uploadType == "media":
return _media_upload(request, response, storage)

if uploadType == "resumable":
return _create_resumable_upload(request, response, storage)

if uploadType == "multipart":
return _multipart_upload(request, response, storage)
if uploadType == "multipart":
return _multipart_upload(request, response, storage)
except NotFound:
response.status = HTTPStatus.NOT_FOUND
except Conflict as err:
_handle_conflict(response, err)


def upload_partial(request, response, storage, *args, **kwargs):
Expand Down
21 changes: 17 additions & 4 deletions tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -846,14 +846,27 @@ def test_signed_url_upload_to_nonexistent_bucket(self):
self.assertEqual(response.status_code, 404)

def test_initiate_resumable_upload_without_metadata(self):
url = "http://127.0.0.1:9023/upload/storage/v1/b/test_bucket/o?"
url += "uploadType=resumable&name=test_file"
self._client.create_bucket("test_bucket")
headers = {"Content-type": "application/json"}
response = requests.post(
"http://127.0.0.1:9023/upload/storage/v1/b/test_bucket/o?uploadType=resumable&name=test_file",
headers=headers,
)
response = requests.post(url, headers=headers)
self.assertEqual(response.status_code, 200)

def test_media_upload_without_metadata(self):
url = "http://127.0.0.1:9023/upload/storage/v1/b/test_bucket/o?"
url += "uploadType=media&name=test_file&contentEncoding=text%2Fplain"
bucket = self._client.create_bucket("test_bucket")
with open(TEST_TEXT, "rb") as file:
headers = {"Content-type": "text/html"}
response = requests.post(url, data=file, headers=headers)
self.assertEqual(response.status_code, 200)
blob = bucket.blob("test_file")
blob_content = blob.download_as_bytes()
file.seek(0)
self.assertEqual(blob_content, file.read())
self.assertEqual(blob.content_type, "text/plain")


class HttpEndpointsTest(ServerBaseCase):
"""Tests for the HTTP endpoints defined by server.HANDLERS."""
Expand Down

0 comments on commit f0db83d

Please sign in to comment.