Skip to content

Home

Atomic-like S3 Framework, the Pydantic way

CI Codacy Coverage Roadmap versions License: MIT Last Commit Codacy Grade

FennFlow is a Python s3 framework designed to help you quickly, confidently, and painlessly manipulate files in your object storage implementing Saga-like compensation flow.

Why use FennFlow?

Working with aiobotocore often feels like handling raw bytes and dicts. FennFlow wraps S3 operations into a high-level Unit of Work pattern, providing:

  • Atomic-like multistep operations β€” if something fails, previous actions are automatically compensated (Saga Pattern).
  • Clean Architecture β€” treat S3 as proper repositories using mixins (CreateRepository, GetRepository, etc.).
  • Pydantic-powered models β€” work with TextContent, JsonContent, ImageContent and others instead of raw bytes.

Supported Connectors

Connector Description
AWS S3 s3 compatible object storage via aiobotocore
In-Memory (default) great for and tests and development

Supported Backends

FennFlow uses backend as a source of truth for your file storage. No matter what your file storage contains, backend ensures your data is consistent.

Backend Description
In-Memory (default) great for and tests, development and small projects

Backend Comparison

Raw aiobotocore In-Memory
Consistency πŸ”΄ None
No link between files and metadata
🟑 Medium
Consistent within process lifetime, lost on crash
Compensation πŸ”΄ None
Orphaned files on failure
🟑 Medium
Automatic within process, orphaned files possible on crash
Reliability πŸ”΄ Low
Failures leave storage in unknown state
🟑 Medium
Syncs with storage on restart, files uploaded during crash cannot be compensated
Latency βœ… Lowest
Pure S3 network overhead only
βœ… Lowest
Minimal in-process overhead
Infrastructure βœ… None βœ… None
Memory usage βœ… None 🟑 Stores file metadata in-process

Quick Start

Here's a minimal example of FennFlow:

import asyncio

from fennflow import ConfigDict, UnitOfWork
from fennflow.backends import InMemoryBackendConfig
from fennflow.connectors import S3ConnectorConfig
from fennflow.files import BinaryContent, JsonContent, TextContent
from fennflow.repositories import (
    DeleteRepository,
    GetRepository,
    ListRepository,
    CreateRepository,
    S3RepoField,
    )


# 1. Define your repository with mixins
class CrudRepository(
    CreateRepository,
    DeleteRepository,
    GetRepository,
    ListRepository,
    ):
    pass


# 2. Set up your Unit of Work
class UOW(UnitOfWork):
    my_files = S3RepoField(CrudRepository, bucket_name="my_files")
    config = ConfigDict(
        backend=InMemoryBackendConfig(),
        connector=S3ConnectorConfig(),
        )


async def main():
    text_file = TextContent.from_content("Hello, world!")
    json_file = JsonContent.from_content([1, 2, 3])
    binary_file = BinaryContent(data=b"some bytes", media_type="text/plain")

    async with UOW() as uow:
        await uow.my_files.at("folder1").put(
            text_file,
            json_file,
            binary_file,
            )

        paths = await uow.my_files.at("folder1").list()
        print(paths)  # ListResponse[Filepath, ...]

        files = await uow.my_files.get(*paths)
        print(files)  # MediaResponse[TextContent, JsonContent, BinaryContent]


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

(This example is complete, it can be run β€œas is”, assuming you’ve installed the fennflow package)

Next Steps

Read the docs to learn more about working with FennFlow.

Read the API Reference to understand FennFlow AI’s interface.