Skip to content

Commit 5df3f39

Browse files
authored
Correctly handle upload responses (#219)
Upload responses look like {"entries": [{"type": "file", ...}]}, not {"type": "file", ...}. However, the code for uploading a new file version was incorrectly assuming the latter, causing it to return File({"entries": [{"type": "file", ...}]}) instead of File({"type": "file", ...}). Now we will expand the "entries", and grab only the file object. We'll also be future-proof, in case a future version of the API starts returning {"type": "file", ...}. Fixes #150.
1 parent d83a8b3 commit 5df3f39

File tree

7 files changed

+39
-10
lines changed

7 files changed

+39
-10
lines changed

HISTORY.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ Release History
3131
automatically backwards-compatibile (as long as there aren't any changes to
3232
change/remove any of the keywords).
3333

34+
- ``File.update_contents()`` and ``File.update_contents_with_stream()`` now
35+
correctly return a ``File`` object with the correct internal JSON structure.
36+
Previously it would return a ``File`` object where the file JSON is hidden
37+
inside ``file['entries'][0]``. This is a bugfix, but will be a breaking
38+
change for any clients that have already written code to handle the bug.
39+
3440
**Features**
3541

3642
- Added more flexibility to the object translation system:

boxsdk/object/file.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,13 @@ def update_contents_with_stream(
130130

131131
files = {'file': ('unused', file_stream)}
132132
headers = {'If-Match': etag} if etag is not None else None
133+
file_response = self._session.post(url, expect_json_response=False, files=files, headers=headers).json()
134+
if 'entries' in file_response:
135+
file_response = file_response['entries'][0]
133136
return self.__class__(
134137
session=self._session,
135138
object_id=self._object_id,
136-
response_object=self._session.post(url, expect_json_response=False, files=files, headers=headers).json(),
139+
response_object=file_response,
137140
)
138141

139142
@api_call

boxsdk/object/folder.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,9 @@ def upload_stream(
279279
files = {
280280
'file': ('unused', file_stream),
281281
}
282-
box_response = self._session.post(url, data=data, files=files, expect_json_response=False)
283-
file_response = box_response.json()['entries'][0]
282+
file_response = self._session.post(url, data=data, files=files, expect_json_response=False).json()
283+
if 'entries' in file_response:
284+
file_response = file_response['entries'][0]
284285
file_id = file_response['id']
285286
return self.translator.translate(file_response['type'])(
286287
session=self._session,

test/functional/mock_box/behavior/file_behavior.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def update_file(self, file_id):
3737
file_object.size = len(content)
3838
self._db_session.add(EventModel(event_type='ITEM_UPLOAD', source_id=file_object.file_id, source_type='file'))
3939
self._db_session.commit()
40-
return json.dumps(file_object)
40+
return json.dumps({'entries': [file_object]})
4141

4242
def upload_file(self):
4343
"""

test/unit/object/conftest.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,22 @@ def mock_content_response(make_mock_box_request):
4040
return mock_box_response
4141

4242

43+
@pytest.fixture(scope='function', params=[False, True])
44+
def mock_upload_response_contains_entries(request):
45+
"""Is the upload response formatted as {"type": "file", "id": "123", ...}, or as {"entries": [{...}]}.
46+
47+
The v2.0 API does the latter, but future versions might do the former. So
48+
we'll test both.
49+
"""
50+
return request.param
51+
52+
4353
@pytest.fixture(scope='function')
44-
def mock_upload_response(mock_object_id, make_mock_box_request):
45-
mock_box_response, _ = make_mock_box_request(
46-
response={'entries': [{'type': 'file', 'id': mock_object_id}]},
47-
)
54+
def mock_upload_response(mock_object_id, make_mock_box_request, mock_upload_response_contains_entries):
55+
response = {'type': 'file', 'id': mock_object_id}
56+
if mock_upload_response_contains_entries:
57+
response = {'entries': [response]}
58+
mock_box_response, _ = make_mock_box_request(response=response)
4859
return mock_box_response
4960

5061

test/unit/object/test_file.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,11 @@ def test_update_contents(
108108
headers=if_match_header,
109109
)
110110
assert isinstance(new_file, File)
111-
assert new_file.object_id == mock_upload_response.json()['entries'][0]['id']
111+
assert new_file.object_id == test_file.object_id
112+
assert 'id' in new_file
113+
assert new_file['id'] == test_file.object_id
114+
assert not hasattr(new_file, 'entries')
115+
assert 'entries' not in new_file
112116

113117

114118
def test_update_contents_with_stream_does_preflight_check_if_specified(

test/unit/object/test_folder.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,11 @@ def test_upload(
195195
data = {'attributes': json.dumps({'name': basename(mock_file_path), 'parent': {'id': mock_object_id}})}
196196
mock_box_session.post.assert_called_once_with(expected_url, expect_json_response=False, files=mock_files, data=data)
197197
assert isinstance(new_file, File)
198-
assert new_file.object_id == mock_upload_response.json()['entries'][0]['id']
198+
assert new_file.object_id == mock_object_id
199+
assert 'id' in new_file
200+
assert new_file['id'] == mock_object_id
201+
assert not hasattr(new_file, 'entries')
202+
assert 'entries' not in new_file
199203

200204

201205
def test_upload_stream_does_preflight_check_if_specified(

0 commit comments

Comments
 (0)