88
99[ ![ 📖 Docs (gh-pages)] ( https://github.com/mts-ai/FastAPI-JSONAPI/actions/workflows/documentation.yaml/badge.svg )] ( https://mts-ai.github.io/FastAPI-JSONAPI/ )
1010
11+
1112# FastAPI-JSONAPI
1213
1314FastAPI-JSONAPI is a FastAPI extension for building REST APIs.
@@ -30,177 +31,159 @@ pip install FastAPI-JSONAPI
3031Create a test.py file and copy the following code into it
3132
3233``` python
34+ import sys
35+ from collections.abc import AsyncIterator
36+ from contextlib import asynccontextmanager
3337from pathlib import Path
34- from typing import Any, ClassVar, Dict
38+ from typing import Any, ClassVar, Optional
39+ from typing import Union
3540
3641import uvicorn
37- from fastapi import APIRouter, Depends, FastAPI
38- from sqlalchemy import Column, Integer, Text
42+ from fastapi import Depends, FastAPI
43+ from fastapi.responses import ORJSONResponse as JSONResponse
44+ from pydantic import ConfigDict
45+ from sqlalchemy.engine import URL
3946from sqlalchemy.engine import make_url
40- from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
41- from sqlalchemy.ext.declarative import declarative_base
42- from sqlalchemy.orm import sessionmaker
47+ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine
48+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
4349
44- from fastapi_jsonapi import RoutersJSONAPI, init
45- from fastapi_jsonapi.misc.sqla.generics.base import DetailViewBaseGeneric, ListViewBaseGeneric
50+ from fastapi_jsonapi import ApplicationBuilder
51+ from fastapi_jsonapi.misc.sqla.generics.base import ViewBaseGeneric
4652from fastapi_jsonapi.schema_base import BaseModel
47- from fastapi_jsonapi.views.utils import HTTPMethod, HTTPMethodConfig
48- from fastapi_jsonapi.views.view_base import ViewBase
49-
50- CURRENT_FILE = Path(__file__ ).resolve()
51- CURRENT_DIR = CURRENT_FILE .parent
52- DB_URL = f " sqlite+aiosqlite:/// { CURRENT_DIR } /db.sqlite3 "
53-
54- Base = declarative_base()
55-
56-
57- class User (Base ):
58- __tablename__ = " users"
59- id = Column(Integer, primary_key = True , autoincrement = True )
60- name = Column(Text, nullable = True )
61-
62-
63- class UserAttributesBaseSchema (BaseModel ):
64- name: str
65-
66- class Config :
67- """ Pydantic schema config."""
53+ from fastapi_jsonapi.views import ViewBase, Operation, OperationConfig
54+
55+ CURRENT_DIR = Path(__file__ ).resolve().parent
56+ sys.path.append(f " { CURRENT_DIR .parent.parent} " )
57+
58+
59+ class DB :
60+ def __init__ (
61+ self ,
62+ url : Union[str , URL ],
63+ echo : bool = False ,
64+ echo_pool : bool = False ,
65+ ):
66+ self .engine: AsyncEngine = create_async_engine(
67+ url = url,
68+ echo = echo,
69+ echo_pool = echo_pool,
70+ )
6871
69- orm_mode = True
72+ self .session_maker: async_sessionmaker[AsyncSession] = async_sessionmaker(
73+ autocommit = False ,
74+ bind = self .engine,
75+ expire_on_commit = False ,
76+ )
7077
78+ async def dispose (self ):
79+ await self .engine.dispose()
7180
72- class UserSchema (UserAttributesBaseSchema ):
73- """ User base schema."""
81+ async def session (self ) -> AsyncIterator[AsyncSession]:
82+ async with self .session_maker() as session:
83+ yield session
7484
7585
76- class UserPatchSchema (UserAttributesBaseSchema ):
77- """ User PATCH schema."""
86+ db = DB(
87+ url = make_url(f " sqlite+aiosqlite:/// { CURRENT_DIR } /db.sqlite3 " ),
88+ )
7889
7990
80- class UserInSchema ( UserAttributesBaseSchema ):
81- """ User input schema. """
91+ class Base ( DeclarativeBase ):
92+ pass
8293
8394
84- def async_session () -> sessionmaker:
85- engine = create_async_engine(url = make_url(DB_URL ))
86- _async_session = sessionmaker(bind = engine, class_ = AsyncSession, expire_on_commit = False )
87- return _async_session
95+ class User (Base ):
96+ __tablename__ = " users"
8897
98+ id : Mapped[int ] = mapped_column(primary_key = True )
99+ name: Mapped[Optional[str ]]
89100
90- class Connector :
91- @ classmethod
92- async def get_session (cls ):
93- """
94- Get session as dependency
95101
96- :return:
97- """
98- sess = async_session()
99- async with sess() as db_session: # type: AsyncSession
100- yield db_session
101- await db_session.rollback()
102+ class UserSchema (BaseModel ):
103+ """ User base schema."""
102104
105+ model_config = ConfigDict(
106+ from_attributes = True ,
107+ )
103108
104- async def sqlalchemy_init () -> None :
105- engine = create_async_engine(url = make_url(DB_URL ))
106- async with engine.begin() as conn:
107- await conn.run_sync(Base.metadata.create_all)
109+ name: str
108110
109111
110112class SessionDependency (BaseModel ):
111- session: AsyncSession = Depends(Connector.get_session)
113+ model_config = ConfigDict(
114+ arbitrary_types_allowed = True ,
115+ )
112116
113- class Config :
114- arbitrary_types_allowed = True
117+ session: AsyncSession = Depends(db.session)
115118
116119
117- def session_dependency_handler (view : ViewBase, dto : SessionDependency) -> Dict [str , Any]:
120+ def session_dependency_handler (view : ViewBase, dto : SessionDependency) -> dict [str , Any]:
118121 return {
119122 " session" : dto.session,
120123 }
121124
122125
123- class UserDetailView (DetailViewBaseGeneric ):
124- method_dependencies: ClassVar[Dict[HTTPMethod, HTTPMethodConfig]] = {
125- HTTPMethod.ALL : HTTPMethodConfig(
126- dependencies = SessionDependency,
127- prepare_data_layer_kwargs = session_dependency_handler,
128- )
129- }
130-
131-
132- class UserListView (ListViewBaseGeneric ):
133- method_dependencies: ClassVar[Dict[HTTPMethod, HTTPMethodConfig]] = {
134- HTTPMethod.ALL : HTTPMethodConfig(
126+ class UserView (ViewBaseGeneric ):
127+ operation_dependencies: ClassVar = {
128+ Operation.ALL : OperationConfig(
135129 dependencies = SessionDependency,
136130 prepare_data_layer_kwargs = session_dependency_handler,
137- )
131+ ),
138132 }
139133
140134
141135def add_routes (app : FastAPI):
142- tags = [
143- {
144- " name" : " User" ,
145- " description" : " " ,
146- },
147- ]
148-
149- router: APIRouter = APIRouter()
150- RoutersJSONAPI(
151- router = router,
136+ builder = ApplicationBuilder(app)
137+ builder.add_resource(
152138 path = " /users" ,
153139 tags = [" User" ],
154- class_detail = UserDetailView,
155- class_list = UserListView,
140+ view = UserView,
156141 schema = UserSchema,
157- resource_type = " user" ,
158- schema_in_patch = UserPatchSchema,
159- schema_in_post = UserInSchema,
160142 model = User,
143+ resource_type = " user" ,
161144 )
145+ builder.initialize()
162146
163- app.include_router(router, prefix = " " )
164- return tags
165147
148+ # noinspection PyUnusedLocal
149+ @asynccontextmanager
150+ async def lifespan (app : FastAPI):
151+ add_routes(app)
166152
167- def create_app () -> FastAPI:
168- """
169- Create app factory.
153+ async with db.engine.begin() as conn:
154+ await conn.run_sync(Base.metadata.create_all)
155+
156+ yield
157+
158+ await db.dispose()
170159
171- :return: app
172- """
173- app = FastAPI(
174- title = " FastAPI and SQLAlchemy" ,
175- debug = True ,
176- openapi_url = " /openapi.json" ,
177- docs_url = " /docs" ,
178- )
179- add_routes(app)
180- app.on_event(" startup" )(sqlalchemy_init)
181- init(app)
182- return app
183160
161+ app = FastAPI(
162+ title = " FastAPI and SQLAlchemy" ,
163+ lifespan = lifespan,
164+ debug = True ,
165+ default_response_class = JSONResponse,
166+ docs_url = " /docs" ,
167+ openapi_url = " /openapi.json" ,
168+ )
184169
185- app = create_app()
186170
187171if __name__ == " __main__" :
188172 uvicorn.run(
189- " main: app" ,
173+ app,
190174 host = " 0.0.0.0" ,
191175 port = 8080 ,
192- reload = True ,
193- app_dir = str (CURRENT_DIR ),
194176 )
195177```
196178
197179This example provides the following API structure:
198180
199- | URL | method | endpoint | Usage |
200- | -------------------| --------| -------------| ---------------------------|
201- | ` /users ` | GET | user_list | Get a collection of users |
202- | ` /users ` | POST | user_list | Create a user |
203- | ` /users ` | DELETE | user_list | Delete users |
204- | ` /users/{obj_id} ` | GET | user_detail | Get user details |
205- | ` /users/{obj_id} ` | PATCH | user_detail | Update a user |
206- | ` /users/{obj_id} ` | DELETE | user_detail | Delete a user |
181+ | URL | method | endpoint | Usage |
182+ | --------------------| --------| -------------| -------------------------------|
183+ | ` /users/ ` | GET | user_list | Get a collection of users |
184+ | ` /users/ ` | POST | user_list | Create a user |
185+ | ` /users/ ` | DELETE | user_list | Delete users |
186+ | ` /users/{obj_id}/ ` | GET | user_detail | Get user details |
187+ | ` /users/{obj_id}/ ` | PATCH | user_detail | Update a user |
188+ | ` /users/{obj_id}/ ` | DELETE | user_detail | Delete a user |
189+ | ` /operations/ ` | POST | atomic | Create, update, delete users |
0 commit comments