aiohttp

No dedicated integration package — use Core directly. Wrap aiohttp multipart parts as AsyncByteSource and call AsyncUploader. Pair with uploadkit-security.

Install

Shell
pip install uploadkit uploadkit-security aiohttp
Shell
uv add uploadkit uploadkit-security aiohttp
Shell
poetry add uploadkit uploadkit-security aiohttp

Adapter glue

Shared policy and error conventions: Common patterns. Here the unique piece is the multipart AsyncByteSource.

Thin AsyncByteSource over aiohttp’s multipart body part (BodyPartReader).

adapters.py
import aiohttp
from aiohttp.multipart import BodyPartReader

class AiohttpByteSource:
    """Adapt an aiohttp multipart part for AsyncUploader."""

    def __init__(self, part: BodyPartReader) -> None:
        self._part = part
        self.name = part.filename
        self.size = None  # often unknown until fully read
        self.content_type = part.headers.get(
            aiohttp.hdrs.CONTENT_TYPE,
            "application/octet-stream",
        )

    async def read(self, size: int = -1) -> bytes:
        if size < 0:
            return await self._part.read(decode=False)
        chunk = await self._part.read_chunk(size)
        return chunk or b""

Parse multipart, find the file field, stream through AsyncUploader.

handlers.py
from aiohttp import web
from uploadkit import AsyncUploader, UploadPolicy, UploaderError
from uploadkit_security import default_async_validators

from adapters import AiohttpByteSource

async def upload_handler(request: web.Request) -> web.Response:
    reader = await request.multipart()
    part = await reader.next()
    while part is not None:
        if part.name == "file" and part.filename:
            break
        part = await reader.next()
    if part is None or not part.filename:
        return web.json_response(
            {"error": "MissingFile", "message": "file field required"},
            status=400,
        )

    policy = UploadPolicy(
        max_size=5 * 1024 * 1024,
        allowed_extensions=frozenset({"png"}),
        allowed_mime_types=frozenset({"image/png"}),
        async_validators=default_async_validators(),
    )
    try:
        result = await AsyncUploader(policy, request.app["async_storage"]).upload(
            AiohttpByteSource(part),
            bucket="uploads",
            object_name=part.filename,
            after_upload=notify,  # sync callback, async callback, or Celery-like .delay
        )
    except UploaderError as exc:
        return web.json_response(
            {"error": type(exc).__name__, "message": str(exc)},
            status=400,
        )
    return web.json_response({
        "object_name": result.object_name,
        "sha256": result.sha256,
        "etag": result.etag,
    })

Use AsyncS3Storage from Storage for AWS S3 or MinIO.

app.py
from aiohttp import web
from handlers import upload_handler
# from myapp.s3_async import AsyncS3Storage  # see /docs/storage/

async def notify(result) -> None:
    # Or pass a sync def, or a Celery-like task with .delay
    ...

# AWS:
# async_storage = AsyncS3Storage(access_key="AKIA...", secret_key="...", region="eu-west-1")
# MinIO:
async_storage = AsyncS3Storage(
    endpoint_url="http://127.0.0.1:9000",
    access_key="minioadmin",
    secret_key="minioadmin",
)

app = web.Application()
app["async_storage"] = async_storage
app.router.add_post("/upload", upload_handler)

if __name__ == "__main__":
    web.run_app(app, host="0.0.0.0", port=8080)

Same Core async stack as FastAPI — only the AsyncByteSource adapter differs. After-upload: Common patterns · Storage: Storage · Docs: uploadkit