8
8
9
9
[ ![ 📖 Docs (gh-pages)] ( https://github.com/mts-ai/FastAPI-JSONAPI/actions/workflows/documentation.yaml/badge.svg )] ( https://mts-ai.github.io/FastAPI-JSONAPI/ )
10
10
11
+
11
12
# FastAPI-JSONAPI
12
13
13
14
FastAPI-JSONAPI is a FastAPI extension for building REST APIs.
@@ -30,177 +31,159 @@ pip install FastAPI-JSONAPI
30
31
Create a test.py file and copy the following code into it
31
32
32
33
``` python
34
+ import sys
35
+ from collections.abc import AsyncIterator
36
+ from contextlib import asynccontextmanager
33
37
from pathlib import Path
34
- from typing import Any, ClassVar, Dict
38
+ from typing import Any, ClassVar, Optional
39
+ from typing import Union
35
40
36
41
import 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
39
46
from 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
43
49
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
46
52
from 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
+ )
68
71
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
+ )
70
77
78
+ async def dispose (self ):
79
+ await self .engine.dispose()
71
80
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
74
84
75
85
76
- class UserPatchSchema (UserAttributesBaseSchema ):
77
- """ User PATCH schema."""
86
+ db = DB(
87
+ url = make_url(f " sqlite+aiosqlite:/// { CURRENT_DIR } /db.sqlite3 " ),
88
+ )
78
89
79
90
80
- class UserInSchema ( UserAttributesBaseSchema ):
81
- """ User input schema. """
91
+ class Base ( DeclarativeBase ):
92
+ pass
82
93
83
94
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"
88
97
98
+ id : Mapped[int ] = mapped_column(primary_key = True )
99
+ name: Mapped[Optional[str ]]
89
100
90
- class Connector :
91
- @ classmethod
92
- async def get_session (cls ):
93
- """
94
- Get session as dependency
95
101
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."""
102
104
105
+ model_config = ConfigDict(
106
+ from_attributes = True ,
107
+ )
103
108
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
108
110
109
111
110
112
class SessionDependency (BaseModel ):
111
- session: AsyncSession = Depends(Connector.get_session)
113
+ model_config = ConfigDict(
114
+ arbitrary_types_allowed = True ,
115
+ )
112
116
113
- class Config :
114
- arbitrary_types_allowed = True
117
+ session: AsyncSession = Depends(db.session)
115
118
116
119
117
- def session_dependency_handler (view : ViewBase, dto : SessionDependency) -> Dict [str , Any]:
120
+ def session_dependency_handler (view : ViewBase, dto : SessionDependency) -> dict [str , Any]:
118
121
return {
119
122
" session" : dto.session,
120
123
}
121
124
122
125
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(
135
129
dependencies = SessionDependency,
136
130
prepare_data_layer_kwargs = session_dependency_handler,
137
- )
131
+ ),
138
132
}
139
133
140
134
141
135
def 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(
152
138
path = " /users" ,
153
139
tags = [" User" ],
154
- class_detail = UserDetailView,
155
- class_list = UserListView,
140
+ view = UserView,
156
141
schema = UserSchema,
157
- resource_type = " user" ,
158
- schema_in_patch = UserPatchSchema,
159
- schema_in_post = UserInSchema,
160
142
model = User,
143
+ resource_type = " user" ,
161
144
)
145
+ builder.initialize()
162
146
163
- app.include_router(router, prefix = " " )
164
- return tags
165
147
148
+ # noinspection PyUnusedLocal
149
+ @asynccontextmanager
150
+ async def lifespan (app : FastAPI):
151
+ add_routes(app)
166
152
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()
170
159
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
183
160
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
+ )
184
169
185
- app = create_app()
186
170
187
171
if __name__ == " __main__" :
188
172
uvicorn.run(
189
- " main: app" ,
173
+ app,
190
174
host = " 0.0.0.0" ,
191
175
port = 8080 ,
192
- reload = True ,
193
- app_dir = str (CURRENT_DIR ),
194
176
)
195
177
```
196
178
197
179
This example provides the following API structure:
198
180
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