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"
  }
}