Class Base
We assume you could run the project with Introduction
Now let's write custom APIs for Create
, Retrieve
, Update
and Delete
a Book
:
Structure & Requirements#
Create Model#
Create a model named Book
in app/models.py
:
from panther.db import Model
class Book(Model):
name: str
author: str
pages_count: int
Create API Class#
Create the BookAPI()
in app/apis.py
:
from panther.app import GenericAPI
class BookAPI(GenericAPI):
...
We are going to complete it later ...
Update URLs#
Add the BookAPI
in app/urls.py
:
from app.apis import BookAPI
urls = {
'book/': BookAPI,
}
We assume that the urls
in core/urls.py
pointing to app/urls.py
, like below:
from app.urls import urls as app_urls
urls = {
'/': app_urls,
}
Add Database#
Add DATABASE
in configs
, we are going to add pantherdb
PantherDB is a Simple, File-Base and Document Oriented database
...
DATABASE = {
'engine': {
'class': 'panther.db.connections.PantherDBConnection',
}
}
...
APIs#
API - Create a Book#
Now we are going to create a book on post
request, We need to:
-
Declare
post
method inBookAPI
:from panther.app import GenericAPI class BookAPI(GenericAPI): async def post(self): ...
-
Declare
request: Request
inBookAPI.post()
function:from panther.app import GenericAPI from panther.request import Request class BookAPI(GenericAPI): async def post(self, request: Request): ...
-
Create serializer in
app/serializers.py
, we usedpydantic
for thevalidation
ofrequest.data
:from pydantic import BaseModel class BookSerializer(BaseModel): name: str author: str pages_count: int
-
Pass the created serializer to our
BookAPI
asinput_model
so the incoming data will be validated and cleaned automatically:Now we have access tofrom panther.app import GenericAPI from panther.request import Request from app.serializers import BookSerializer class BookAPI(GenericAPI): input_model = BookSerializer async def post(self, request: Request): ...
request.data
, We are going to use it like the below for ease of use, so the auto-suggest helps us in development:from panther.app import GenericAPI from panther.request import Request from app.serializers import BookSerializer class BookAPI(GenericAPI): input_model = BookSerializer async def post(self, request: Request): body: BookSerializer = request.validated_data ...
-
Now we have access to the validated data, and we can create our first book:
from panther.app import GenericAPI from panther.request import Request from app.serializers import BookSerializer from app.models import Book class BookAPI(GenericAPI): input_model = BookSerializer async def post(self, request: Request): body: BookSerializer = request.validated_data await Book.insert_one( name=body.name, author=body.author, pages_count=body.pages_count, ) ...
-
And finally we return
201 Created
status_code as response ofpost
:from panther import status from panther.app import GenericAPI from panther.request import Request from panther.response import Response from app.serializers import BookSerializer from app.models import Book class BookAPI(GenericAPI): input_model = BookSerializer async def post(self, request: Request): body: BookSerializer = request.validated_data book = await Book.insert_one( name=body.name, author=body.author, pages_count=body.pages_count, ) return Response(data=book, status_code=status.HTTP_201_CREATED)
The response.data can be
Instance of Models
,dict
,str
,tuple
,list
,str
orNone
Panther will return
None
if you don't return anything as response.
API - List of Books#
We just need to add another method for GET
method and return the lists of books:
from panther import status
from panther.app import GenericAPI
from panther.request import Request
from panther.response import Response
from app.serializers import BookSerializer
from app.models import Book
class BookAPI(GenericAPI):
input_model = BookSerializer
async def post(self, request: Request):
...
async def get(self):
books = await Book.find()
return Response(data=books, status_code=status.HTTP_200_OK)
Panther validate input with
input_model
, only inPOST
,PUT
,PATCH
methods.
Filter Response Fields#
Assume we don't want to return field author
in response:
-
Create new serializer in
app/serializers.py
:from pydantic import BaseModel class BookOutputSerializer(BaseModel): name: str pages_count: int
-
Add the
BookOutputSerializer
asoutput_model
to yourclass
from panther import status from panther.app import GenericAPI from panther.request import Request from panther.response import Response from app.serializers import BookSerializer, BookOutputSerializer from app.models import Book class BookAPI(GenericAPI): input_model = BookSerializer output_model = BookOutputSerializer async def post(self, request: Request): ... async def get(self): books = await Book.find() return Response(data=books, status_code=status.HTTP_200_OK)
Panther use the
output_model
, in all methods.
Cache The Response#
For caching the response, we should add cache=True
in API()
.
And it will return the cached response every time till cache_exp_time
For setting a custom expiration time for API we need to add cache_exp_time
to API()
:
from datetime import timedelta
from panther import status
from panther.app import GenericAPI
from panther.request import Request
from panther.response import Response
from app.serializers import BookSerializer, BookOutputSerializer
from app.models import Book
class BookAPI(GenericAPI):
input_model = BookSerializer
output_model = BookOutputSerializer
cache = True
cache_exp_time = timedelta(seconds=10)
async def post(self, request: Request):
...
async def get(self):
books = await Book.find()
return Response(data=books, status_code=status.HTTP_200_OK)
Panther is going to use the
DEFAULT_CACHE_EXP
fromcore/configs.py
ifcache_exp_time
has not been set.
Throttle The Request#
For setting rate limit for requests, we can add throttling to BookAPI
, it should be the instance of panther.throttling.Throttling
,
something like below (in the below example user can't request more than 10 times in a minutes):
from datetime import timedelta
from panther import status
from panther.app import GenericAPI
from panther.request import Request
from panther.response import Response
from panther.throttling import Throttling
from app.serializers import BookSerializer, BookOutputSerializer
from app.models import Book
class BookAPI(GenericAPI):
input_model = BookSerializer
output_model = BookOutputSerializer
cache = True
cache_exp_time = timedelta(seconds=10)
throttling = Throttling(rate=10, duration=timedelta(minutes=1))
async def post(self, request: Request):
...
async def get(self):
books = await Book.find()
return Response(data=books, status_code=status.HTTP_200_OK)
API - Retrieve a Book#
For retrieve
, update
and delete
API, we are going to
-
Create another class named
SingleBookAPI
inapp/apis.py
:from panther.app import GenericAPI class SingleBookAPI(GenericAPI): ...
-
Add it in
app/urls.py
:from app.apis import BookAPI, SingleBookAPI urls = { 'book/': BookAPI, 'book/<book_id>/': SingleBookAPI, }
You should write the Path Variable in
<
and>
You should have the parameter with the same name of
path variable
in youapi
with normaltype hints
Panther will convert type of the
path variable
to your parameter type, then pass it
-
Complete the api:
from panther import status from panther.app import GenericAPI from panther.response import Response from app.models import Book class SingleBookAPI(GenericAPI): async def get(self, book_id: int): if book := await Book.find_one(id=book_id): return Response(data=book, status_code=status.HTTP_200_OK) else: return Response(status_code=status.HTTP_404_NOT_FOUND)
API - Update a Book#
Add another method named put()
for PUT
method and update the book you want:
from panther import status
from panther.app import GenericAPI
from panther.request import Request
from panther.response import Response
from app.models import Book
from app.serializers import BookSerializer
class SingleBookAPI(GenericAPI):
input_model = BookSerializer
async def get(self, book_id: int):
...
async def put(self, request: Request, book_id: int):
is_updated: bool = await Book.update_one({'id': book_id}, request.validated_data.model_dump())
data = {'is_updated': is_updated}
return Response(data=data, status_code=status.HTTP_202_ACCEPTED)
You can handle the PATCH the same way as PUT
API - Delete a Book#
Add another method named delete()
for DELETE
method and delete the book you want:
from panther import status
from panther.app import GenericAPI
from panther.request import Request
from panther.response import Response
from app.models import Book
class SingleBookAPI(GenericAPI):
input_model = BookSerializer
async def get(self, book_id: int):
...
async def put(self, request: Request, book_id: int):
...
async def delete(self, book_id: int):
is_deleted: bool = await Book.delete_one(id=book_id)
if is_deleted:
return Response(status_code=status.HTTP_204_NO_CONTENT)
else:
return Response(status_code=status.HTTP_400_BAD_REQUEST)