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 contextlib import asynccontextmanager
from pathlib import Path
from typing import ClassVar, Annotated, Optional
import uvicorn
from fastapi import APIRouter, 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.types_metadata import ClientCanSetId
from fastapi_jsonapi.views import ViewBase, Operation, OperationConfig
CURRENT_FILE = Path(__file__).resolve()
CURRENT_DIR = CURRENT_FILE.parent
sys.path.append(f"{CURRENT_DIR.parent.parent}")
db = DB(
url=make_url(f"sqlite+aiosqlite:///{CURRENT_DIR.absolute()}/db.sqlite3"),
)
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[Optional[str]]
class UserAttributesBaseSchema(BaseModel):
model_config = ConfigDict(
from_attributes=True,
)
name: str
class UserSchema(UserAttributesBaseSchema):
"""User base schema."""
class UserPatchSchema(UserAttributesBaseSchema):
"""User PATCH schema."""
class UserInSchema(UserAttributesBaseSchema):
"""User input schema."""
id: Annotated[int, ClientCanSetId()]
class SessionDependency(BaseModel):
model_config = ConfigDict(
arbitrary_types_allowed=True,
)
session: AsyncSession = Depends(db.session)
def session_dependency_handler(view: ViewBase, dto: SessionDependency) -> dict:
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,
resource_type="user",
schema_in_patch=UserPatchSchema,
schema_in_post=UserInSchema,
model=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(
f"{CURRENT_FILE.name.replace(CURRENT_FILE.suffix, '')}:app",
host="0.0.0.0",
port=8084,
reload=True,
app_dir=f"{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.