Reading Results¶
Every upload request returns a Store — a Pydantic model with structured results for all processed fields and files.
The Store Model¶
@app.post("/upload")
async def upload(store: Store = Depends(storage)):
store.status # UploadStatus: "completed" | "partial" | "failed" | "empty"
store.message # Summary message
store.error # First error (None if no errors)
store.errors # All error messages
store.files # dict[str, list[FileData]]
store.flat_files # All files in one list
store.successful_files # Only successes
store.failed_files # Only failures
store.total_files # Count of all files
store.total_size # Sum of sizes for successful files
store.first("avatar") # First FileData for "avatar", or None
if store: # True only when status is "completed"
...
Upload Status¶
store.status is an UploadStatus — a StrEnum, so members compare equal to plain
strings and serialize as strings in JSON:
| Status | Meaning |
|---|---|
"completed" |
Every file was persisted successfully |
"partial" |
Some files succeeded, some failed — inspect failed_files to retry |
"failed" |
Files were submitted (or errors occurred) but nothing succeeded |
"empty" |
No files were submitted at all |
from filestore import UploadStatus
if store.status is UploadStatus.PARTIAL: # enum comparison
retry(store.failed_files)
if store.status == "partial": # plain string works too
...
Don't truth-test store.status
store.status is a non-empty string, so it is always truthy — even when
"failed". Use if store: / if not store: (which check for "completed"),
or compare against a specific status.
The FileData Model¶
Each processed file produces a FileData:
file_data = store.first("avatar")
# Identity
file_data.field_name # "avatar"
file_data.filename # "photo.jpg" (final stored name)
file_data.original_filename # "IMG_2024.jpg" (as submitted)
# Location
file_data.path # Path("/app/uploads/photo.jpg") — local only
file_data.url # "/media/photo.jpg" — if base_url was set
file_data.key # "photo.jpg" — object key / relative path
file_data.location # url, else path, else key
# Content
file_data.content_type # "image/jpeg"
file_data.size # 1048576 (bytes)
file_data.file # b"..." — memory engine only
# Status
file_data.status # True / False (also: bool(file_data))
file_data.error # Error message (when status=False)
file_data.storage # "local", "memory", "s3", "gcs", "azure"
# Extras
file_data.metadata # {"bucket": "my-bucket", ...}
Keep the key
FileData.key is exactly what engine.delete() and engine.presign_download()
expect — persist it if you need to manage the object later.
Common Patterns¶
Return the Store Directly¶
Store is a Pydantic model, so FastAPI serializes it automatically — including in
OpenAPI schemas. Raw bytes in FileData.file are excluded from serialization.
Need a plain dict? Use standard Pydantic:
Check Overall Status¶
@app.post("/upload")
async def upload(store: Store = Depends(storage)):
if not store:
raise HTTPException(status_code=422, detail=store.errors)
return {"files": [f.filename for f in store.successful_files]}
Process Each File¶
@app.post("/upload")
async def upload(store: Store = Depends(storage)):
results = []
for file_data in store.flat_files:
if file_data.status:
results.append({
"name": file_data.filename,
"url": file_data.url,
"size": file_data.size,
})
else:
results.append({
"name": file_data.original_filename,
"error": file_data.error,
})
return {"files": results}
Access Memory Bytes¶
from filestore import FileStore, MemoryEngine
storage = FileStore("image", engine=MemoryEngine())
@app.post("/process")
async def process(store: Store = Depends(storage)):
image = store.first("image")
raw_bytes = image.file # bytes
# Process the image...