Skip to content

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:

  1. Declare post method in BookAPI:

    from panther.app import GenericAPI
    
    
    class BookAPI(GenericAPI):
    
        async def post(self):
            ...
    

  2. Declare request: Request in BookAPI.post() function:

    from panther.app import GenericAPI
    from panther.request import Request
    
    
    class BookAPI(GenericAPI):
    
        async def post(self, request: Request):
            ...
    

  3. Create serializer in app/serializers.py, we used pydantic for the validation of request.data :

    from pydantic import BaseModel
    
    
    class BookSerializer(BaseModel):
        name: str
        author: str
        pages_count: int
    

  4. Pass the created serializer to our BookAPI as input_model so the incoming data will be validated and cleaned automatically:

    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):
            ...
    
    Now we have access to 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
            ...
    
  5. 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,
            )
            ...
    
  6. And finally we return 201 Created status_code as response of post:

    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 or None

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 in POST, PUT, PATCH methods.

Filter Response Fields#

Assume we don't want to return field author in response:

  1. Create new serializer in app/serializers.py:

    from pydantic import BaseModel
    
    
    class BookOutputSerializer(BaseModel):
        name: str
        pages_count: int
    
  2. Add the BookOutputSerializer as output_model to your class

    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 from core/configs.py if cache_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

  1. Create another class named SingleBookAPI in app/apis.py:

    from panther.app import GenericAPI
    
    
    class SingleBookAPI(GenericAPI):
        ...
    
  2. 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 you api with normal type hints

Panther will convert type of the path variable to your parameter type, then pass it

  1. 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)