Skip to content

Commit 9e78a3b

Browse files
authored
Merge pull request #63 from mts-ai/limit-view-methods
limit view methods
2 parents ff110fc + ef55753 commit 9e78a3b

File tree

10 files changed

+267
-50
lines changed

10 files changed

+267
-50
lines changed

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def add_routes(app: FastAPI):
148148
router: APIRouter = APIRouter()
149149
RoutersJSONAPI(
150150
router=router,
151-
path="/user",
151+
path="/users",
152152
tags=["User"],
153153
class_detail=UserDetailView,
154154
class_list=UserListView,
@@ -195,11 +195,11 @@ if __name__ == "__main__":
195195

196196
This example provides the following API structure:
197197

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 | user_detail | Update a user |
205-
| `/user/{obj_id}` | DELETE | user_detail | Delete a user |
198+
| URL | method | endpoint | Usage |
199+
|-------------------|--------|-------------|---------------------------|
200+
| `/users` | GET | user_list | Get a collection of users |
201+
| `/users` | POST | user_list | Create a user |
202+
| `/users` | DELETE | user_list | Delete users |
203+
| `/users/{obj_id}` | GET | user_detail | Get user details |
204+
| `/users/{obj_id}` | PATCH | user_detail | Update a user |
205+
| `/users/{obj_id}` | DELETE | user_detail | Delete a user |
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
.. _api_limited_methods_example:
2+
3+
Limit API methods
4+
#################
5+
6+
Sometimes you won't need all the CRUD methods.
7+
For example, you want to create only GET, POST and GET LIST methods,
8+
so user can't update or delete any items.
9+
10+
11+
Set ``methods`` on Routers registration:
12+
13+
.. code-block:: python
14+
15+
RoutersJSONAPI(
16+
router=router,
17+
path="/users",
18+
tags=["User"],
19+
class_detail=UserDetailView,
20+
class_list=UserListView,
21+
schema=UserSchema,
22+
model=User,
23+
resource_type="user",
24+
methods=[
25+
RoutersJSONAPI.Methods.GET_LIST,
26+
RoutersJSONAPI.Methods.POST,
27+
RoutersJSONAPI.Methods.GET,
28+
],
29+
)
30+
31+
32+
This will limit generated views to:
33+
34+
======================== ====== ============= ===========================
35+
URL method endpoint Usage
36+
======================== ====== ============= ===========================
37+
/users GET user_list Get a collection of users
38+
/users POST user_list Create a user
39+
/users/{user_id} GET user_detail Get user details
40+
======================== ====== ============= ===========================
41+
42+
43+
Full code example (should run "as is"):
44+
45+
.. literalinclude:: ../examples/api_limited_methods.py
46+
:language: python

docs/filtering.rst

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ So this is a first example:
2424

2525
.. sourcecode:: http
2626

27-
GET /user?filter=[{"name":"first_name","op":"eq","val":"John"}] HTTP/1.1
27+
GET /users?filter=[{"name":"first_name","op":"eq","val":"John"}] HTTP/1.1
2828
Accept: application/vnd.api+json
2929

3030
In this example we want to retrieve user records for people named John. So we can see that the filtering interface completely fits that of SQLAlchemy: a list a filter information.
@@ -37,7 +37,7 @@ Example with field:
3737

3838
.. sourcecode:: http
3939

40-
GET /user?filter=[{"name":"first_name","op":"eq","field":"birth_date"}] HTTP/1.1
40+
GET /users?filter=[{"name":"first_name","op":"eq","field":"birth_date"}] HTTP/1.1
4141
Accept: application/vnd.api+json
4242

4343
In this example, we want to retrieve people whose name is equal to their birth_date. This example is absurd, it's just here to explain the syntax of this kind of filter.
@@ -74,7 +74,7 @@ There is a shortcut to achieve the same filtering:
7474

7575
.. sourcecode:: http
7676

77-
GET /user?filter=[{"name":"group.name","op":"ilike","val":"%admin%"}] HTTP/1.1
77+
GET /users?filter=[{"name":"group.name","op":"ilike","val":"%admin%"}] HTTP/1.1
7878
Accept: application/vnd.api+json
7979

8080
You can also use boolean combination of operations:
@@ -116,22 +116,22 @@ You can also use boolean combination of operations:
116116

117117
.. sourcecode:: http
118118

119-
GET /user?filter=[{"name":"group.name","op":"ilike","val":"%admin%"},{"or":[{"not":{"name":"first_name","op":"eq","val":"John"}},{"and":[{"name":"first_name","op":"like","val":"%Jim%"},{"name":"date_create","op":"gt","val":"1990-01-01"}]}]}] HTTP/1.1
119+
GET /users?filter=[{"name":"group.name","op":"ilike","val":"%admin%"},{"or":[{"not":{"name":"first_name","op":"eq","val":"John"}},{"and":[{"name":"first_name","op":"like","val":"%Jim%"},{"name":"date_create","op":"gt","val":"1990-01-01"}]}]}] HTTP/1.1
120120
Accept: application/vnd.api+json
121121

122122

123123
Filtering records by a field that is null
124124

125125
.. sourcecode:: http
126126

127-
GET /user?filter=[{"name":"name","op":"is_","val":null}] HTTP/1.1
127+
GET /users?filter=[{"name":"name","op":"is_","val":null}] HTTP/1.1
128128
Accept: application/vnd.api+json
129129

130130
Filtering records by a field that is not null
131131

132132
.. sourcecode:: http
133133

134-
GET /user?filter=[{"name":"name","op":"isnot","val":null}] HTTP/1.1
134+
GET /users?filter=[{"name":"name","op":"isnot","val":null}] HTTP/1.1
135135
Accept: application/vnd.api+json
136136

137137

@@ -172,22 +172,22 @@ For example
172172

173173
.. sourcecode:: http
174174

175-
GET /user?filter[first_name]=John HTTP/1.1
175+
GET /users?filter[first_name]=John HTTP/1.1
176176
Accept: application/vnd.api+json
177177

178178
equals:
179179

180180
.. sourcecode:: http
181181

182-
GET /user?filter=[{"name":"first_name","op":"eq","val":"John"}] HTTP/1.1
182+
GET /users?filter=[{"name":"first_name","op":"eq","val":"John"}] HTTP/1.1
183183
Accept: application/vnd.api+json
184184

185185

186186
You can also use more than one simple filter in a request:
187187

188188
.. sourcecode:: http
189189

190-
GET /user?filter[first_name]=John&filter[gender]=male HTTP/1.1
190+
GET /users?filter[first_name]=John&filter[gender]=male HTTP/1.1
191191
Accept: application/vnd.api+json
192192

193193
which is equal to:
@@ -209,17 +209,17 @@ which is equal to:
209209

210210
.. sourcecode:: http
211211

212-
GET /user?filter=[{"name":"first_name","op":"eq","val":"John"},{"name":"gender","op":"eq","val":"male"}] HTTP/1.1
212+
GET /users?filter=[{"name":"first_name","op":"eq","val":"John"},{"name":"gender","op":"eq","val":"male"}] HTTP/1.1
213213

214214
You can also use relationship attribute in a request:
215215

216216
.. sourcecode:: http
217217

218-
GET /user?filter[group_id]=1 HTTP/1.1
218+
GET /users?filter[group_id]=1 HTTP/1.1
219219
Accept: application/vnd.api+json
220220

221221
which is equal to:
222222

223223
.. sourcecode:: http
224224

225-
GET /user?filter=[{"name":"group.id","op":"eq","val":"1"}] HTTP/1.1
225+
GET /users?filter=[{"name":"group.id","op":"eq","val":"1"}] HTTP/1.1

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ FastAPI-JSONAPI with FastAPI.
6161
minimal_api_example
6262
api_filtering_example
6363
quickstart
64+
api_limited_methods_example
6465
routing
6566
atomic_operations
6667
view_dependencies

docs/python_snippets/client_generated_id/schematic_example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def add_routes(app: FastAPI):
110110
router: APIRouter = APIRouter()
111111
RoutersJSONAPI(
112112
router=router,
113-
path="/user",
113+
path="/users",
114114
tags=["User"],
115115
class_detail=UserDetailView,
116116
class_list=UserListView,

docs/quickstart.rst

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,27 +27,27 @@ This example provides the following API:
2727
+----------------+--------+----------------+---------------------------------+
2828
| url | method | endpoint | action |
2929
+================+========+================+=================================+
30-
| /user | GET | user_list | Retrieve a collection of users |
30+
| /users | GET | user_list | Retrieve a collection of users |
3131
+----------------+--------+----------------+---------------------------------+
32-
| /user | POST | user_list | Create a user |
32+
| /users | POST | user_list | Create a user |
3333
+----------------+--------+----------------+---------------------------------+
34-
| /user/<int:id> | GET | user_detail | Retrieve details of a user |
34+
| /users/<int:id> | GET | user_detail | Retrieve details of a user |
3535
+----------------+--------+----------------+---------------------------------+
36-
| /user/<int:id> | PATCH | user_detail | Update a user |
36+
| /users/<int:id> | PATCH | user_detail | Update a user |
3737
+----------------+--------+----------------+---------------------------------+
38-
| /user/<int:id> | DELETE | user_detail | Delete a user |
38+
| /users/<int:id> | DELETE | user_detail | Delete a user |
3939
+----------------+--------+----------------+---------------------------------+
4040

4141
in developing
4242

4343
+-------------------------------------------+--------+------------------+------------------------------------------------------+
4444
| url | method | endpoint | action |
4545
+===========================================+========+==================+======================================================+
46-
| /user/<int:id>/group | GET | computer_list | Retrieve a collection computers related to a user |
46+
| /users/<int:id>/group | GET | computer_list | Retrieve a collection computers related to a user |
4747
+-------------------------------------------+--------+------------------+------------------------------------------------------+
48-
| /user/<int:id>/group | POST | computer_list | Create a computer related to a user |
48+
| /users/<int:id>/group | POST | computer_list | Create a computer related to a user |
4949
+-------------------------------------------+--------+------------------+------------------------------------------------------+
50-
| /user/<int:id>/relationships/group | GET | user_computers | Retrieve relationships between a user and computers |
50+
| /users/<int:id>/relationships/group | GET | user_computers | Retrieve relationships between a user and computers |
5151
+-------------------------------------------+--------+------------------+------------------------------------------------------+
5252
| /users/<int:id>/relationships/computers | POST | user_computers | Create relationships between a user and computers |
5353
+-------------------------------------------+--------+------------------+------------------------------------------------------+

examples/api_for_tortoise_orm/urls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def add_routes(app: FastAPI) -> List[Dict[str, Any]]:
3636
# TODO: fix example
3737
RoutersJSONAPI(
3838
router=routers,
39-
path="/user",
39+
path="/users",
4040
tags=["User"],
4141
class_detail=UserDetail,
4242
class_list=UserList,

examples/api_limited_methods.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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)
31+
name = Column(Text, nullable=True)
32+
33+
34+
class UserAttributesBaseSchema(BaseModel):
35+
name: str
36+
37+
class Config:
38+
orm_mode = True
39+
40+
41+
class UserSchema(UserAttributesBaseSchema):
42+
"""User base schema."""
43+
44+
45+
def async_session() -> sessionmaker:
46+
engine = create_async_engine(url=make_url(DB_URL))
47+
_async_session = sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)
48+
return _async_session
49+
50+
51+
class Connector:
52+
@classmethod
53+
async def get_session(cls):
54+
"""
55+
Get session as dependency
56+
57+
:return:
58+
"""
59+
sess = async_session()
60+
async with sess() as db_session: # type: AsyncSession
61+
yield db_session
62+
await db_session.rollback()
63+
64+
65+
async def sqlalchemy_init() -> None:
66+
engine = create_async_engine(url=make_url(DB_URL))
67+
async with engine.begin() as conn:
68+
await conn.run_sync(Base.metadata.create_all)
69+
70+
71+
class SessionDependency(BaseModel):
72+
session: AsyncSession = Depends(Connector.get_session)
73+
74+
class Config:
75+
arbitrary_types_allowed = True
76+
77+
78+
def session_dependency_handler(view: ViewBase, dto: SessionDependency) -> Dict[str, Any]:
79+
return {
80+
"session": dto.session,
81+
}
82+
83+
84+
class UserDetailView(DetailViewBaseGeneric):
85+
method_dependencies = {
86+
HTTPMethod.ALL: HTTPMethodConfig(
87+
dependencies=SessionDependency,
88+
prepare_data_layer_kwargs=session_dependency_handler,
89+
),
90+
}
91+
92+
93+
class UserListView(ListViewBaseGeneric):
94+
method_dependencies = {
95+
HTTPMethod.ALL: HTTPMethodConfig(
96+
dependencies=SessionDependency,
97+
prepare_data_layer_kwargs=session_dependency_handler,
98+
),
99+
}
100+
101+
102+
def add_routes(app: FastAPI):
103+
tags = [
104+
{
105+
"name": "User",
106+
"description": "",
107+
},
108+
]
109+
110+
router: APIRouter = APIRouter()
111+
RoutersJSONAPI(
112+
router=router,
113+
path="/users",
114+
tags=["User"],
115+
class_detail=UserDetailView,
116+
class_list=UserListView,
117+
schema=UserSchema,
118+
model=User,
119+
resource_type="user",
120+
methods=[
121+
RoutersJSONAPI.Methods.GET_LIST,
122+
RoutersJSONAPI.Methods.POST,
123+
RoutersJSONAPI.Methods.GET,
124+
],
125+
)
126+
127+
app.include_router(router, prefix="")
128+
return tags
129+
130+
131+
def create_app() -> FastAPI:
132+
"""
133+
Create app factory.
134+
135+
:return: app
136+
"""
137+
app = FastAPI(
138+
title="FastAPI app with limited methods",
139+
debug=True,
140+
openapi_url="/openapi.json",
141+
docs_url="/docs",
142+
)
143+
add_routes(app)
144+
app.on_event("startup")(sqlalchemy_init)
145+
init(app)
146+
return app
147+
148+
149+
app = create_app()
150+
151+
if __name__ == "__main__":
152+
uvicorn.run(
153+
app,
154+
host="0.0.0.0",
155+
port=8080,
156+
)

0 commit comments

Comments
 (0)