Skip to content

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.

@app.post("/upload")
async def upload(store: Store = Depends(storage)) -> Store:
    return store

Need a plain dict? Use standard Pydantic:

store.model_dump(mode="json")

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...

Aggregate Size

@app.post("/upload")
async def upload(store: Store = Depends(storage)):
    return {
        "total_files": store.total_files,
        "total_size_mb": round(store.total_size / (1024 * 1024), 2),
        "successful": len(store.successful_files),
        "failed": len(store.failed_files),
    }