Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ build:

sphinx:
configuration: docs/src/conf.py

python:
install:
- requirements: docs/requirements.txt
- method: pip
path: .
extra_requirements:
- docs
89 changes: 62 additions & 27 deletions docs/src/usage.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
Usage
=====

This section provides practical examples and guidance on using ``fastapi-async-storages``
to handle asynchronous file storage in your FastAPI applications.

Working with storages
---------------------

Often in projects, you want to get input file in the API and store it somewhere.
The `fastapi-async-storages` simplifies the process to store and retrieve the files in a re-usable manner.

Expand Down Expand Up @@ -56,7 +54,6 @@ Now let's see a minimal example of using :class:`~async_storages.S3Storage` in a

Working with ORM extensions
---------------------------

The example you saw was useful, but **fastapi-async-storages** has ORM integrations
which makes storing and serving the files easier.

Expand All @@ -67,7 +64,6 @@ Support ORM include:

SQLAlchemy
~~~~~~~~~~

You can use custom :code:`SQLAlchemy` types from :code:`fastapi-async-storages` for this.

Supported types include:
Expand Down Expand Up @@ -101,32 +97,71 @@ Let's see an example:
)

class Document(Base):
__tablename__ = "documents"
__tablename__ = "documents"

id = Column(Integer, primary_key=True)
file = Column(FileType(storage=storage))
image = Column(ImageType(storage=storage))
id = Column(Integer, primary_key=True)
file = Column(FileType(storage=storage))
image = Column(ImageType(storage=storage))

async def main():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)

# create an in-memory image
img_buf = BytesIO()
Image.new("RGB", (32, 16), color=(255, 0, 0)).save(img_buf, format="PNG")
img_buf.seek(0)

# upload and link file and image
img_name await storage.upload(img_buf, "uploads/test-image.png")
file_name = await storage.upload(BytesIO(b"hello world"), "uploads/test.txt")

async with async_session() as session:
doc = Document(file=file_name, image=img_name)
session.add(doc)
await session.commit()

doc = await session.get(Document, doc.id)
url = await doc.file.get_path()
print(url)
width, height = await doc.image.get_dimensions()
print(f"Dimensions: {width}x{height}")
# create an in-memory image
img_buf = BytesIO()
Image.new("RGB", (32, 16), color=(255, 0, 0)).save(img_buf, format="PNG")
img_buf.seek(0)

# upload and link file and image
img_name await storage.upload(img_buf, "uploads/test-image.png")
file_name = await storage.upload(BytesIO(b"hello world"), "uploads/test.txt")

async with async_session() as session:
doc = Document(file=file_name, image=img_name)
session.add(doc)
await session.commit()

doc = await session.get(Document, doc.id)
url = await doc.file.get_path()
print(url)
width, height = await doc.image.get_dimensions()
print(f"Dimensions: {width}x{height}")

Integration with `Alembic <https://alembic.sqlalchemy.org/en/latest/>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
By default, custom types are not registered in Alembic's migrations.
To integrate these new types with Alembic, you can do this:

We create the following snippet in ``custom_types.py``:

.. code-block:: python

from typing import Any
from async_storages.integrations.sqlalchemy import FileType as _FileType

from app.core.storages import storage

class FileType(_FileType):
def __init__(self, *args: Any, **kwargs: Any):
super().__init__(storage=storage, *args, **kwargs)

And by using the new :class:`~async_storages.integrations.sqlalchemy.FileType` Alembic can do the imports properly.

Add files path to ``script.py.mako``.
Alembic allows you to modify ``script.py.mako`` and the migrations are generated with proper imports.

.. code-block:: mako

"""${message}

Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}

"""
from alembic import op
import sqlalchemy as sa
import path_to_custom_types_py_file
${imports if imports else ""}

# THE REST OF SCRIPT
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ dependencies = []

[project.optional-dependencies]
s3 = ["aioboto3>=15.4.0"]
docs = [
"furo>=2025.9.25",
"sphinx>=8.2.3",
]

[dependency-groups]
dev = [
Expand All @@ -21,10 +25,6 @@ dev = [
"pytest-asyncio>=1.2.0",
"sqlalchemy>=2.0.44",
]
docs = [
"furo>=2025.9.25",
"sphinx>=8.2.3",
]

[tool.uv.build-backend]
module-root = "src"
Expand Down
20 changes: 10 additions & 10 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.