Skip to content

Using Multiple Connectors

FennFlow makes it trivial to work with multiple object storages simultaneously. Each UnitOfWork owns its own connector and backend scope, so you can compose them freely in the same coroutine.

Example: Migrating files from AWS S3 to MinIO

import asyncio

from fennflow import ConfigDict, UnitOfWork
from fennflow.backends import SqlalchemyBackendConfig
from fennflow.connectors import S3ConnectorConfig
from fennflow.repositories import (
    GetRepository,
    ListRepository,
    PutRepository,
    S3RepoField,
)


class ReadRepository(GetRepository, ListRepository):
    pass


class WriteRepository(PutRepository):
    pass


class AWSS3UOW(UnitOfWork):
    files = S3RepoField(ReadRepository, bucket_name="my-bucket")
    config = ConfigDict(
        backend=SqlalchemyBackendConfig(scope="aws"),
        connector=S3ConnectorConfig(
            endpoint_url="https://s3.amazonaws.com",
            aws_access_key_id="aws-key",
            aws_secret_access_key="aws-secret",
        ),
    )


class MinIOUOW(UnitOfWork):
    files = S3RepoField(WriteRepository, bucket_name="my-bucket")
    config = ConfigDict(
        backend=SqlalchemyBackendConfig(scope="minio"),
        connector=S3ConnectorConfig(
            endpoint_url="https://minio.example.com",
            aws_access_key_id="minio-key",
            aws_secret_access_key="minio-secret",
        ),
    )


async def main():
    async with AWSS3UOW() as aws, MinIOUOW() as minio:
        paths = await aws.files.at("folder/").list()

        for path in paths:
            response = await aws.files.get(path)
            await minio.files.at("folder/").put(*response)


if __name__ == "__main__":
    asyncio.run(main())

The two UoWs are fully independent — different credentials, different endpoints, different backend scopes. If anything fails mid-transfer, each UoW rolls back its own pending operations independently.