FastApi
FastApi Example¶
This example shows how to use sqlalchemy-filterset
with FastAPI
and SQLAlchemy.
The source code is available on the Github.
Application structure¶
Application has next structure:
./
├── webapp/
│ ├── __init__.py
│ ├── application.py
│ ├── endpoints.py
│ ├── schemas.py
│ ├── models.py
│ ├── filters.py
│ ├── queries.py
│ └── tests.py
├── config.yml
├── docker-compose.yml
├── Dockerfile
└── requirements.txt
Create FastAPI application¶
from typing import Generator
from fastapi import FastAPI
from myapp.models import Base
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
engine = create_engine("postgresql://user:password@host/database")
Base.metadata.create_all(bind=engine)
SessionLocal = sessionmaker(bind=engine)
def get_db() -> Generator[Session, None, None]:
try:
db = SessionLocal()
yield db
finally:
db.close()
app = FastAPI()
Models¶
Here is an example of creating models for e-commerce application. More information about the models can be found in the official sqlalchemy documentation
import enum
import uuid
from sqlalchemy import Boolean, Column, Enum, ForeignKey, Numeric, String
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import declarative_base, relationship
Base = declarative_base()
class CategoryType(enum.Enum):
foo = "foo"
bar = "bar"
class Category(Base):
__tablename__ = "categories"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
type = Column(Enum(CategoryType), nullable=False)
title = Column(String)
class Tag(Base):
__tablename__ = "tags"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
title = Column(String)
class TagToProduct(Base):
__tablename__ = "tag_to_product"
left_id = Column(
UUID,
ForeignKey("tag.id", ondelete="CASCADE"),
primary_key=True,
)
right_id = Column(
UUID,
ForeignKey("product.id", ondelete="CASCADE"),
primary_key=True,
)
class Product(Base):
__tablename__ = "products"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(String)
price = Column(Numeric)
is_active = Column(Boolean)
category_id = Column(
UUID(as_uuid=True),
ForeignKey("categories.id"),
nullable=True,
)
category: Category = relationship("Category", backref="products")
Schemas¶
Add simple schemas for Product
model.
import uuid
from pydantic import BaseModel
class ProductOut(BaseModel):
id: uuid.UUID
name: str
price: float
is_active: bool
class Config:
orm_mode = True
Filters¶
Add FilterSet
and FilterSchema
for the Product
model.
import uuid
from pydantic import BaseModel
from webapp.models import Category, CategoryType, Product, Tag, TagToProduct
from sqlalchemy_filterset import (
Filter,
FilterSet,
InFilter,
JoinStrategy,
LimitOffsetFilter,
MultiJoinStrategy,
OrderingField,
OrderingFilter,
RangeFilter,
SearchFilter,
)
class ProductFilterSet(FilterSet):
id = Filter(Product.id)
ids = InFilter(Product.id)
name = SearchFilter(Product.name)
price = RangeFilter(Product.price)
is_active = Filter(Product.is_active)
category_type = Filter(
Category.type,
strategy=JoinStrategy(
Category,
Product.category_id == Category.id,
),
)
tag_title = Filter(
Tag.title,
strategy=MultiJoinStrategy(
JoinStrategy(TagToProduct, onclause=Product.id == TagToProduct.right_id),
JoinStrategy(Tag, onclause=Tag.id == TagToProduct.left_id),
),
)
ordering = OrderingFilter(
name=OrderingField(Product.name),
price=OrderingField(Product.price),
)
limit_offset = LimitOffsetFilter()
class ProductFilterSchema(BaseModel):
id: uuid.UUID | None
ids: list[uuid.UUID] | None
name: str | None
price: tuple[float, float] | None
is_active: bool | None
category_type: CategoryType | None
tag_title: str | None
ordering: list[str] | None
limit_offset: tuple[int | None, int | None] | None
class Config:
orm_mode = True
Queries¶
Add queries for api endpoints.
import dataclasses
import uuid
from fastapi import Query
from myapp.models import CategoryType
@dataclasses.dataclass
class ProductQuery:
id: uuid.UUID | None = Query(None)
ids: list[uuid.UUID] | None = Query(None)
name: str | None = Query(None)
price_min: int | None = Query(None)
price_max: int | None = Query(None)
is_active: bool | None = Query(None)
category_type: CategoryType | None = Query(None)
ordering: list[str] | None = Query(None)
limit: int | None = Query(None)
offset: int | None = Query(None)
@property
def limit_offset(self) -> tuple[int | None, int | None] | None:
if self.limit or self.offset:
return self.limit, self.offset
return None
@property
def price(self) -> tuple[float, float] | None:
if self.price_min and self.price_max:
return self.price_min, self.price_max
return None
Endpoints¶
Add endpoints for the application.
from fastapi import Depends
from pydantic import parse_obj_as
from sqlalchemy import select
from sqlalchemy.orm import Session
from webapp.applications import app, get_db
from webapp.filters import ProductFilterSchema, ProductFilterSet
from webapp.models import Product
from webapp.queries import ProductQuery
from webapp.schemas import ProductOut
@app.get("/products/")
def list_products(
filters: ProductQuery = Depends(),
db: Session = Depends(get_db),
) -> list[ProductOut]:
filter_set = ProductFilterSet(db, select(Product))
filter_params = parse_obj_as(ProductFilterSchema, filters)
filtered_products = filter_set.filter(filter_params.dict(exclude_none=True))
return parse_obj_as(list[ProductOut], filtered_products)
Usage¶
Run application with uvicorn
.
uvicorn webapp.application:app --reload
Conclusion¶
In conclusion, this example demonstrates how to use the sqlalchemy-filterset
library
with FastAPI
and SQLAlchemy
. It shows the structure of an application that uses this library,
including the creation of a FastAPI
application, models, schemas, and filters.
The example also includes the usage of various filters such as InFilter
, RangeFilter
,
and OrderingFilter
to filter data from the database. The example also demonstrates the use of
JoinStrategy
to filter data from related models.