Skip to content

Commit

Permalink
Merge 0aea5a7 into 847301b
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaszsocha2 committed Sep 21, 2022
2 parents 847301b + 0aea5a7 commit 8029e71
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 30 deletions.
20 changes: 15 additions & 5 deletions boxsdk/object/folder.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class _CollaborationType(TextEnum):

class _Collaborator:
"""This helper class represents a collaborator on Box. A Collaborator can be a User, Group, or an email address"""

def __init__(self, collaborator: Any):
if isinstance(collaborator, User):
self._setup(user=collaborator)
Expand Down Expand Up @@ -138,21 +139,29 @@ def create_upload_session(self, file_size: int, file_name: str) -> 'UploadSessio
)

@api_call
def get_chunked_uploader(self, file_path: str) -> 'ChunkedUploader':
def get_chunked_uploader(self, file_path: str, file_name: Optional[str] = None) -> 'ChunkedUploader':
# pylint: disable=consider-using-with
"""
Instantiate the chunked upload instance and create upload session with path to file.
:param file_path:
The local path to the file you wish to upload.
:param file_name:
The name with extention of the file that will be uploaded, e.g. new_file_name.zip.
If not specified, the name from the local system is used.
:returns:
A :class:`ChunkedUploader` object.
"""
total_size = os.stat(file_path).st_size
upload_file_name = file_name if file_name else os.path.basename(file_path)
content_stream = open(file_path, 'rb')
file_name = os.path.basename(file_path)
upload_session = self.create_upload_session(total_size, file_name)
return upload_session.get_chunked_uploader_for_stream(content_stream, total_size)

try:
upload_session = self.create_upload_session(total_size, upload_file_name)
return upload_session.get_chunked_uploader_for_stream(content_stream, total_size)
except Exception:
content_stream.close()
raise

def _get_accelerator_upload_url_fow_new_uploads(self) -> Optional[str]:
"""
Expand Down Expand Up @@ -310,7 +319,8 @@ def upload_stream(
headers['Content-MD5'] = sha1
if not headers:
headers = None
file_response = self._session.post(url, data=data, files=files, expect_json_response=False, headers=headers).json()
file_response = self._session.post(url, data=data, files=files, expect_json_response=False,
headers=headers).json()
if 'entries' in file_response:
file_response = file_response['entries'][0]
return self.translator.translate(
Expand Down
16 changes: 10 additions & 6 deletions boxsdk/util/chunked_uploader.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class ChunkedUploader:

def __init__(self, upload_session: 'UploadSession', content_stream: IO, file_size: int):
def __init__(self, upload_session: 'UploadSession', content_stream: IO[bytes], file_size: int):
"""
The initializer for the :class:`ChunkedUploader`
Expand Down Expand Up @@ -42,8 +42,7 @@ def start(self) -> Optional['File']:
if self._is_aborted:
raise BoxException('The upload has been previously aborted. Please retry upload with a new upload session.')
self._upload()
content_sha1 = self._sha1.digest()
return self._upload_session.commit(content_sha1=content_sha1, parts=self._part_array)
return self._commit_and_erase_stream_reference_when_succeed()

def resume(self) -> Optional['File']:
"""
Expand All @@ -68,8 +67,7 @@ def resume(self) -> Optional['File']:
self._inflight_part = None
self._part_definitions[part['offset']] = part
self._upload()
content_sha1 = self._sha1.digest()
return self._upload_session.commit(content_sha1=content_sha1, parts=self._part_array)
return self._commit_and_erase_stream_reference_when_succeed()

def abort(self) -> bool:
"""
Expand All @@ -86,7 +84,7 @@ def abort(self) -> bool:

def _upload(self) -> None:
"""
Utility function for looping through all parts of of the upload session and uploading them.
Utility function for looping through all parts of the upload session and uploading them.
"""
while len(self._part_array) < self._upload_session.total_parts:
# Retrieve the part inflight if it exists, if it does not exist then get the next part from the stream.
Expand Down Expand Up @@ -124,6 +122,12 @@ def _get_next_part(self) -> 'InflightPart':
copied_length += len(bytes_read)
return InflightPart(offset, chunk, self._upload_session, self._file_size)

def _commit_and_erase_stream_reference_when_succeed(self):
content_sha1 = self._sha1.digest()
commit_result = self._upload_session.commit(content_sha1=content_sha1, parts=self._part_array)
# self._content_stream = None
return commit_result


class InflightPart:

Expand Down
57 changes: 38 additions & 19 deletions docs/usage/files.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,39 +200,39 @@ without aborting the entire upload, and failed parts can then be retried.

#### Upload new file

The SDK provides a method of automatically handling a chunked upload. First get a folder you want to upload the file to. Then call [`folder.get_chunked_uploader(file_path, rename_file=False)`][get_chunked_uploader_for_file] to retrieve a [`ChunkedUploader`][chunked_uploader_class] object. Calling the method [`chunked_upload.start()`][start] will kick off the chunked upload process and return the [File][file_class]
The SDK provides a method of automatically handling a chunked upload. First get a folder you want to upload the file to.
Then call [`folder.get_chunked_uploader(file_path, rename_file=False)`][get_chunked_uploader_for_file] to retrieve
a [`ChunkedUploader`][chunked_uploader_class] object. Calling the method [`chunked_upload.start()`][start] will
kick off the chunked upload process and return the [File][file_class]
object that was uploaded.

<!-- samples x_chunked_uploads automatic -->
```python
# uploads large file to a root folder
chunked_uploader = client.folder('0').get_chunked_uploader('/path/to/file')
chunked_uploader = client.folder('0').get_chunked_uploader(file_path='/path/to/file.txt', file_name='new_name.txt')
uploaded_file = chunked_uploader.start()
print(f'File "{uploaded_file.name}" uploaded to Box with file ID {uploaded_file.id}')
```

You can also return a [`ChunkedUploader`][chunked_uploader_class] object by creating a [`UploadSession`][upload_session_class] object first
and calling the method [`upload_session.get_chunked_upload(file_path)`][get_chunked_uploader] or
[`upload_session.get_chunked_uploader_for_stream(content_stream, file_size)`][get_chunked_uploader_for_stream].

```python
chunked_uploader = client.upload_session('56781').get_chunked_uploader('/path/to/file')
uploaded_file = chunked_uploader.start()
print(f'File "{uploaded_file.name}" uploaded to Box with file ID {uploaded_file.id}')
```
You can also upload file stream by creating a [`UploadSession`][upload_session_class] first and then calling the
method [`upload_session.get_chunked_uploader_for_stream(content_stream, file_size)`][get_chunked_uploader_for_stream].

```python
test_file_path = '/path/to/large_file.mp4'
content_stream = open(test_file_path, 'rb')
total_size = os.stat(test_file_path).st_size
chunked_uploader = client.upload_session('56781').get_chunked_uploader_for_stream(content_stream, total_size)
uploaded_file = chunked_uploader.start()
print(f'File "{uploaded_file.name}" uploaded to Box with file ID {uploaded_file.id}')
with open(test_file_path, 'rb') as content_stream:
total_size = os.stat(test_file_path).st_size
upload_session = client.folder('0').create_upload_session(file_size=total_size, file_name='large_file.mp4')
chunked_uploader = upload_session.get_chunked_uploader_for_stream(content_stream=content_stream, file_size=total_size)
uploaded_file = chunked_uploader.start()
print(f'File "{uploaded_file.name}" uploaded to Box with file ID {uploaded_file.id}')
```

#### Upload new file version

To upload a new file version for a large file, first get a file you want to replace. Then call [`file.get_chunked_uploader(file_path)`][get_chunked_uploader_for_version] to retrieve a [`ChunkedUploader`][chunked_uploader_class] object. Calling the method [`chunked_upload.start()`][start] will kick off the chunked upload process and return the updated [File][file_class].
To upload a new file version for a large file, first get a file you want to replace.
Then call [`file.get_chunked_uploader(file_path)`][get_chunked_uploader_for_version]
to retrieve a [`ChunkedUploader`][chunked_uploader_class] object. Calling the method [`chunked_upload.start()`][start]
will kick off the chunked upload process and return the updated [File][file_class].

<!-- samples x_chunked_uploads automatic_new_version -->
```python
Expand All @@ -243,13 +243,32 @@ print(f'File "{uploaded_file.name}" uploaded to Box with file ID {uploaded_file.
# the uploaded_file.id will be the same as 'existing_big_file_id'
```

#### Preflight check before upload

To check if a file can be uploaded with given name to a specific folder call
[`folder.preflight_check(size, name)`][preflight_check]. If the check did not pass, this method will raise an exception
including some details on why it did not pass.

<!-- samples x_chunked_uploads automatic_new_version -->
```python
file_name = 'large_file.mp4'
test_file_path = '/path/to/large_file.mp4'
total_size = os.stat(test_file_path).st_size
destination_folder_id = '0'
try:
client.folder(destination_folder_id).preflight_check(size=total_size, name=file_name)
except BoxAPIException as e:
print(f'File {file_name} cannot be uploaded to folder with id: {destination_folder_id}. Reason: {e.message}')
```


[start]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.chunked_uploader.ChunkedUploader.start
[chunked_uploader_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.chunked_uploader.ChunkedUploader
[get_chunked_uploader_for_version]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.get_chunked_uploader
[get_chunked_uploader_for_file]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder.get_chunked_uploader
[upload_session_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.upload_session.UploadSession
[get_chunked_uploader]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.upload_session.UploadSession.get_chunked_uploader
[get_chunked_uploader_for_stream]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.upload_session.UploadSession.get_chunked_uploader_for_stream
[preflight_check]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.

#### Resume Upload

Expand All @@ -276,7 +295,7 @@ uploaded_file = chunked_uploader.resume()
print(f'File "{uploaded_file.name}" uploaded to Box with file ID {uploaded_file.id}')
```

[resume]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.chunked_uploader.ChunkedUploader.resume
[resume]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder.preflight_check

#### Abort Chunked Upload

Expand Down

0 comments on commit 8029e71

Please sign in to comment.