Endpoints
CRUD
In api
directory of your project add blog.py
with the following content:
import ormar.exceptions
from fastapi import Header, HTTPException
from freenit.api.router import route
from freenit.models.pagination import Page, paginate
from ..models.blog import Blog, BlogOptional
tags = ["blog"]
@route("/blogs", tags=tags)
class BlogListAPI:
@staticmethod
async def get(
page: int = Header(default=1),
perpage: int = Header(default=10),
) -> Page[Blog]:
return await paginate(Blog.objects, page, perpage)
@staticmethod
async def post(blog: Blog) -> Blog:
await blog.save()
return blog
@route("/blogs/{id}", tags=tags)
class BlogDetailAPI:
@staticmethod
async def get(id: int) -> Blog:
try:
blog = await Blog.objects.get(pk=id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such blog")
return blog
@staticmethod
async def patch(id: int, blog_data: BlogOptional) -> Blog:
try:
blog = await Blog.objects.get(pk=id)
await blog.patch(blog_data)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such blog")
return blog
@staticmethod
async def delete(id: int) -> Blog:
try:
blog = await Blog.objects.get(pk=id)
except ormar.exceptions.NoMatch:
raise HTTPException(status_code=404, detail="No such blog")
await blog.delete()
return blog
@route
is
Freenit's decorator to make it easy to write class based endpoints. As FastAPI
itself has great support for function based endpoints, the idea was to make
it possible for developer to choose between functions and classes. With Freenit
you can write any style you want. Also note that class methods are static
(decorated with @staticmethod
) because API classes will never create an object.
Or in other words, methods are going to be called on class, not object. Order of
decorators is important and @staticmethod
has to be the top one. The
@description
is not mandatory, but highly preferable. If no @description
is
given, default is to concatenate name of the method and first tag and use them
as description.
By default endpoint to get list of blog posts is paginated. That means that it
will return first page with first 10 results by default. Through page
and
perpage
frontend can require other pages and the page size.
Return value type hinting is important. It will tell Freenit what object is
returned from the method and how to convert it to JSON. Alternatively, you can
use responses
attribute in @route
like the following:
@route('/blogs', tags=['blog'], responses={'post': Blog})
class BlogListAPI():
@staticmethod
async def post(blog: Blog, user: User = Depends(user_perms)):
blog.user = user
await blog.save()
return blog
@route
. If method
also has return type hinting, responses object has priority in denoting how to
serialize object to JSON. It is the same as FastAPI's response_model
argument
and it exists for situations when type hinting is not expressive enough.
If you need to include and/or exclude fields, you can use get_pydantic()
and
exclude/include
to get what you want. For example:
BlogReturn = Blog.get_pydantic(exclude={'id'}):
@route('/blogs', tags=['blog'])
class BlogListAPI():
@staticmethod
async def post(blog: Blog, user: User = Depends(user_perms)) -> BlogReturn:
blog.user = user
await blog.save()
return blog
Blog.get_pydantic()
can be used in type hinting as well as argument
to responses
object in @route
.
DB Migration
To connect it all, you need to add the following to api/__init__.py
:
import myproject.api.blog
After that you need to create migration. To do that run the following command
from myproject
directory:
alembic revision --autogenerate -m blog
alembic/versions
and format it with
black. Next time you run bin/devel.sh
that migration will be applied.
Now you should see Blog endpoint in Swagger