Skip to content

Conversation

@eleftherioszisis
Copy link
Contributor

@eleftherioszisis eleftherioszisis commented Jan 12, 2026

Multipart Asset Upload Flow
1. Initiate Upload
POST /{entity_type}/{entity_id}/assets/upload/initiate
Initiates an S3 multipart upload and returns presigned URLs for each part.
A database asset is created with status = "uploading" and upload_meta containing the S3 upload_id and part metadata.
2. Upload Parts
The client uploads file parts directly to S3 using the provided presigned URLs.
3. Complete Upload
POST /{entity_type}/{entity_id}/assets/{asset_id}/upload/complete
Completes the multipart upload.
The asset is updated to status = "created" and upload_meta is cleared.
4. Delete / Abort Upload
DELETE /{entity_type}/{entity_id}/assets/{asset_id}
Deletes the asset record triggering the asset deletion event. If the asset is in the uploading state, the corresponding S3 multipart upload is aborted.

@codecov
Copy link

codecov bot commented Jan 12, 2026

Codecov Report

❌ Patch coverage is 98.43750% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
app/db/events.py 84.61% 2 Missing ⚠️
Flag Coverage Δ
pytest 97.66% <98.43%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
app/config.py 97.14% <100.00%> (+0.26%) ⬆️
app/db/model.py 99.08% <100.00%> (+<0.01%) ⬆️
app/db/types.py 99.45% <100.00%> (+<0.01%) ⬆️
app/errors.py 92.13% <100.00%> (+0.18%) ⬆️
app/repository/asset.py 100.00% <100.00%> (ø)
app/routers/asset.py 92.50% <100.00%> (+3.50%) ⬆️
app/schemas/asset.py 96.55% <100.00%> (+0.63%) ⬆️
app/service/asset.py 94.16% <100.00%> (+1.94%) ⬆️
app/utils/common.py 100.00% <100.00%> (ø)
app/utils/files.py 100.00% <100.00%> (ø)
... and 2 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@eleftherioszisis eleftherioszisis marked this pull request as ready for review January 12, 2026 22:46
@eleftherioszisis eleftherioszisis changed the title Implement Delegation & Multipart Upload for s3 files. Implement s3 Delegation & Multipart Upload for files. Jan 12, 2026
message=msg,
error_code=ApiErrorCode.ASSET_INVALID_CONTENT_TYPE,
http_status_code=HTTPStatus.UNPROCESSABLE_ENTITY,
) from None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason to not embed the except, and then raise ... from None?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tbh I copied it from regular upload.


@router.post("/{entity_route}/{entity_id}/assets/upload/initiate", include_in_schema=False)
@router.post("/{entity_route}/{entity_id}/assets/upload/initiate")
def initiate_entity_asset_upload(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should include the word multipart; other uploads are "initiated" elsewhere, so the terminology is non-specific

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So sth like:

"/{entity_route}/{entity_id}/assets/multipart-upload/initiate"

?

),
] = None
label: AssetLabel
preferred_part_count: Annotated[int, Field(description="Hint of desired part count.")] = (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this hint needed? I'm not sure we need this flexibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assumed that a user client and a cloud service will have different needs for number of parts. I can remove it if it seems not useful.



def clip(x: int, min_value: int, max_value: int) -> int:
"""Clamp x to the inclusive range [min_value, max_value]."""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it clip or clamp?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's clip in numpy :D


S3_MULTIPART_UPLOAD_MAX_SIZE: int = 5 * 1024**3 # TODO: Set appropriate upper file size limit
S3_MULTIPART_UPLOAD_MIN_PART_SIZE: int = 5 * 1024**2
S3_MULTIPART_UPLOAD_MAX_PART_SIZE: int = 5 * 1024**3
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems very high, no?

Copy link
Contributor Author

@eleftherioszisis eleftherioszisis Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, these parameters need some fine tuning. These are the s3 limits. We may want to stay way below that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants