Skip to content

Commit cc7a6e7

Browse files
committed
update docs and examples
1 parent b49898a commit cc7a6e7

4 files changed

Lines changed: 192 additions & 21 deletions

File tree

README.md

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This framework is designed to quickly build REST APIs and fit the complexity
1515
of real life projects with legacy data and multiple data storages.
1616

1717
## Architecture
18+
1819
![docs/img/schema.png](docs/img/schema.png)
1920

2021
## Install
@@ -28,7 +29,6 @@ pip install FastAPI-JSONAPI
2829
Create a test.py file and copy the following code into it
2930

3031
```python
31-
import sys
3232
from pathlib import Path
3333
from typing import Any, Dict
3434

@@ -40,17 +40,15 @@ from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
4040
from sqlalchemy.ext.declarative import declarative_base
4141
from sqlalchemy.orm import sessionmaker
4242

43-
from fastapi_jsonapi import RoutersJSONAPI
43+
from fastapi_jsonapi import RoutersJSONAPI, init
4444
from fastapi_jsonapi.misc.sqla.generics.base import DetailViewBaseGeneric, ListViewBaseGeneric
4545
from fastapi_jsonapi.schema_base import BaseModel
4646
from fastapi_jsonapi.views.utils import HTTPMethod, HTTPMethodConfig
4747
from fastapi_jsonapi.views.view_base import ViewBase
4848

4949
CURRENT_FILE = Path(__file__).resolve()
5050
CURRENT_DIR = CURRENT_FILE.parent
51-
PROJECT_DIR = CURRENT_DIR.parent.parent
52-
DB_URL = f"sqlite+aiosqlite:///{CURRENT_DIR.absolute()}/db.sqlite3"
53-
sys.path.append(str(PROJECT_DIR))
51+
DB_URL = f"sqlite+aiosqlite:///{CURRENT_DIR}/db.sqlite3"
5452

5553
Base = declarative_base()
5654

@@ -115,7 +113,7 @@ class SessionDependency(BaseModel):
115113
arbitrary_types_allowed = True
116114

117115

118-
def session_dependency_handler(view: ViewBase, dto: BaseModel) -> Dict[str, Any]:
116+
def session_dependency_handler(view: ViewBase, dto: SessionDependency) -> Dict[str, Any]:
119117
return {
120118
"session": dto.session,
121119
}
@@ -179,28 +177,29 @@ def create_app() -> FastAPI:
179177
)
180178
add_routes(app)
181179
app.on_event("startup")(sqlalchemy_init)
180+
init(app)
182181
return app
183182

184183

185184
app = create_app()
186185

187186
if __name__ == "__main__":
188187
uvicorn.run(
189-
"test:app",
188+
"main:app",
190189
host="0.0.0.0",
191-
port=8084,
190+
port=8080,
192191
reload=True,
193192
app_dir=str(CURRENT_DIR),
194193
)
195-
196194
```
197195

198196
This example provides the following API structure:
199197

200-
| URL | method | endpoint | Usage |
201-
|-------------------|--------|---------------|---------------------------|
202-
| /user | GET | user_list | Get a collection of users |
203-
| /user | POST | user_list | Create a user |
204-
| /user/< int:int > | GET | user_detail | Get user details |
205-
| /user/< int:int > | PATCH | person_detail | Update a user |
206-
| /user/< int:int > | DELETE | person_detail | Delete a user |
198+
| URL | method | endpoint | Usage |
199+
|------------------|--------|---------------|---------------------------|
200+
| `/user` | GET | user_list | Get a collection of users |
201+
| `/user` | POST | user_list | Create a user |
202+
| `/user` | DELETE | user_list | Delete users |
203+
| `/user/{obj_id}` | GET | user_detail | Get user details |
204+
| `/user/{obj_id}` | PATCH | person_detail | Update a user |
205+
| `/user/{obj_id}` | DELETE | person_detail | Delete a user |

docs/minimal_api_head.rst

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ This example provides the following API structure:
1010
======================== ====== ============= ===========================
1111
URL method endpoint Usage
1212
======================== ====== ============= ===========================
13-
/persons GET person_list Get a collection of persons
14-
/persons POST person_list Create a person
15-
/persons/<int:person_id> GET person_detail Get person details
16-
/persons/<int:person_id> PATCH person_detail Update a person
17-
/persons/<int:person_id> DELETE person_detail Delete a person
13+
/users GET user_list Get a collection of users
14+
/users POST user_list Create a user
15+
/users DELETE user_list Delete users
16+
/users/{user_id} GET user_detail Get user details
17+
/users/{user_id} PATCH user_detail Update a user
18+
/users/{user_id} DELETE user_detail Delete a user
1819
======================== ====== ============= ===========================

examples/api_minimal.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import sys
2+
from pathlib import Path
3+
from typing import Any, Dict
4+
5+
import uvicorn
6+
from fastapi import APIRouter, Depends, FastAPI
7+
from sqlalchemy import Column, Integer, Text
8+
from sqlalchemy.engine import make_url
9+
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
10+
from sqlalchemy.ext.declarative import declarative_base
11+
from sqlalchemy.orm import sessionmaker
12+
13+
from fastapi_jsonapi import RoutersJSONAPI, init
14+
from fastapi_jsonapi.misc.sqla.generics.base import DetailViewBaseGeneric, ListViewBaseGeneric
15+
from fastapi_jsonapi.schema_base import BaseModel
16+
from fastapi_jsonapi.views.utils import HTTPMethod, HTTPMethodConfig
17+
from fastapi_jsonapi.views.view_base import ViewBase
18+
19+
CURRENT_FILE = Path(__file__).resolve()
20+
CURRENT_DIR = CURRENT_FILE.parent
21+
PROJECT_DIR = CURRENT_DIR.parent.parent
22+
DB_URL = f"sqlite+aiosqlite:///{CURRENT_DIR}/db.sqlite3"
23+
sys.path.append(str(PROJECT_DIR))
24+
25+
Base = declarative_base()
26+
27+
28+
class User(Base):
29+
__tablename__ = "users"
30+
id = Column(Integer, primary_key=True, autoincrement=True)
31+
name: str = Column(Text, nullable=True)
32+
33+
34+
class UserAttributesBaseSchema(BaseModel):
35+
name: str
36+
37+
class Config:
38+
"""Pydantic schema config."""
39+
40+
orm_mode = True
41+
42+
43+
class UserSchema(UserAttributesBaseSchema):
44+
"""User base schema."""
45+
46+
47+
class UserPatchSchema(UserAttributesBaseSchema):
48+
"""User PATCH schema."""
49+
50+
51+
class UserInSchema(UserAttributesBaseSchema):
52+
"""User input schema."""
53+
54+
55+
def async_session() -> sessionmaker:
56+
engine = create_async_engine(url=make_url(DB_URL))
57+
_async_session = sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)
58+
return _async_session
59+
60+
61+
class Connector:
62+
@classmethod
63+
async def get_session(cls):
64+
"""
65+
Get session as dependency
66+
67+
:return:
68+
"""
69+
sess = async_session()
70+
async with sess() as db_session: # type: AsyncSession
71+
yield db_session
72+
await db_session.rollback()
73+
74+
75+
async def sqlalchemy_init() -> None:
76+
engine = create_async_engine(url=make_url(DB_URL))
77+
async with engine.begin() as conn:
78+
await conn.run_sync(Base.metadata.create_all)
79+
80+
81+
class SessionDependency(BaseModel):
82+
session: AsyncSession = Depends(Connector.get_session)
83+
84+
class Config:
85+
arbitrary_types_allowed = True
86+
87+
88+
def session_dependency_handler(view: ViewBase, dto: SessionDependency) -> Dict[str, Any]:
89+
return {
90+
"session": dto.session,
91+
}
92+
93+
94+
class UserDetailView(DetailViewBaseGeneric):
95+
method_dependencies = {
96+
HTTPMethod.ALL: HTTPMethodConfig(
97+
dependencies=SessionDependency,
98+
prepare_data_layer_kwargs=session_dependency_handler,
99+
),
100+
}
101+
102+
103+
class UserListView(ListViewBaseGeneric):
104+
method_dependencies = {
105+
HTTPMethod.ALL: HTTPMethodConfig(
106+
dependencies=SessionDependency,
107+
prepare_data_layer_kwargs=session_dependency_handler,
108+
),
109+
}
110+
111+
112+
def add_routes(app: FastAPI):
113+
tags = [
114+
{
115+
"name": "User",
116+
"description": "",
117+
},
118+
]
119+
120+
routers: APIRouter = APIRouter()
121+
RoutersJSONAPI(
122+
router=routers,
123+
path="/user",
124+
tags=["User"],
125+
class_detail=UserDetailView,
126+
class_list=UserListView,
127+
schema=UserSchema,
128+
resource_type="user",
129+
schema_in_patch=UserPatchSchema,
130+
schema_in_post=UserInSchema,
131+
model=User,
132+
)
133+
134+
app.include_router(routers, prefix="")
135+
return tags
136+
137+
138+
def create_app() -> FastAPI:
139+
"""
140+
Create app factory.
141+
142+
:return: app
143+
"""
144+
app = FastAPI(
145+
title="FastAPI and SQLAlchemy",
146+
debug=True,
147+
openapi_url="/openapi.json",
148+
docs_url="/docs",
149+
)
150+
add_routes(app)
151+
app.on_event("startup")(sqlalchemy_init)
152+
init(app)
153+
return app
154+
155+
156+
app = create_app()
157+
158+
if __name__ == "__main__":
159+
uvicorn.run(
160+
"main:app",
161+
host="0.0.0.0",
162+
port=8080,
163+
reload=True,
164+
app_dir=str(CURRENT_DIR),
165+
)

fastapi_jsonapi/views/detail_view.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
from typing import TypeVar, Union
33

4+
from fastapi_jsonapi import BadRequest
45
from fastapi_jsonapi.schema import (
56
BaseJSONAPIItemInSchema,
67
JSONAPIResultDetailSchema,
@@ -34,6 +35,11 @@ async def update_resource_result(
3435
data_update: BaseJSONAPIItemInSchema,
3536
**extra_view_deps,
3637
) -> JSONAPIResultDetailSchema:
38+
if obj_id != data_update.id:
39+
raise BadRequest(
40+
detail="obj_id and data.id should be same",
41+
pointer="/data/id",
42+
)
3743
dl_kwargs = await handle_endpoint_dependencies(self, extra_view_deps)
3844
dl = self._get_data_layer_for_detail(**dl_kwargs)
3945

0 commit comments

Comments
 (0)