Skip to content

Commit f0db83d

Browse files
authored
Initial support for uploadType=media (#153)
* Initial support for uploadType=media * Test `contentEncoding` query parameter * Refactor `insert` error handling
1 parent 99a10a9 commit f0db83d

2 files changed

Lines changed: 81 additions & 40 deletions

File tree

gcp_storage_emulator/handlers/objects.py

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -155,33 +155,58 @@ def _make_object_resource(
155155
return obj
156156

157157

158+
def _content_type_from_request(request, default=None):
159+
if "contentEncoding" in request.query:
160+
return request.query["contentEncoding"][0]
161+
return default
162+
163+
164+
def _media_upload(request, response, storage):
165+
object_id = request.query["name"][0]
166+
content_type = _content_type_from_request(
167+
request, request.get_header("content-type")
168+
)
169+
obj = _make_object_resource(
170+
request.base_url,
171+
request.params["bucket_name"],
172+
object_id,
173+
content_type,
174+
str(len(request.data)),
175+
)
176+
obj = _checksums(request.data, obj)
177+
storage.create_file(
178+
request.params["bucket_name"],
179+
object_id,
180+
request.data,
181+
obj,
182+
)
183+
184+
response.json(obj)
185+
186+
158187
def _multipart_upload(request, response, storage):
159188
object_id = request.data["meta"].get("name")
160189
# Overrides the object metadata's name value, if any.
161190
if "name" in request.query:
162191
object_id = request.query["name"][0]
192+
content_type = _content_type_from_request(request, request.data["content-type"])
163193
obj = _make_object_resource(
164194
request.base_url,
165195
request.params["bucket_name"],
166196
object_id,
167-
request.data["content-type"],
197+
content_type,
168198
str(len(request.data["content"])),
169199
request.data["meta"],
170200
)
171-
try:
172-
obj = _checksums(request.data["content"], obj)
173-
storage.create_file(
174-
request.params["bucket_name"],
175-
object_id,
176-
request.data["content"],
177-
obj,
178-
)
201+
obj = _checksums(request.data["content"], obj)
202+
storage.create_file(
203+
request.params["bucket_name"],
204+
object_id,
205+
request.data["content"],
206+
obj,
207+
)
179208

180-
response.json(obj)
181-
except NotFound:
182-
response.status = HTTPStatus.NOT_FOUND
183-
except Conflict as err:
184-
_handle_conflict(response, err)
209+
response.json(obj)
185210

186211

187212
def _create_resumable_upload(request, response, storage):
@@ -193,8 +218,8 @@ def _create_resumable_upload(request, response, storage):
193218
# Overrides the object metadata's name value, if any.
194219
if "name" in request.query:
195220
object_id = request.query["name"][0]
196-
content_type = request.get_header(
197-
"x-upload-content-type", "application/octet-stream"
221+
content_type = _content_type_from_request(
222+
request, request.get_header("x-upload-content-type", "application/octet-stream")
198223
)
199224
content_length = request.get_header("x-upload-content-length", None)
200225
obj = _make_object_resource(
@@ -204,22 +229,17 @@ def _create_resumable_upload(request, response, storage):
204229
content_type,
205230
content_length,
206231
)
207-
try:
208-
id = storage.create_resumable_upload(
209-
request.params["bucket_name"],
210-
object_id,
211-
obj,
212-
)
213-
encoded_id = urllib.parse.urlencode(
214-
{
215-
"upload_id": id,
216-
}
217-
)
218-
response["Location"] = request.full_url + "&{}".format(encoded_id)
219-
except NotFound:
220-
response.status = HTTPStatus.NOT_FOUND
221-
except Conflict as err:
222-
_handle_conflict(response, err)
232+
id = storage.create_resumable_upload(
233+
request.params["bucket_name"],
234+
object_id,
235+
obj,
236+
)
237+
encoded_id = urllib.parse.urlencode(
238+
{
239+
"upload_id": id,
240+
}
241+
)
242+
response["Location"] = request.full_url + "&{}".format(encoded_id)
223243

224244

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

275295
uploadType = uploadType[0]
276296

277-
if uploadType == "resumable":
278-
return _create_resumable_upload(request, response, storage)
297+
try:
298+
if uploadType == "media":
299+
return _media_upload(request, response, storage)
300+
301+
if uploadType == "resumable":
302+
return _create_resumable_upload(request, response, storage)
279303

280-
if uploadType == "multipart":
281-
return _multipart_upload(request, response, storage)
304+
if uploadType == "multipart":
305+
return _multipart_upload(request, response, storage)
306+
except NotFound:
307+
response.status = HTTPStatus.NOT_FOUND
308+
except Conflict as err:
309+
_handle_conflict(response, err)
282310

283311

284312
def upload_partial(request, response, storage, *args, **kwargs):

tests/test_server.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -846,14 +846,27 @@ def test_signed_url_upload_to_nonexistent_bucket(self):
846846
self.assertEqual(response.status_code, 404)
847847

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

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

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

0 commit comments

Comments
 (0)