Skip to content

Commit 7ecb52f

Browse files
authored
Merge pull request #38 from aiokitchen/feature/multipart-manual-upload
Feature/multipart manual upload
2 parents 931cae4 + 6b3c371 commit 7ecb52f

2 files changed

Lines changed: 270 additions & 92 deletions

File tree

README.md

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
aiohttp-s3-client
22
================
33

4-
[![PyPI - License](https://img.shields.io/pypi/l/aiohttp-s3-client)](https://pypi.org/project/aiohttp-s3-client) [![Wheel](https://img.shields.io/pypi/wheel/aiohttp-s3-client)](https://pypi.org/project/aiohttp-s3-client) [![Mypy](http://www.mypy-lang.org/static/mypy_badge.svg)]() [![PyPI](https://img.shields.io/pypi/v/aiohttp-s3-client)](https://pypi.org/project/aiohttp-s3-client) [![PyPI](https://img.shields.io/pypi/pyversions/aiohttp-s3-client)](https://pypi.org/project/aiohttp-s3-client) [![Coverage Status](https://coveralls.io/repos/github/mosquito/aiohttp-s3-client/badge.svg?branch=master)](https://coveralls.io/github/mosquito/aiohttp-s3-client?branch=master) ![tox](https://github.com/mosquito/aiohttp-s3-client/workflows/tox/badge.svg?branch=master)
4+
[![PyPI - License](https://img.shields.io/pypi/l/aiohttp-s3-client)](https://pypi.org/project/aiohttp-s3-client) [![Wheel](https://img.shields.io/pypi/wheel/aiohttp-s3-client)](https://pypi.org/project/aiohttp-s3-client) [![Mypy](http://www.mypy-lang.org/static/mypy_badge.svg)]() [![PyPI](https://img.shields.io/pypi/v/aiohttp-s3-client)](https://pypi.org/project/aiohttp-s3-client) [![PyPI](https://img.shields.io/pypi/pyversions/aiohttp-s3-client)](https://pypi.org/project/aiohttp-s3-client) [![Coverage Status](https://coveralls.io/repos/github/mosquito/aiohttp-s3-client/badge.svg?branch=master)](https://coveralls.io/github/mosquito/aiohttp-s3-client?branch=master) [![tests](https://github.com/aiokitchen/aiohttp-s3-client/actions/workflows/tests.yml/badge.svg)](https://github.com/aiokitchen/aiohttp-s3-client/actions/workflows/tests.yml)
55

66
The simple module for putting and getting object from Amazon S3 compatible endpoints
77

@@ -296,3 +296,74 @@ await client.get_file_parallel(
296296
workers_count=8,
297297
)
298298
```
299+
300+
### Manual multipart upload
301+
302+
You can also manually control multipart upload process using `multipart_upload` method.
303+
It returns an async context manager which handles upload creation and completion.
304+
This method gives you more control over the upload process, for example you can
305+
specify part size, add custom metadata, or control concurrency.
306+
307+
#### Important multipart restrictions and recommendations:
308+
309+
- **Minimum part size: 5 MiB** (`5 * 1024 * 1024` bytes). Every part must be at least
310+
5 MiB in size, except for the final part.
311+
- **Maximum number of parts: `10,000`.** The total number of uploaded parts must be
312+
<= `10,000`.
313+
- Choosing a part size: **pick a part size that satisfies both constraints**.
314+
A safe formula when you know the total object size is:
315+
```python
316+
part_size = max(5 * 1024 * 1024, math.ceil(total_size / 10000))
317+
```
318+
- If you don't know the total size in advance, choose a conservative part size
319+
(for example 8 MiB or 16 MiB) so you are unlikely to exceed 10,000 parts.
320+
- The uploader implements retries for failed part uploads; you should still
321+
ensure parts (except the last) meet the 5 MiB minimum before uploading.
322+
323+
The `put_part` method returns a coroutine — calling `put_part(...)` does not
324+
perform the network upload immediately, it registers the part (and its part
325+
number) and returns a coroutine which performs the actual upload when awaited.
326+
This lets you schedule uploads and then await them concurrently.
327+
328+
Note: the coroutine returned by `put_part(...)` performs the actual network
329+
upload when awaited and the uploader will automatically retry failed part
330+
uploads according to its retry policy; awaiting the coroutine will run those
331+
retries for that part. You don't need to retry manually when using the
332+
returned coroutine — the uploader handles integrity checks and retrying.
333+
334+
#### Important usage notes:
335+
336+
- You **MUST** call `put_part(...)` in the logical part sequence so parts get the
337+
correct part numbers (the uploader assigns part numbers in call order).
338+
- You **MAY** await the returned coroutines later and in any concurrency pattern you
339+
like (for example with `asyncio.gather`), which enables concurrent part
340+
uploads.
341+
342+
#### Examples
343+
344+
Create parts then upload them concurrently:
345+
346+
```python
347+
import hashlib
348+
import aiohttp
349+
from aiohttp_s3_client import S3Client
350+
351+
client = S3Client(url="http://your-s3-host", session=aiohttp.ClientSession())
352+
353+
async with client.multipart_upload("test/video.mov") as uploader:
354+
uploads = []
355+
356+
# Call put_part in the correct part sequence and collect coroutines.
357+
# The uploader assigns part numbers in the order put_part is called.
358+
for chunk in chunks:
359+
uploads.append(
360+
uploader.put_part(
361+
chunk,
362+
content_sha256=hashlib.sha256(chunk).hexdigest(),
363+
)
364+
)
365+
366+
# Now execute all part uploads concurrently. The uploader will handle
367+
# retries and integrity checks for each part.
368+
await asyncio.gather(*uploads)
369+
```

0 commit comments

Comments
 (0)