Function 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 Function#
Create the book_api()
in app/apis.py
:
from panther.app import API
@API()
async def book_api():
...
We are going to complete it later ...
Update URLs#
Add the book_api
in app/urls.py
:
from app.apis import book_api
urls = {
'book/': book_api,
}
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
request: Request
inbook_api
function:from panther.app import API from panther.request import Request @API() async def book_api(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
book_api
asinput_model
so the incoming data will be validated and cleaned automatically:Now we have access tofrom panther.app import API from panther.request import Request from app.serializers import BookSerializer @API(input_model=BookSerializer) async def book_api(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 API from panther.request import Request from app.serializers import BookSerializer @API(input_model=BookSerializer) async def book_api(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 API from panther.request import Request from app.serializers import BookSerializer from app.models import Book @API(input_model=BookSerializer) async def book_api(request: Request): body: BookSerializer = request.validated_data await Book.insert_one( name=body.name, author=body.author, pages_count=body.pages_count, ) ...
-
But we only want this happens in
post
requests, so we add thiscondition
:from panther.app import API from panther.request import Request from app.serializers import BookSerializer from app.models import Book @API(input_model=BookSerializer) async def book_api(request: Request): if request.method == 'POST': 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
and501 Not Implemented
for other methods:from panther import status from panther.app import API from panther.request import Request from panther.response import Response from app.serializers import BookSerializer from app.models import Book @API(input_model=BookSerializer) async def book_api(request: Request): if request.method == 'POST': body: BookSerializer = request.validated_data book: 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) return Response(status_code=status.HTTP_501_NOT_IMPLEMENTED)
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 condition on GET
methods and return the lists of books:
from panther import status
from panther.app import API
from panther.request import Request
from panther.response import Response
from app.serializers import BookSerializer
from app.models import Book
@API(input_model=BookSerializer)
async def book_api(request: Request):
if request.method == 'POST':
...
elif request.method == 'GET':
books = await Book.find()
return Response(data=books, status_code=status.HTTP_200_OK)
return Response(status_code=status.HTTP_501_NOT_IMPLEMENTED)
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 yourAPI()
from panther import status from panther.app import API from panther.request import Request from panther.response import Response from app.serializers import BookSerializer, BookOutputSerializer from app.models import Book @API(input_model=BookSerializer, output_model=BookOutputSerializer) async def book_api(request: Request): if request.method == 'POST': ... elif request.method == 'GET': books = await Book.find() return Response(data=books, status_code=status.HTTP_200_OK) return Response(status_code=status.HTTP_501_NOT_IMPLEMENTED)
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 API
from panther.request import Request
from panther.response import Response
from app.serializers import BookSerializer, BookOutputSerializer
from app.models import Book
@API(input_model=BookSerializer, output_model=BookOutputSerializer, cache=True, cache_exp_time=timedelta(seconds=10))
async def book_api(request: Request):
if request.method == 'POST':
...
elif request.method == 'GET':
books = await Book.find()
return Response(data=books, status_code=status.HTTP_200_OK)
return Response(status_code=status.HTTP_501_NOT_IMPLEMENTED)
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 API()
, 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 API
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
@API(
input_model=BookSerializer,
output_model=BookOutputSerializer,
cache=True,
cache_exp_time=timedelta(seconds=10),
throttling=Throttling(rate=10, duration=timedelta(minutes=1))
)
async def book_api(request: Request):
if request.method == 'POST':
...
elif request.method == 'GET':
books = await Book.find()
return Response(data=books, status_code=status.HTTP_200_OK)
return Response(status_code=status.HTTP_501_NOT_IMPLEMENTED)
API - Retrieve a Book#
For retrieve
, update
and delete
API, we are going to
-
Create another api named
single_book_api
inapp/apis.py
:from panther.app import API from panther.request import Request @API() async def single_book_api(request: Request): ...
-
Add it in
app/urls.py
:from app.apis import book_api, single_book_api urls = { 'book/': book_api, 'book/<book_id>/': single_book_api, }
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 API from panther.request import Request from panther.response import Response from app.models import Book @API() async def single_book_api(request: Request, book_id: int): if request.method == 'GET': 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#
-
We can update in several ways:
-
Update a document
from panther import status from panther.app import API from panther.request import Request from panther.response import Response from app.models import Book from app.serializers import BookSerializer @API(input_model=BookSerializer) async def single_book_api(request: Request, book_id: int): body: BookSerializer = request.validated_data if request.method == 'GET': ... elif request.method == 'PUT': book: Book = await Book.find_one(id=book_id) await book.update( name=body.name, author=body.author, pages_count=body.pages_count ) return Response(status_code=status.HTTP_202_ACCEPTED)
-
Update with
update_one
queryfrom panther import status from panther.app import API from panther.request import Request from panther.response import Response from app.models import Book from app.serializers import BookSerializer @API(input_model=BookSerializer) async def single_book_api(request: Request, book_id: int): if request.method == 'GET': ... elif request.method == 'PUT': 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)
-
Update with
update_many
queryfrom panther import status from panther.app import API from panther.request import Request from panther.response import Response from app.models import Book from app.serializers import BookSerializer @API(input_model=BookSerializer) async def single_book_api(request: Request, book_id: int): if request.method == 'GET': ... elif request.method == 'PUT': updated_count: int = await Book.update_many({'id': book_id}, request.validated_data.model_dump()) data = {'updated_count': updated_count} return Response(data=data, status_code=status.HTTP_202_ACCEPTED)
You can handle the PATCH the same way as PUT
-
API - Delete a Book#
-
We can delete in several ways too:
-
Delete a document
2. Delete withfrom panther import status from panther.app import API from panther.request import Request from panther.response import Response from app.models import Book @API() async def single_book_api(request: Request, book_id: int): if request.method == 'GET': ... elif request.method == 'PUT': ... elif request.method == 'DELETE': 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)
delete_one
queryfrom panther import status from panther.app import API from panther.request import Request from panther.response import Response from app.models import Book @API() async def single_book_api(request: Request, book_id: int): if request.method == 'GET': ... elif request.method == 'PUT': ... elif request.method == 'DELETE': is_deleted: bool = await Book.delete_one(id=book_id) return Response(status_code=status.HTTP_204_NO_CONTENT)
-
Delete with
delete_many
queryfrom panther import status from panther.app import API from panther.request import Request from panther.response import Response from app.models import Book @API() async def single_book_api(request: Request, book_id: int): if request.method == 'GET': ... elif request.method == 'PUT': ... elif request.method == 'DELETE': deleted_count: int = await Book.delete_many(id=book_id) return Response(status_code=status.HTTP_204_NO_CONTENT)
-