Client generated id
According to the specification JSON:API doc
it is possible to create an id
on the client and pass
it to the server. Let’s define the id type as a UUID.
Request:
POST /users HTTP/1.1
Content-Type: application/vnd.api+json
{
"data": {
"type": "user",
"attributes": {
"name": "John"
},
"id": "867ab602-d9f7-44d0-8d3a-d2ae6d96b3a7"
}
}
Response:
HTTP/1.1 201 Created
Content-Type: application/vnd.api+json
{
"data": {
"type": "user",
"id": "867ab602-d9f7-44d0-8d3a-d2ae6d96b3a7",
"attributes": {
"name": "John"
},
"links": {
"self": "/users/867ab602-d9f7-44d0-8d3a-d2ae6d96b3a7"
},
},
"jsonapi": {
"version": "1.0"
},
"links": {
"self": "/users/867ab602-d9f7-44d0-8d3a-d2ae6d96b3a7"
}
}
In order to do this you need to define an id
with the Field keyword client_can_set_id in the
schema
or schema_in_post
.
Example:
import sys
from pathlib import Path
from typing import ClassVar
import uvicorn
from fastapi import APIRouter, Depends, FastAPI
from fastapi_jsonapi.schema_base import Field, BaseModel as PydanticBaseModel
from sqlalchemy import Column, Integer, Text
from sqlalchemy.engine import make_url
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from fastapi_jsonapi import RoutersJSONAPI, init
from fastapi_jsonapi.misc.sqla.generics.base import DetailViewBaseGeneric, ListViewBaseGeneric
from fastapi_jsonapi.views.utils import HTTPMethod, HTTPMethodConfig
from fastapi_jsonapi.views.view_base import ViewBase
CURRENT_FILE = Path(__file__).resolve()
CURRENT_DIR = CURRENT_FILE.parent
PROJECT_DIR = CURRENT_DIR.parent.parent
DB_URL = f"sqlite+aiosqlite:///{CURRENT_DIR.absolute()}/db.sqlite3"
sys.path.append(str(PROJECT_DIR))
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, autoincrement=False)
name = Column(Text, nullable=True)
class BaseModel(PydanticBaseModel):
class Config:
orm_mode = True
class UserAttributesBaseSchema(BaseModel):
name: str
class UserSchema(UserAttributesBaseSchema):
"""User base schema."""
class UserPatchSchema(UserAttributesBaseSchema):
"""User PATCH schema."""
class UserInSchema(UserAttributesBaseSchema):
"""User input schema."""
id: int = Field(client_can_set_id=True)
async def get_session():
sess = sessionmaker(
bind=create_async_engine(url=make_url(DB_URL)),
class_=AsyncSession,
expire_on_commit=False,
)
async with sess() as db_session: # type: AsyncSession
yield db_session
await db_session.rollback()
async def sqlalchemy_init() -> None:
engine = create_async_engine(url=make_url(DB_URL))
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
class SessionDependency(BaseModel):
session: AsyncSession = Depends(get_session)
class Config:
arbitrary_types_allowed = True
def session_dependency_handler(view: ViewBase, dto: SessionDependency) -> dict:
return {"session": dto.session}
class UserDetailView(DetailViewBaseGeneric):
method_dependencies: ClassVar = {
HTTPMethod.ALL: HTTPMethodConfig(
dependencies=SessionDependency,
prepare_data_layer_kwargs=session_dependency_handler,
)
}
class UserListView(ListViewBaseGeneric):
method_dependencies: ClassVar = {
HTTPMethod.ALL: HTTPMethodConfig(
dependencies=SessionDependency,
prepare_data_layer_kwargs=session_dependency_handler,
)
}
def add_routes(app: FastAPI):
tags = [
{
"name": "User",
"description": "",
},
]
router: APIRouter = APIRouter()
RoutersJSONAPI(
router=router,
path="/users",
tags=["User"],
class_detail=UserDetailView,
class_list=UserListView,
schema=UserSchema,
resource_type="user",
schema_in_patch=UserPatchSchema,
schema_in_post=UserInSchema,
model=User,
)
app.include_router(router, prefix="")
return tags
def create_app() -> FastAPI:
"""
Create app factory.
:return: app
"""
app = FastAPI(
title="FastAPI and SQLAlchemy",
debug=True,
openapi_url="/openapi.json",
docs_url="/docs",
)
add_routes(app)
app.on_event("startup")(sqlalchemy_init)
init(app)
return app
app = create_app()
if __name__ == "__main__":
current_file_name = CURRENT_FILE.name.replace(CURRENT_FILE.suffix, "")
uvicorn.run(
f"{current_file_name}:app",
host="0.0.0.0",
port=8084,
reload=True,
app_dir=str(CURRENT_DIR),
)
In case the key client_can_set_id is not set, the id
field will be ignored in post requests.
In fact, the library deviates slightly from the specification and allows you to use any type, not just UUID. Just define the one you need in the Pydantic model to do it.