Usage
Working with storages
Often in projects, you want to get input file in the API and store it somewhere.
The fastapi-storages
simplifies the process to store and retrieve the files
in a re-usable manner.
There are two storages available:
FileSystemStorage
: To store files on the local file system.S3Storage
: To store file objects in AmazonS3
or any s3-compatible object storage.
FileSystemStorage
A very minimal example to use FileSystemStorage
:
from fastapi import FastAPI, UploadFile
from fastapi_storages import FileSystemStorage
app = FastAPI()
storage = FileSystemStorage(path="/tmp")
@app.post("/upload/")
def create_upload_file(file: UploadFile):
storage.write(file)
This will configure a FileSystemStorage
to store files in the /tmp
directory
and the request file is automatically saved into the destination.
S3Storage
Now let's see a minimal example of using S3Storage
in action:
from fastapi import FastAPI, UploadFile
from fastapi_storages import S3Storage
class PublicAssetS3Storage(S3Storage):
AWS_ACCESS_KEY_ID = "access"
AWS_SECRET_ACCESS_KEY = "secret"
AWS_S3_BUCKET_NAME = "test"
AWS_S3_ENDPOINT_URL = "s3.amazonaws.com"
AWS_DEFAULT_ACL = "public-read"
AWS_S3_USE_SSL = True
app = FastAPI()
storage = PublicAssetS3Storage()
@app.post("/upload/")
def create_upload_file(file: UploadFile):
storage.write(file)
As you can see the code is not changed and storage.write(file)
is called the same way
it was used in FileSystemStorage
.
Warning
You should never hard-code credentials like AWS_ACCESS_KEY_ID
and AWS_SECRET_ACCESS_KEY
in the code.
Instead, you can read values from environment variables or as a handy way, fastapi-storages
will use the environment variables automatically, if they are defined.
Working with ORM extensions
The example you saw was useful, but fastapi-storages
has ORM integrations
which makes storing and serving the files easier.
Support ORM include:
SQLAlchemy
SQLAdmin
SQLAlchemy
You can use custom SQLAlchemy
types from fastapi-storages
for this.
Supported types include:
FileType
ImageType
Let's see an example:
from fastapi import FastAPI, UploadFile
from sqlalchemy import Column, Integer, create_engine
from sqlalchemy.orm import Session, declarative_base
from fastapi_storages import FileSystemStorage
from fastapi_storages.integrations.sqlalchemy import FileType
app = FastAPI()
Base = declarative_base()
engine = create_engine("sqlite:///test.db")
class Example(Base):
__tablename__ = "example"
id = Column(Integer, primary_key=True)
file = Column(FileType(storage=FileSystemStorage(path="/tmp")))
# Create database and table
Base.metadata.create_all(engine)
@app.post("/upload/")
def create_upload_file(file: UploadFile):
example = Example(file=file)
with Session(engine) as session:
session.add(example)
session.commit()
return {"filename": example.file.name}
As you can see the API create_upload_file
has changed compared to using the storage directly.
You don't need the storage.write(...)
call anymore.
With the custom SQLAlchemy types, before the model is saved in database,
the file is stored in the specified storage and then the record is saved.
You can just replace the storage with S3Storage
and everything works without the change.
This will make your code cleaner and more readable.
Integration with Alembic
By default, custom types are not registered in Alembic's migrations. To integrate these new types with Alembic, you can do either of these:
Create new "type" on top of these types
We create the following snippet in custom_types.py
from fastapi_storages.integrations.sqlalchemy import FileType as _FileType
from fastapi_storages import FileSystemStorage
class FileType(_FileType):
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(storage=FileSystemStorage(path='/tmp'), *args, **kwargs)
And by using the new FileType
Alembic can do the imports properly, it's a simple trick, but is very simple.
Add files path to script.py.mako
Alembic allows you to modify alembic/script.py.mako
and the migrations are generated with proper imports.
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
import sqlalchemy as sa
from alembic import op
import path_to_custom_types_py_file
${imports if imports else ""}
# THE REST OF SCRIPT