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,25 +31,61 @@ 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
33
36
from contextlib import asynccontextmanager
34
37
from pathlib import Path
35
38
from typing import Any, ClassVar, Optional
39
+ from typing import Union
36
40
37
41
import uvicorn
38
- from fastapi import APIRouter, Depends, FastAPI
42
+ from fastapi import Depends, FastAPI
43
+ from fastapi.responses import ORJSONResponse as JSONResponse
39
44
from pydantic import ConfigDict
45
+ from sqlalchemy.engine import URL
40
46
from sqlalchemy.engine import make_url
41
47
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine
42
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
53
+ from fastapi_jsonapi.views import ViewBase, Operation, OperationConfig
49
54
50
55
CURRENT_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
+ )
52
89
53
90
54
91
class Base (DeclarativeBase ):
@@ -62,64 +99,22 @@ class User(Base):
62
99
name: Mapped[Optional[str ]]
63
100
64
101
65
- class UserAttributesBaseSchema (BaseModel ):
102
+ class UserSchema (BaseModel ):
103
+ """ User base schema."""
104
+
66
105
model_config = ConfigDict(
67
106
from_attributes = True ,
68
107
)
69
108
70
109
name: str
71
110
72
111
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
-
117
112
class SessionDependency (BaseModel ):
118
113
model_config = ConfigDict(
119
114
arbitrary_types_allowed = True ,
120
115
)
121
116
122
- session: AsyncSession = Depends(Connector .session)
117
+ session: AsyncSession = Depends(db .session)
123
118
124
119
125
120
def session_dependency_handler (view : ViewBase, dto : SessionDependency) -> dict[str , Any]:
@@ -128,89 +123,67 @@ def session_dependency_handler(view: ViewBase, dto: SessionDependency) -> dict[s
128
123
}
129
124
130
125
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 (
134
129
dependencies = SessionDependency,
135
130
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
+ ),
146
132
}
147
133
148
134
149
135
def 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(
160
138
path = " /users" ,
161
139
tags = [" User" ],
162
- class_detail = UserDetailView,
163
- class_list = UserListView,
140
+ view = UserView,
164
141
schema = UserSchema,
165
- resource_type = " user" ,
166
- schema_in_patch = UserPatchSchema,
167
- schema_in_post = UserInSchema,
168
142
model = User,
143
+ resource_type = " user" ,
169
144
)
170
-
171
- app.include_router(router, prefix = " " )
172
- return tags
145
+ builder.initialize()
173
146
174
147
175
148
# noinspection PyUnusedLocal
176
149
@asynccontextmanager
177
150
async def lifespan (app : FastAPI):
178
151
add_routes(app)
179
- init(app)
180
152
181
- await Connector.init()
153
+ async with db.engine.begin() as conn:
154
+ await conn.run_sync(Base.metadata.create_all)
182
155
183
156
yield
184
157
185
- await Connector .dispose()
158
+ await db .dispose()
186
159
187
160
188
161
app = FastAPI(
189
- lifespan = lifespan,
190
162
title = " FastAPI and SQLAlchemy" ,
163
+ lifespan = lifespan,
191
164
debug = True ,
192
- openapi_url = " /openapi.json " ,
165
+ default_response_class = JSONResponse ,
193
166
docs_url = " /docs" ,
167
+ openapi_url = " /openapi.json" ,
194
168
)
195
169
196
170
197
171
if __name__ == " __main__" :
198
172
uvicorn.run(
199
- " main: app" ,
173
+ app,
200
174
host = " 0.0.0.0" ,
201
175
port = 8080 ,
202
- reload = True ,
203
- app_dir = f " { CURRENT_DIR } " ,
204
176
)
205
177
```
206
178
207
179
This example provides the following API structure:
208
180
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