A minimal API
import sys
from contextlib import asynccontextmanager
from pathlib import Path
from typing import Any, ClassVar, Optional
import uvicorn
from fastapi import Depends, FastAPI
from fastapi.responses import ORJSONResponse as JSONResponse
from pydantic import ConfigDict
from sqlalchemy.engine import make_url
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from examples.api_for_sqlalchemy.models.db import DB
from fastapi_jsonapi import ApplicationBuilder
from fastapi_jsonapi.misc.sqla.generics.base import ViewBaseGeneric
from fastapi_jsonapi.schema_base import BaseModel
from fastapi_jsonapi.views import Operation, OperationConfig, ViewBase
CURRENT_DIR = Path(__file__).resolve().parent
sys.path.append(f"{CURRENT_DIR.parent.parent}")
db = DB(
url=make_url(f"sqlite+aiosqlite:///{CURRENT_DIR}/db.sqlite3"),
)
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[Optional[str]]
class UserSchema(BaseModel):
"""User base schema."""
model_config = ConfigDict(
from_attributes=True,
)
name: str
class SessionDependency(BaseModel):
model_config = ConfigDict(
arbitrary_types_allowed=True,
)
session: AsyncSession = Depends(db.session)
def session_dependency_handler(view: ViewBase, dto: SessionDependency) -> dict[str, Any]:
return {
"session": dto.session,
}
class UserView(ViewBaseGeneric):
operation_dependencies: ClassVar = {
Operation.ALL: OperationConfig(
dependencies=SessionDependency,
prepare_data_layer_kwargs=session_dependency_handler,
),
}
def add_routes(app: FastAPI):
builder = ApplicationBuilder(app)
builder.add_resource(
path="/users",
tags=["User"],
view=UserView,
schema=UserSchema,
model=User,
resource_type="user",
)
builder.initialize()
# noinspection PyUnusedLocal
@asynccontextmanager
async def lifespan(app: FastAPI):
add_routes(app)
async with db.engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
yield
await db.dispose()
app = FastAPI(
title="FastAPI and SQLAlchemy",
lifespan=lifespan,
debug=True,
default_response_class=JSONResponse,
docs_url="/docs",
openapi_url="/openapi.json",
)
if __name__ == "__main__":
uvicorn.run(
app,
host="0.0.0.0",
port=8080,
)
This example provides the following API structure:
URL |
method |
endpoint |
Usage |
---|---|---|---|
/users |
GET |
user_list |
Get a collection of users |
/users |
POST |
user_list |
Create a user |
/users |
DELETE |
user_list |
Delete users |
/users/{user_id} |
GET |
user_detail |
Get user details |
/users/{user_id} |
PATCH |
user_detail |
Update a user |
/users/{user_id} |
DELETE |
user_detail |
Delete a user |
Request:
POST /users HTTP/1.1
Content-Type: application/vnd.api+json
{
"data": {
"type": "user",
"attributes": {
"name": "John"
}
}
}
Response:
HTTP/1.1 201 Created
Content-Type: application/vnd.api+json
{
"data": {
"attributes": {
"name": "John"
},
"id": "1",
"links": {
"self": "/users/1"
},
"type": "user"
},
"jsonapi": {
"version": "1.0"
},
"links": {
"self": "/users/1"
}
}
Request:
GET /users/1 HTTP/1.1
Content-Type: application/vnd.api+json
Response:
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"data": {
"attributes": {
"name": "John"
},
"id": "1",
"links": {
"self": "/users/1"
},
"type": "user"
},
"jsonapi": {
"version": "1.0"
},
"links": {
"self": "/users/1"
}
}
Request:
GET /users HTTP/1.1
Content-Type: application/vnd.api+json
Response:
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"data": [
{
"attributes": {
"name": "John"
},
"id": "1",
"links": {
"self": "/users/1"
},
"type": "user"
}
],
"jsonapi": {
"version": "1.0"
},
"links": {
"self": "http://localhost:5000/users"
},
"meta": {
"count": 1
}
}
Request:
PATCH /users/1 HTTP/1.1
Content-Type: application/vnd.api+json
{
"data": {
"id": 1,
"type": "user",
"attributes": {
"name": "Sam"
}
}
}
Response:
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"data": {
"attributes": {
"name": "Sam"
},
"id": "1",
"links": {
"self": "/users/1"
},
"type": "user"
},
"jsonapi": {
"version": "1.0"
},
"links": {
"self": "/users/1"
}
}
Request:
DELETE /users/1 HTTP/1.1
Content-Type: application/vnd.api+json
Response:
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"jsonapi": {
"version": "1.0"
},
"meta": {
"message": "Object successfully deleted"
}
}