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,25 +31,61 @@ 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
3336from contextlib import asynccontextmanager
3437from pathlib import Path
3538from typing import Any, ClassVar, Optional
39+ from typing import Union
3640
3741import uvicorn
38- from fastapi import APIRouter, Depends, FastAPI
42+ from fastapi import Depends, FastAPI
43+ from fastapi.responses import ORJSONResponse as JSONResponse
3944from pydantic import ConfigDict
45+ from sqlalchemy.engine import URL
4046from sqlalchemy.engine import make_url
4147from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine
4248from 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
53+ from fastapi_jsonapi.views import ViewBase, Operation, OperationConfig
4954
5055CURRENT_DIR = Path(__file__ ).resolve().parent
51- DB_URL = f " sqlite+aiosqlite:/// { CURRENT_DIR } /db.sqlite3 "
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+ )
71+
72+ self .session_maker: async_sessionmaker[AsyncSession] = async_sessionmaker(
73+ autocommit = False ,
74+ bind = self .engine,
75+ expire_on_commit = False ,
76+ )
77+
78+ async def dispose (self ):
79+ await self .engine.dispose()
80+
81+ async def session (self ) -> AsyncIterator[AsyncSession]:
82+ async with self .session_maker() as session:
83+ yield session
84+
85+
86+ db = DB(
87+ url = make_url(f " sqlite+aiosqlite:/// { CURRENT_DIR } /db.sqlite3 " ),
88+ )
5289
5390
5491class Base (DeclarativeBase ):
@@ -62,64 +99,22 @@ class User(Base):
6299 name: Mapped[Optional[str ]]
63100
64101
65- class UserAttributesBaseSchema (BaseModel ):
102+ class UserSchema (BaseModel ):
103+ """ User base schema."""
104+
66105 model_config = ConfigDict(
67106 from_attributes = True ,
68107 )
69108
70109 name: str
71110
72111
73- class UserSchema (UserAttributesBaseSchema ):
74- """ User base schema."""
75-
76-
77- class UserPatchSchema (UserAttributesBaseSchema ):
78- """ User PATCH schema."""
79-
80-
81- class UserInSchema (UserAttributesBaseSchema ):
82- """ User input schema."""
83-
84-
85- def async_session () -> tuple[AsyncEngine, async_sessionmaker]:
86- engine_: AsyncEngine = create_async_engine(
87- url = f " { make_url(DB_URL )} " ,
88- echo = True ,
89- )
90- session_maker_: async_sessionmaker[AsyncSession] = async_sessionmaker(
91- autocommit = False ,
92- bind = engine,
93- expire_on_commit = False ,
94- )
95- return engine_, session_maker_
96-
97-
98- engine, session_maker = async_session()
99-
100-
101- class Connector :
102- @ classmethod
103- async def dispose (cls ):
104- await engine.dispose()
105-
106- @ classmethod
107- async def init (cls ) -> None :
108- async with engine.begin() as conn:
109- await conn.run_sync(Base.metadata.create_all)
110-
111- @ classmethod
112- async def session (cls ):
113- async with session_maker() as db_session:
114- yield db_session
115-
116-
117112class SessionDependency (BaseModel ):
118113 model_config = ConfigDict(
119114 arbitrary_types_allowed = True ,
120115 )
121116
122- session: AsyncSession = Depends(Connector .session)
117+ session: AsyncSession = Depends(db .session)
123118
124119
125120def session_dependency_handler (view : ViewBase, dto : SessionDependency) -> dict[str , Any]:
@@ -128,89 +123,67 @@ def session_dependency_handler(view: ViewBase, dto: SessionDependency) -> dict[s
128123 }
129124
130125
131- class UserDetailView ( DetailViewBaseGeneric ):
132- method_dependencies : ClassVar[dict[HTTPMethod, HTTPMethodConfig]] = {
133- HTTPMethod .ALL : HTTPMethodConfig (
126+ class UserView ( ViewBaseGeneric ):
127+ operation_dependencies : ClassVar = {
128+ Operation .ALL : OperationConfig (
134129 dependencies = SessionDependency,
135130 prepare_data_layer_kwargs = session_dependency_handler,
136- )
137- }
138-
139-
140- class UserListView (ListViewBaseGeneric ):
141- method_dependencies: ClassVar[dict[HTTPMethod, HTTPMethodConfig]] = {
142- HTTPMethod.ALL : HTTPMethodConfig(
143- dependencies = SessionDependency,
144- prepare_data_layer_kwargs = session_dependency_handler,
145- )
131+ ),
146132 }
147133
148134
149135def add_routes (app : FastAPI):
150- tags = [
151- {
152- " name" : " User" ,
153- " description" : " " ,
154- },
155- ]
156-
157- router: APIRouter = APIRouter()
158- RoutersJSONAPI(
159- router = router,
136+ builder = ApplicationBuilder(app)
137+ builder.add_resource(
160138 path = " /users" ,
161139 tags = [" User" ],
162- class_detail = UserDetailView,
163- class_list = UserListView,
140+ view = UserView,
164141 schema = UserSchema,
165- resource_type = " user" ,
166- schema_in_patch = UserPatchSchema,
167- schema_in_post = UserInSchema,
168142 model = User,
143+ resource_type = " user" ,
169144 )
170-
171- app.include_router(router, prefix = " " )
172- return tags
145+ builder.initialize()
173146
174147
175148# noinspection PyUnusedLocal
176149@asynccontextmanager
177150async def lifespan (app : FastAPI):
178151 add_routes(app)
179- init(app)
180152
181- await Connector.init()
153+ async with db.engine.begin() as conn:
154+ await conn.run_sync(Base.metadata.create_all)
182155
183156 yield
184157
185- await Connector .dispose()
158+ await db .dispose()
186159
187160
188161app = FastAPI(
189- lifespan = lifespan,
190162 title = " FastAPI and SQLAlchemy" ,
163+ lifespan = lifespan,
191164 debug = True ,
192- openapi_url = " /openapi.json " ,
165+ default_response_class = JSONResponse ,
193166 docs_url = " /docs" ,
167+ openapi_url = " /openapi.json" ,
194168)
195169
196170
197171if __name__ == " __main__" :
198172 uvicorn.run(
199- " main: app" ,
173+ app,
200174 host = " 0.0.0.0" ,
201175 port = 8080 ,
202- reload = True ,
203- app_dir = f " { CURRENT_DIR } " ,
204176 )
205177```
206178
207179This example provides the following API structure:
208180
209- | URL | method | endpoint | Usage |
210- | -------------------| --------| -------------| ---------------------------|
211- | ` /users ` | GET | user_list | Get a collection of users |
212- | ` /users ` | POST | user_list | Create a user |
213- | ` /users ` | DELETE | user_list | Delete users |
214- | ` /users/{obj_id} ` | GET | user_detail | Get user details |
215- | ` /users/{obj_id} ` | PATCH | user_detail | Update a user |
216- | ` /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