Skip to content

Working with File Content

FennFlow wraps raw bytes in typed content models. Instead of juggling bytes and dict, you work with TextContent, JsonContent, BinaryContent and others — each with typed accessors for their data.

Example: Creating, uploading and retrieving typed files

import asyncio

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


class ReadWriteRepository(CreateRepository, GetRepository, ListRepository):
    pass


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


async def main():
    # Three ways to create file content
    text_file = TextContent.from_content("Hello, world!")
    json_file = JsonContent.from_content({"user": "alice", "score": 42})

    BinaryContent.from_local_path("my_file.txt")
    binary_file = BinaryContent(
        data=b"some bytes",
        media_type=MediaType.APPLICATION_OCTET_STREAM,
    )

    async with AppUOW() as uow:
        await uow.files.at("folder/").create(text_file, json_file, binary_file)

        paths = await uow.files.at("folder/").list()
        response = await uow.files.get(*paths)

        print(response.texts[0].content)  # "Hello, world!"
        print(response.texts[1].filename)  # "my_file.txt"

        print(response.filter(JsonContent)[0].content)  # {"user": "alice", "score": 42}
        print(
            response.filter_by_media_type(MediaType.APPLICATION_OCTET_STREAM)[0].data
        )  # b"some bytes"


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

TextContent.content returns a str, JsonContent.content returns the parsed Python object — no manual decode() or json.loads() needed.

MediaResponse uses ContentFactory to resolve the correct class on retrieval automatically based on the stored MIME type, so .texts, .filter(), .filter_by_media_type() and other typed accessors on MediaResponse always return the right types.