Skip to main content

FastAPI extension to create REST web api according to JSON:API 1.0 specification with FastAPI, Pydantic and data provider of your choice (SQLAlchemy, Tortoise ORM)

Project description

Documentation Status PyPI

FastAPI-JSONAPI

FastAPI-JSONAPI is a FastAPI extension for building REST APIs. Implementation of a strong specification JSONAPI 1.0. This framework is designed to quickly build REST APIs and fit the complexity of real life projects with legacy data and multiple data storages.

Architecture

docs/img/schema.png

Install

pip install FastAPI-JSONAPI

A minimal API

Create a test.py file and copy the following code into it

import sys
from pathlib import Path
from typing import Any, Dict, List, Union, Optional

import uvicorn
from fastapi import APIRouter, Depends, FastAPI
from pydantic import BaseModel
from sqlalchemy import Column, Text, Integer, select
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 sqlalchemy.sql import Select

from fastapi_jsonapi import RoutersJSONAPI
from fastapi_jsonapi import SqlalchemyEngine
from fastapi_jsonapi.data_layers.orm import DBORMType
from fastapi_jsonapi.querystring import QueryStringManager
from fastapi_jsonapi.schema import JSONAPIResultListSchema
from fastapi_jsonapi.schema import collect_app_orm_schemas

CURRENT_FILE = Path(__file__).resolve()
CURRENT_DIR = CURRENT_FILE.parent
PROJECT_DIR = CURRENT_DIR.parent.parent

sys.path.append(str(PROJECT_DIR))

Base = declarative_base()


def async_session() -> sessionmaker:
    uri = "sqlite+aiosqlite:///db.sqlite3"
    engine = create_async_engine(url=make_url(uri))
    _async_session = sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)
    return _async_session


class Connector:

    @classmethod
    async def get_session(cls):
        """
        Getting a session to the database.

        :return:
        """
        async_session_ = async_session()
        async with async_session_() as db_session:
            async with db_session.begin():
                yield db_session


class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True, autoincrement=True)
    first_name: str = Column(Text, nullable=True)


class UserBaseSchema(BaseModel):
    """User base schema."""

    class Config:
        """Pydantic schema config."""
        orm_mode = True

    first_name: Optional[str] = None


class UserPatchSchema(UserBaseSchema):
    """User PATCH schema."""


class UserInSchema(UserBaseSchema):
    """User input schema."""


class UserSchema(UserInSchema):
    """User item schema."""

    class Config:
        """Pydantic model config."""
        orm_mode = True
        model = "users"

    id: int


class UserDetail:

    @classmethod
    async def get(cls, obj_id: int, session: AsyncSession = Depends(Connector.get_session)) -> UserSchema:
        user: User = (await session.execute(select(User).where(User.id == obj_id))).scalar_one()
        return UserSchema.from_orm(user)

    @classmethod
    async def patch(
        cls,
        obj_id: int,
        data: UserPatchSchema,
        session: AsyncSession = Depends(Connector.get_session),
    ) -> UserSchema:
        user: User = (await session.execute(select(User).where(User.id == obj_id))).scalar_one()
        user.first_name = data.first_name
        await session.commit()
        return UserSchema.from_orm(user)

    @classmethod
    async def delete(cls, obj_id: int, session: AsyncSession = Depends(Connector.get_session)) -> None:
        user: User = (await session.execute(select(User).where(User.id == obj_id))).scalar_one()
        await session.delete(user)
        await session.commit()


class UserList:
    @classmethod
    async def get(
        cls,
        query_params: QueryStringManager,
        session: AsyncSession = Depends(Connector.get_session),
    ) -> Union[Select, JSONAPIResultListSchema]:
        user_query = select(User)
        dl = SqlalchemyEngine(query=user_query, schema=UserSchema, model=User, session=session)
        count, users_db = await dl.get_collection(qs=query_params)
        total_pages = count // query_params.pagination.size + (count % query_params.pagination.size and 1)
        users: List[UserSchema] = [UserSchema.from_orm(i_user) for i_user in users_db]
        return JSONAPIResultListSchema(
            meta={"count": count, "totalPages": total_pages},
            data=[{"id": i_obj.id, "attributes": i_obj.dict(), "type": "user"} for i_obj in users],
        )

    @classmethod
    async def post(cls, data: UserInSchema, session: AsyncSession = Depends(Connector.get_session)) -> UserSchema:
        user = User(first_name=data.first_name)
        session.add(user)
        await session.commit()
        return UserSchema.from_orm(user)


def add_routes(app: FastAPI) -> List[Dict[str, Any]]:
    tags = [
        {
            "name": "User",
            "description": "",
        },
    ]

    routers: APIRouter = APIRouter()
    RoutersJSONAPI(
        router=routers,
        path="/user",
        tags=["User"],
        class_detail=UserDetail,
        class_list=UserList,
        schema=UserSchema,
        type_resource="user",
        schema_in_patch=UserPatchSchema,
        schema_in_post=UserInSchema,
        model=User,
        engine=DBORMType.sqlalchemy,
    )

    app.include_router(routers, prefix="")
    return tags


async def sqlalchemy_init() -> None:
    uri = "sqlite+aiosqlite:///db.sqlite3"
    engine = create_async_engine(url=make_url(uri))
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.drop_all)
        await conn.run_sync(Base.metadata.create_all)


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)
    collect_app_orm_schemas(app)
    return app


app = create_app()

if __name__ == "__main__":
    uvicorn.run(
        "test:app",
        host="0.0.0.0",
        port=8084,
        reload=True,
        app_dir=str(CURRENT_DIR),
    )

This example provides the following API structure:

URL method endpoint Usage
/user GET user_list Get a collection of users
/user POST user_list Create a user
/user/< int:int > GET user_detail Get user details
/user/< int:int > PATCH person_detail Update a user
/user/< int:int > DELETE person_detail Delete a user

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fastapi_jsonapi-1.1.0.tar.gz (54.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastapi_jsonapi-1.1.0-py2.py3-none-any.whl (56.1 kB view details)

Uploaded Python 2Python 3

File details

Details for the file fastapi_jsonapi-1.1.0.tar.gz.

File metadata

  • Download URL: fastapi_jsonapi-1.1.0.tar.gz
  • Upload date:
  • Size: 54.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.4

File hashes

Hashes for fastapi_jsonapi-1.1.0.tar.gz
Algorithm Hash digest
SHA256 e54e83daedc37491eae85f6d11a3b89e27cbfe7923f41453a1199b3d3809198f
MD5 83f6bd0ad9c10fabdfe97fd10484eca7
BLAKE2b-256 f4cfd531e3c87689fa9459ae794b85181f6238252ecd835c335a8cf6aaef41f7

See more details on using hashes here.

File details

Details for the file fastapi_jsonapi-1.1.0-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for fastapi_jsonapi-1.1.0-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 bbbbbcc02f821836c62645b2f3afd096f35cade82783c96716d5cc078586475d
MD5 dcc228e529db3e0a189f6176fe7d7659
BLAKE2b-256 6031cabfea263832e75505bfd7bb6df993ab4773c91d557809a1abc0d1c10a88

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page