Routing
In this extension, the API is built using the Resource classes.
They are built based on the capabilities of FastAPI, namely APIRouter and Route
The Resource classes provide easy access to HTTP methods.
Example of CRUD operations:
Consider an example with posts.
Note
posts = [] is used as a simulation of the database posts table.
| posts.py |
|---|
| from uuid import UUID, uuid4
from fastapi import FastAPI, Response, HTTPException, Query
from fastapi_restful import RESTExtension, Resource
from pydantic import BaseModel, Field
from uvicorn import run
app = FastAPI()
api = RESTExtension(app)
posts = []
class NewPost(BaseModel):
id: UUID = Field(default_factory=uuid4)
body: str
class UpdatePost(BaseModel):
body: str
class Posts(Resource):
def get(self, query_param: str = Query(None, description='My query')):
return {'items': posts, 'total_count': len(posts)}
def post(self, new_post: NewPost):
posts.append(new_post)
return {'post_id': new_post.id}
class SpecificPost(Resource):
def get(self, post_id: UUID):
post = next(filter(lambda x: x.id == post_id, posts), None)
if post is None:
raise HTTPException(status_code=404)
return post
def put(self, post_id: UUID, new_data: UpdatePost):
post = next(filter(lambda x: x.id == post_id, posts), None)
if post is None:
raise HTTPException(status_code=404)
post.body = new_data.body
return post
def delete(self, post_id: UUID):
post = next(filter(lambda x: x.id == post_id, posts), None)
if post is None:
return Response(status_code=204)
posts.remove(post)
return Response(status_code=204)
urls = {
'/posts': Posts,
'/posts/{post_id}': SpecificPost
}
for path, resource in urls.items():
api.add_resource(resource, path=path)
api.apply()
if __name__ == '__main__':
run('posts:app')
|
| posts.py |
|---|
| ...
class NewPost(BaseModel):
id: UUID = Field(default_factory=uuid4)
body: str
class UpdatePost(BaseModel):
body: str
class Posts(Resource):
def get(self, query_param: str = Query(None, description='My query')):
return {'items': posts, 'total_count': len(posts)}
def post(self, new_post: NewPost):
posts.append(new_post)
return {'post_id': new_post.id}
class SpecificPost(Resource):
def get(self, post_id: UUID):
post = next(filter(lambda x: x.id == post_id, posts), None)
if post is None:
raise HTTPException(status_code=404)
return post
def put(self, post_id: UUID, new_data: UpdatePost):
post = next(filter(lambda x: x.id == post_id, posts), None)
if post is None:
raise HTTPException(status_code=404)
post.body = new_data.body
return post
def delete(self, post_id: UUID):
post = next(filter(lambda x: x.id == post_id, posts), None)
if post is None:
return Response(status_code=204)
posts.remove(post)
return Response(status_code=204)
...
|
You can declare pydantic models for serialize body, query and path parameters,
just like you would do with a standard route declaration in FastAPI
Declaring urls
| posts.py |
|---|
| ...
urls = {
'/posts': Posts,
'/posts/{post_id}': SpecificPost
}
for path, resource in urls.items():
api.add_resource(resource, path=path)
api.apply()
...
|
Declare the path in the urls dictionary, where the key is the path to the resource, and the value is the corresponding class.
Note
It is allowed not to specify the route paths in the urls dictionary, but instead to specify them in the classes themselves, in the path attribute.
In this case, only your class should be passed to the add_resource method.
And then the familiar commands are already coming:
add_resource - Add a resource to the api
apply - include all declared routes from api to your FastAPI app
As a result, you will have 5 routes announced:
GET /api/posts
POST /api/posts
GET /api/posts/{post_id}
PUT /api/posts/{post_id}
DELETE /api/posts/{post_id}
Route settings
You probably know that FastAPI allows you to pass various arguments when declaring a route, such as: summary, response_model etc.
Here it can also be done using the route_settings decorator.
| posts.py |
|---|
| from uuid import UUID, uuid4
from fastapi import FastAPI, Response, HTTPException, Query
from fastapi_restful import RESTExtension, Resource, route_settings
from pydantic import BaseModel, Field
from uvicorn import run
...
|
Import route_settings
| posts.py |
|---|
| ...
class Posts(Resource):
@route_settings(summary='Get all posts')
def get(self, query_param: str = Query(None, description='My query')):
return {'items': posts, 'total_count': len(posts)}
...
|
With this decorator, we can throw all the arguments that accepts method the FastAPI.add_api_route.
Note
The example below is absolutely identical.
In this example, the route is declared with standard FastAPI methods.
app.get('/api/posts', summary='Get all posts')
def all_posts(self, query_param: str = Query(None, description='My query param')):
return {'items': posts, 'total_count': len(posts)}
Resource tag
The Resource class has the tag attribute.
We can set it a value, for example, for grouping routes in the swagger documentation.
| posts.py |
|---|
| ...
class Posts(Resource):
tag = 'Manage posts'
@route_settings(summary='Get all posts')
def get(self, query_param: str = Query(None, description='My query')):
return {'items': posts, 'total_count': len(posts)}
def post(self, new_post: NewPost):
posts.append(new_post)
return {'post_id': new_post.id}
class SpecificPost(Resource):
tag = 'Manage posts'
def get(self, post_id: UUID):
post = next(filter(lambda x: x.id == post_id, posts), None)
if post is None:
raise HTTPException(status_code=404)
return post
def put(self, post_id: UUID, new_data: UpdatePost):
post = next(filter(lambda x: x.id == post_id, posts), None)
if post is None:
raise HTTPException(status_code=404)
post.body = new_data.body
return post
def delete(self, post_id: UUID):
post = next(filter(lambda x: x.id == post_id, posts), None)
if post is None:
return Response(status_code=204)
posts.remove(post)
return Response(status_code=204)
...
|
Set the value Manage posts for each attribute tag of the Resource classes.
If you launch the application and open the documentation page in the browser (http://127.0.0.1:8000/docs),
you will see that all routes are grouped by tag the Manage posts
