From f40f4436f0321abdcfa54f44946e24e5b2bac1a8 Mon Sep 17 00:00:00 2001
From: AliRn Is A Fast & Friendly Web Framework For Building Async APIs With Python 3.11+ Supported by JetBrains Panther uses Uvicorn as ASGI (Asynchronous Server Gateway Interface) Panther Uses bpython for shell Then run the project: now you can see these two urls: http://127.0.0.1:8000/ http://127.0.0.1:8000/info/ Writing Your First CRUD: First CRUD core/configs.py: core/urls.py: app/urls.py: app/apis.py: Variable: Type: Default: You can set your Authentication class in We already have one built-in authentication class which is used You can write your own authentication class too (we are going to discuss it) This class will You can customize these 3 variables for key \u2003\u2003\u2003\u2003--> default is algorithm \u2003 --> default is life_time\u2003\u2003--> default is Create a class and inherits it from Implement Address it in You can look at the source code of JWTAuthentication for [Test Source Code] [Test Source Code] [Test Source Code] [Test Source Code] [Test Source Code] [Test Source Code] [Test Source Code] [Test Source Code] [Test Source Code] We assume you could run the project with Introduction Now let's write custom API Create a model named Create the We are going to complete it later ... Add the We assume that the Add one database middleware in PantherDB is a Simple, FileBase and Document Oriented database: Now we are going to create a book on Declare
"},{"location":"#installation","title":"Installation","text":"
$ python3 -m venv .venv
$ source .venv/bin/activate
$ .\\.venv\\Scripts\\activate
"},{"location":"#usage","title":"Usage","text":""},{"location":"#create-project","title":"Create Project","text":"$ pip install panther
$ pip install panther[full]
"},{"location":"#run-project","title":"Run Project","text":"$ panther create <project_name> <directory>\n
"},{"location":"#monitoring-requests","title":"Monitoring Requests","text":"$ panther run
"},{"location":"#python-shell","title":"Python Shell","text":"$ panther monitor
"},{"location":"#example","title":"Example","text":"$ panther shell
$ cd myproject
$ panther run
or $ panther run --reload
"},{"location":"#or-create-it-yourself","title":"or create it yourself:","text":"$ panther create myproject\n
URLs = 'core.urls.url_routing'\n
from app.urls import urls as app_urls\n\nurl_routing = {\n '/': app_urls,\n}\n
from app.apis import hello_world, info\n\nurls = {\n '': hello_world,\n 'info/': info,\n}\n
"},{"location":"authentications/","title":"Authentications","text":"from datetime import datetime, timedelta\n\nfrom panther.app import API\nfrom panther.configs import config\nfrom panther import version, status\nfrom panther.request import Request\nfrom panther.response import Response\nfrom panther.throttling import Throttling\n\n\nInfoThrottling = Throttling(rate=5, duration=timedelta(minutes=1))\n\n@API()\nasync def hello_world():\n return {'detail': 'Hello World'}\n\n\n@API(cache=True, throttling=InfoThrottling)\nasync def info(request: Request):\n data = {\n 'version': version(),\n 'datetime_now': datetime.now().isoformat(),\n 'user_agent': request.headers.user_agent,\n 'db_engine': config['db_engine'],\n }\n return Response(data=data, status_code=status.HTTP_202_ACCEPTED)\n
AUTHENTICATION
str
None
core/configs.py
, then Panther will use this class for authentication in every API
, if you set auth=True
in @API()
, and put the user
in request.user
or raise HTTP_401_UNAUTHORIZED
JWT
for authentication.
token
from Authorization
header of request with keyword of Bearer
decode
it user
in USER_MODEL
you have already setJWTAuthentication
is going to use panther.db.models.BaseUser
if you didn't set the USER_MODEL
in your core/configs.py
JWTAuthentication
in your core/configs.py
as JWTConfig
like below (JWTConfig
is optional):...\nfrom datetime import timedelta\nfrom panther.utils import load_env \nfrom pathlib import Path\n\nBASE_DIR = Path(__name__).resolve().parent \nenv = load_env(BASE_DIR / '.env')\n\nSECRET_KEY = env['SECRET_KEY']\n\nJWTConfig = { \n 'key': SECRET_KEY, \n 'algorithm': 'HS256', \n 'life_time': timedelta(days=2), \n}\n
SECRET_KEY
HS256
timedelta(days=1)
panther.authentications.BaseAuthentication
authentication(cls, request: Request)
method
request.headers.authorization
or ...USER_MODEL
panther.exceptions.AuthenticationException
core/configs.py
AUTHENTICATION = 'project_name.core.authentications.CustomAuthentication'
"},{"location":"benchmarks/#results","title":"Results","text":""},{"location":"benchmarks/#sanic","title":"Sanic","text":"Create
, Retrieve
, Update
and Delete
for a Book
:Book
in app/models.py
:
"},{"location":"class_first_crud/#create-api-class","title":"Create API Class","text":"from panther.db import Model\n\n\nclass Book(Model):\n name: str\n author: str\n pages_count: int\n
BookAPI()
in app/apis.py
:from panther.app import GenericAPI\n\n\nclass BookAPI(GenericAPI):\n ... \n
BookAPI
in app/urls.py
:from app.apis import BookAPI\n\n\nurls = {\n 'book/': BookAPI,\n}\n
urls
in core/urls.py
pointing to app/urls.py
, like below:
"},{"location":"class_first_crud/#add-database-middleware","title":"Add Database Middleware","text":"from app.urls import urls as app_urls\n\n\nurls = {\n '/': app_urls,\n}\n
core/configs.py
MIDDLEWARES
, we are going to add pantherdb
"},{"location":"class_first_crud/#apis","title":"APIs","text":""},{"location":"class_first_crud/#api-create-a-book","title":"API - Create a Book","text":"...\n\nMIDDLEWARES = [\n ('panther.middlewares.db.Middleware', {'url': f'pantherdb://{BASE_DIR}/{DB_NAME}.pdb'}),\n]\n
post
request, We need to:
post
method in BookAPI
: from panther.app import GenericAPI\n\n\nclass BookAPI(GenericAPI):\n\n def post(self):\n ...\n
Declare request: Request
in BookAPI.post()
function:
from panther.app import GenericAPI\nfrom panther.request import Request\n\n\nclass BookAPI(GenericAPI):\n\n def post(self, request: Request):\n ...\n
Create serializer in app/serializers.py
, we used pydantic
for the validation
of request.data
:
from pydantic import BaseModel\n\n\nclass BookSerializer(BaseModel):\n name: str\n author: str\n pages_count: int\n
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\nfrom panther.request import Request\n\nfrom app.serializers import BookSerializer\n\n\nclass BookAPI(GenericAPI):\n input_model = BookSerializer\n\n def post(self, request: Request):\n ...\n
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\nfrom panther.request import Request\n\nfrom app.serializers import BookSerializer\n\n\nclass BookAPI(GenericAPI):\n input_model = BookSerializer\n\n def post(self, request: Request):\n body: BookSerializer = request.data\n ...\n
Now we have access to the validated data, and we can create our first book:
from panther.app import GenericAPI\nfrom panther.request import Request\n\nfrom app.serializers import BookSerializer\nfrom app.models import Book\n\n\nclass BookAPI(GenericAPI):\n input_model = BookSerializer\n\n def post(self, request: Request):\n body: BookSerializer = request.data\n Book.insert_one(\n name=body.name,\n author=body.author,\n pages_count=body.pages_count,\n )\n ...\n
And finally we return 201 Created
status_code as response of post
:
from panther import status\nfrom panther.app import GenericAPI\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.serializers import BookSerializer\nfrom app.models import Book\n\n\nclass BookAPI(GenericAPI):\n input_model = BookSerializer\n\n def post(self, request: Request):\n body: BookSerializer = request.data\n book = Book.insert_one(\n name=body.name,\n author=body.author,\n pages_count=body.pages_count,\n )\n return Response(data=book, status_code=status.HTTP_201_CREATED)\n
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.
We just need to add another method for GET
method and return the lists of books:
from panther import status\nfrom panther.app import GenericAPI\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.serializers import BookSerializer\nfrom app.models import Book\n\n\nclass BookAPI(GenericAPI):\n input_model = BookSerializer\n\n def post(self, request: Request):\n ...\n\n def get(self):\n books: list[Book] = Book.find()\n return Response(data=books, status_code=status.HTTP_200_OK)\n
Panther validate input with input_model
, only in POST
, PUT
, PATCH
methods.
Assume we don't want to return field author
in response:
Create new serializer in app/serializers.py
:
from pydantic import BaseModel\n\n\nclass BookOutputSerializer(BaseModel):\n name: str\n pages_count: int\n
Add the BookOutputSerializer
as output_model
to your class
from panther import status\nfrom panther.app import GenericAPI\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.serializers import BookSerializer, BookOutputSerializer\nfrom app.models import Book\n\n\nclass BookAPI(GenericAPI):\n input_model = BookSerializer\n output_model = BookOutputSerializer\n\n def post(self, request: Request):\n ...\n\n def get(self):\n books: list[Book] = Book.find()\n return Response(data=books, status_code=status.HTTP_200_OK)\n
Panther use the output_model
, in all methods.
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\n\nfrom panther import status\nfrom panther.app import GenericAPI\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.serializers import BookSerializer, BookOutputSerializer\nfrom app.models import Book\n\n\nclass BookAPI(GenericAPI):\n input_model = BookSerializer\n output_model = BookOutputSerializer\n cache = True\n cache_exp_time = timedelta(seconds=10)\n\n def post(self, request: Request):\n ...\n\n def get(self):\n books: list[Book] = Book.find()\n return Response(data=books, status_code=status.HTTP_200_OK)\n
Panther is going to use the DEFAULT_CACHE_EXP
from core/configs.py
if cache_exp_time
has not been set.
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\n\nfrom panther import status\nfrom panther.app import GenericAPI\nfrom panther.request import Request\nfrom panther.response import Response\nfrom panther.throttling import Throttling\n\nfrom app.serializers import BookSerializer, BookOutputSerializer\nfrom app.models import Book\n\n\nclass BookAPI(GenericAPI):\n input_model = BookSerializer\n output_model = BookOutputSerializer\n cache = True\n cache_exp_time = timedelta(seconds=10)\n throttling = Throttling(rate=10, duration=timedelta(minutes=1))\n\n def post(self, request: Request):\n ...\n\n def get(self):\n books: list[Book] = Book.find()\n return Response(data=books, status_code=status.HTTP_200_OK)\n
"},{"location":"class_first_crud/#api-retrieve-a-book","title":"API - Retrieve a Book","text":"For retrieve
, update
and delete
API, we are going to
Create another class named SingleBookAPI
in app/apis.py
:
from panther.app import GenericAPI\n\n\nclass SingleBookAPI(GenericAPI):\n ...\n
Add it in app/urls.py
:
from app.apis import BookAPI, SingleBookAPI\n\n\nurls = {\n 'book/': BookAPI,\n 'book/<book_id>/': SingleBookAPI,\n}\n
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
Complete the api:
from panther import status\nfrom panther.app import GenericAPI\nfrom panther.response import Response\n\nfrom app.models import Book\n\n\nclass SingleBookAPI(GenericAPI):\n\n def get(self, book_id: int):\n if book := Book.find_one(id=book_id):\n return Response(data=book, status_code=status.HTTP_200_OK)\n else:\n return Response(status_code=status.HTTP_404_NOT_FOUND)\n
We can update in several ways:
Update a document
from panther import status\nfrom panther.app import GenericAPI\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.models import Book\nfrom app.serializers import BookSerializer\n\n\nclass SingleBookAPI(GenericAPI):\n input_model = BookSerializer\n\n def get(self, book_id: int):\n ...\n\n def put(self, request: Request, book_id: int):\n body: BookSerializer = request.data\n\n book: Book = Book.find_one(id=book_id)\n book.update(\n name=body.name, \n author=body.author, \n pages_count=body.pages_count\n )\n return Response(status_code=status.HTTP_202_ACCEPTED)\n
Update with update_one
query
from panther import status\nfrom panther.app import GenericAPI\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.models import Book\nfrom app.serializers import BookSerializer\n\n\nclass SingleBookAPI(GenericAPI):\n input_model = BookSerializer\n\n def get(self, book_id: int):\n ...\n\n def put(self, request: Request, book_id: int):\n is_updated: bool = Book.update_one({'id': book_id}, request.data.dict())\n data = {'is_updated': is_updated}\n return Response(data=data, status_code=status.HTTP_202_ACCEPTED)\n
Update with update_many
query
from panther import status\nfrom panther.app import GenericAPI\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.models import Book\nfrom app.serializers import BookSerializer\n\n\nclass SingleBookAPI(GenericAPI):\n input_model = BookSerializer\n\n def get(self, book_id: int):\n ...\n\n def put(self, request: Request, book_id: int):\n updated_count: int = Book.update_many({'id': book_id}, request.data.dict())\n data = {'updated_count': updated_count}\n return Response(data=data, status_code=status.HTTP_202_ACCEPTED)\n
You can handle the PATCH the same way as PUT
We can delete in several ways too:
Delete a document
from panther import status\n from panther.app import GenericAPI\n from panther.request import Request\n from panther.response import Response\n\n from app.models import Book\n\n class SingleBookAPI(GenericAPI):\n input_model = BookSerializer\n\n def get(self, book_id: int):\n ...\n\n def put(self, request: Request, book_id: int):\n ...\n\n def delete(self, book_id: int):\n is_deleted: bool = Book.delete_one(id=book_id)\n if is_deleted:\n return Response(status_code=status.HTTP_204_NO_CONTENT)\n else:\n return Response(status_code=status.HTTP_400_BAD_REQUEST)\n
2. Delete with delete_one
query from panther import status\n from panther.app import GenericAPI\n from panther.request import Request\n from panther.response import Response\n\n from app.models import Book\n\n\n class SingleBookAPI(GenericAPI):\n input_model = BookSerializer\n\n def get(self, book_id: int):\n ...\n\n def put(self, request: Request, book_id: int):\n ...\n\n def delete(self, book_id: int):\n is_deleted: bool = Book.delete_one(id=book_id)\n return Response(status_code=status.HTTP_204_NO_CONTENT)\n
Delete with delete_many
query
from panther import status\n from panther.app import GenericAPI\n from panther.request import Request\n from panther.response import Response\n\n from app.models import Book\n\n\n class SingleBookAPI(GenericAPI):\n input_model = BookSerializer\n\n def get(self, book_id: int):\n ...\n\n def put(self, request: Request, book_id: int):\n ...\n\n def delete(self, book_id: int):\n deleted_count: int = Book.delete_many(id=book_id)\n return Response(status_code=status.HTTP_204_NO_CONTENT)\n
Panther stores all the configs in the core/configs.py
Type: bool
(Default: False
)
It should be True
if you want to use panther monitor
command and see the monitoring logs
If True
it will:
logs/monitoring.log
Type: bool
(Default: False
)
If True
it will:
logs/query.log
Type: list
(Default: [ ]
)
List of middlewares you want to use
"},{"location":"configs/#authentication","title":"AUTHENTICATION","text":"Type: str | None
(Default: None
)
Every request goes through authentication()
method of this class
Example: AUTHENTICATION = 'panther.authentications.JWTAuthentication'
Type: str
(Required)
It should be the address of your urls
dict
Example: URLS = 'configs.urls.url_routing'
Type: timedelta| None
(Default: None
)
We use it as default cache_exp_time
you can overwrite it in your @API
too
It is used when you set cache=True
in @API
decorator
Example: DEFAULT_CACHE_EXP = timedelta(seconds=10)
Type: Throttling | None
(Default: None
)
We use it as default throttling
you can overwrite it in your @API
too
Example: THROTTLING = Throttling(rate=10, duration=timedelta(seconds=10))
Type: str | None
(Default: 'panther.db.models.BaseUser'
)
It is used for authentication
Example: USER_MODEL = 'panther.db.models.User'
Type: dict | None
(Default: JWTConfig = {'key': SECRET_KEY}
)
We use it when you set panther.authentications.JWTAuthentication
as AUTHENTICATION
We assume you could run the project with Introduction
Now let's write custom API Create
, Retrieve
, Update
and Delete
for a Book
:
Create a model named Book
in app/models.py
:
from panther.db import Model\n\n\nclass Book(Model):\n name: str\n author: str\n pages_count: int\n
"},{"location":"function_first_crud/#create-api-function","title":"Create API Function","text":"Create the book_api()
in app/apis.py
:
from panther.app import API\n\n\n@API()\nasync def book_api():\n ... \n
We are going to complete it later ...
"},{"location":"function_first_crud/#update-urls","title":"Update URLs","text":"Add the book_api
in app/urls.py
:
from app.apis import book_api\n\n\nurls = {\n 'book/': book_api,\n}\n
We assume that the urls
in core/urls.py
pointing to app/urls.py
, like below:
from app.urls import urls as app_urls\n\n\nurls = {\n '/': app_urls,\n}\n
"},{"location":"function_first_crud/#add-database-middleware","title":"Add Database Middleware","text":"Add one database middleware in core/configs.py
MIDDLEWARES
, we are going to add pantherdb
PantherDB is a Simple, FileBase and Document Oriented database:
...\n\nMIDDLEWARES = [\n ('panther.middlewares.db.Middleware', {'url': f'pantherdb://{BASE_DIR}/{DB_NAME}.pdb'}),\n]\n
"},{"location":"function_first_crud/#apis","title":"APIs","text":""},{"location":"function_first_crud/#api-create-a-book","title":"API - Create a Book","text":"Now we are going to create a book on post
request, We need to:
Declare request: Request
in book_api
function:
from panther.app import API\nfrom panther.request import Request\n\n\n@API()\nasync def book_api(request: Request):\n ...\n
Create serializer in app/serializers.py
, we used pydantic
for the validation
of request.data
:
from pydantic import BaseModel\n\n\nclass BookSerializer(BaseModel):\n name: str\n author: str\n pages_count: int\n
Pass the created serializer to our book_api
as input_model
so the incoming data will be validated and cleaned automatically:
from panther.app import API\nfrom panther.request import Request\n\nfrom app.serializers import BookSerializer\n\n\n@API(input_model=BookSerializer)\nasync def book_api(request: Request):\n ...\n
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 API\nfrom panther.request import Request\n\nfrom app.serializers import BookSerializer\n\n\n@API(input_model=BookSerializer)\nasync def book_api(request: Request):\n body: BookSerializer = request.data\n ...\n
Now we have access to the validated data, and we can create our first book:
from panther.app import API\nfrom panther.request import Request\n\nfrom app.serializers import BookSerializer\nfrom app.models import Book\n\n\n@API(input_model=BookSerializer)\nasync def book_api(request: Request):\n body: BookSerializer = request.data\n\n Book.insert_one(\n name=body.name,\n author=body.author,\n pages_count=body.pages_count,\n )\n ...\n
But we only want this happens in post
requests, so we add this condition
:
from panther.app import API\nfrom panther.request import Request\n\nfrom app.serializers import BookSerializer\nfrom app.models import Book\n\n\n@API(input_model=BookSerializer)\nasync def book_api(request: Request):\n if request.method == 'POST':\n body: BookSerializer = request.data\n\n Book.create(\n name=body.name,\n author=body.author,\n pages_count=body.pages_count,\n )\n ...\n
And finally we return 201 Created
status_code as response of post
and 501 Not Implemented
for other methods:
from panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.serializers import BookSerializer\nfrom app.models import Book\n\n\n@API(input_model=BookSerializer)\nasync def book_api(request: Request):\n if request.method == 'POST':\n body: BookSerializer = request.data\n\n book: Book = Book.create(\n name=body.name,\n author=body.author,\n pages_count=body.pages_count,\n )\n return Response(data=book, status_code=status.HTTP_201_CREATED)\n\n return Response(status_code=status.HTTP_501_NOT_IMPLEMENTED)\n
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.
We just need to add another condition on GET
methods and return the lists of books:
from panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.serializers import BookSerializer\nfrom app.models import Book\n\n\n@API(input_model=BookSerializer)\nasync def book_api(request: Request):\n if request.method == 'POST':\n ...\n\n elif request.method == 'GET':\n books: list[Book] = Book.find()\n return Response(data=books, status_code=status.HTTP_200_OK)\n\n return Response(status_code=status.HTTP_501_NOT_IMPLEMENTED)\n
Panther validate input with input_model
, only in POST
, PUT
, PATCH
methods.
Assume we don't want to return field author
in response:
Create new serializer in app/serializers.py
:
from pydantic import BaseModel\n\n\nclass BookOutputSerializer(BaseModel):\n name: str\n pages_count: int\n
Add the BookOutputSerializer
as output_model
to your API()
from panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.serializers import BookSerializer, BookOutputSerializer\nfrom app.models import Book\n\n\n@API(input_model=BookSerializer, output_model=BookOutputSerializer)\nasync def book_api(request: Request):\n if request.method == 'POST':\n ...\n\n elif request.method == 'GET':\n books: list[Book] = Book.find()\n return Response(data=books, status_code=status.HTTP_200_OK)\n\n return Response(status_code=status.HTTP_501_NOT_IMPLEMENTED)\n
Panther use the output_model
, in all methods.
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\n\nfrom panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.serializers import BookSerializer, BookOutputSerializer\nfrom app.models import Book\n\n\n@API(input_model=BookSerializer, output_model=BookOutputSerializer, cache=True, cache_exp_time=timedelta(seconds=10))\nasync def book_api(request: Request):\n if request.method == 'POST':\n ...\n\n elif request.method == 'GET':\n books: list[Book] = Book.find()\n return Response(data=books, status_code=status.HTTP_200_OK)\n\n return Response(status_code=status.HTTP_501_NOT_IMPLEMENTED)\n
Panther is going to use the DEFAULT_CACHE_EXP
from core/configs.py
if cache_exp_time
has not been set.
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\n\nfrom panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\nfrom panther.throttling import Throttling\n\nfrom app.serializers import BookSerializer, BookOutputSerializer\nfrom app.models import Book\n\n\n@API(\n input_model=BookSerializer, \n output_model=BookOutputSerializer, \n cache=True, \n cache_exp_time=timedelta(seconds=10),\n throttling=Throttling(rate=10, duration=timedelta(minutes=1))\n)\nasync def book_api(request: Request):\n if request.method == 'POST':\n ...\n\n elif request.method == 'GET':\n books: list[Book] = Book.find()\n return Response(data=books, status_code=status.HTTP_200_OK)\n\n return Response(status_code=status.HTTP_501_NOT_IMPLEMENTED)\n
"},{"location":"function_first_crud/#api-retrieve-a-book","title":"API - Retrieve a Book","text":"For retrieve
, update
and delete
API, we are going to
Create another api named single_book_api
in app/apis.py
:
from panther.app import API\nfrom panther.request import Request\n\n\n@API()\nasync def single_book_api(request: Request):\n ...\n
Add it in app/urls.py
:
from app.apis import book_api, single_book_api\n\n\nurls = {\n 'book/': book_api,\n 'book/<book_id>/': single_book_api,\n}\n
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
Complete the api:
from panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.models import Book\n\n\n@API()\nasync def single_book_api(request: Request, book_id: int):\n if request.method == 'GET':\n if book := Book.find_one(id=book_id):\n return Response(data=book, status_code=status.HTTP_200_OK)\n else:\n return Response(status_code=status.HTTP_404_NOT_FOUND)\n
We can update in several ways:
Update a document
from panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.models import Book\nfrom app.serializers import BookSerializer\n\n\n@API(input_model=BookSerializer)\nasync def single_book_api(request: Request, book_id: int):\n body: BookSerializer = request.data\n if request.method == 'GET':\n ...\n elif request.method == 'PUT':\n book: Book = Book.find_one(id=book_id)\n book.update(\n name=body.name, \n author=body.author, \n pages_count=body.pages_count\n )\n return Response(status_code=status.HTTP_202_ACCEPTED)\n
Update with update_one
query
from panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.models import Book\nfrom app.serializers import BookSerializer\n\n\n@API(input_model=BookSerializer)\nasync def single_book_api(request: Request, book_id: int):\n if request.method == 'GET':\n ...\n elif request.method == 'PUT':\n is_updated: bool = Book.update_one({'id': book_id}, request.data.dict())\n data = {'is_updated': is_updated}\n return Response(data=data, status_code=status.HTTP_202_ACCEPTED)\n
Update with update_many
query
from panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.models import Book\nfrom app.serializers import BookSerializer\n\n\n@API(input_model=BookSerializer)\nasync def single_book_api(request: Request, book_id: int):\n if request.method == 'GET':\n ...\n elif request.method == 'PUT':\n updated_count: int = Book.update_many({'id': book_id}, request.data.dict())\n data = {'updated_count': updated_count}\n return Response(data=data, status_code=status.HTTP_202_ACCEPTED)\n
You can handle the PATCH the same way as PUT
We can delete in several ways too:
Delete a document
from panther import status\n from panther.app import API\n from panther.request import Request\n from panther.response import Response\n\n from app.models import Book\n\n\n @API()\n async def single_book_api(request: Request, book_id: int):\n if request.method == 'GET':\n ...\n elif request.method == 'PUT':\n ...\n elif request.method == 'DELETE':\n is_deleted: bool = Book.delete_one(id=book_id)\n if is_deleted:\n return Response(status_code=status.HTTP_204_NO_CONTENT)\n else:\n return Response(status_code=status.HTTP_400_BAD_REQUEST)\n
2. Delete with delete_one
query from panther import status\n from panther.app import API\n from panther.request import Request\n from panther.response import Response\n\n from app.models import Book\n\n\n @API()\n async def single_book_api(request: Request, book_id: int):\n if request.method == 'GET':\n ...\n elif request.method == 'PUT':\n ...\n elif request.method == 'DELETE':\n is_deleted: bool = Book.delete_one(id=book_id)\n return Response(status_code=status.HTTP_204_NO_CONTENT)\n
Delete with delete_many
query
from panther import status\n from panther.app import API\n from panther.request import Request\n from panther.response import Response\n\n from app.models import Book\n\n\n @API()\n async def single_book_api(request: Request, book_id: int):\n if request.method == 'GET':\n ...\n elif request.method == 'PUT':\n ...\n elif request.method == 'DELETE':\n deleted_count: int = Book.delete_many(id=book_id)\n return Response(status_code=status.HTTP_204_NO_CONTENT)\n
Variable: LOG_QUERIES
Type: bool
Default: False
Panther has a log_query
decorator on queries that process the perf_time
of every query
Make sure it is False
on production for better performance
INFO: | 2023-03-19 20:37:27 | Query --> User.insert_one() --> 1.6 ms\n
"},{"location":"log_queries/#the-log-query-decorator-is-something-like-this","title":"The Log Query Decorator Is Something Like This","text":"def log_query(func):\n def log(*args, **kwargs):\n if config['log_queries'] is False:\n return func(*args, **kwargs)\n\n start = perf_counter()\n response = func(*args, **kwargs)\n end = perf_counter()\n class_name = ...\n query_logger.info(f'Query --> {class_name}.{func.__name__}() --> {(end - start) * 1_000:.2} ms')\n return response\n return log\n
"},{"location":"middlewares/","title":"Middlewares","text":"Variable: MIDDLEWARES
Type: list
Default: []
Panther has several built-in
middleware:
Database Middleware
Redis Middleware
And you can write your own custom middlewares too
"},{"location":"middlewares/#structure-of-middlewares","title":"Structure of middlewares","text":"MIDDLEWARES
itself is a list
of tuples
which each tuple
is like below:
(Address of Middleware Class
, kwargs as dict
)
This middleware will create a db
connection that uses in ODM
or you can use it manually from:
from panther.db.connection import db\n
We only support 2 database: PantherDB
& MongoDB
panther.middlewares.db.Middleware
kwargs:
{'url': f'pantherdb://{BASE_DIR}/{DB_NAME}.pdb'}
{'url': f'mongodb://{DB_HOST}:27017/{DB_NAME}'}
Example of PantherDB
(Built-in Local Storage
):
MIDDLEWARES = [\n ('panther.middlewares.db.Middleware', {'url': f'pantherdb://{BASE_DIR}/{DB_NAME}.pdb'}),\n]\n
MongoDB
: MIDDLEWARES = [\n ('panther.middlewares.db.Middleware', {'url': f'mongodb://{DB_HOST}:27017/{DB_NAME}'}),\n]\n
panther.middlewares.redis.Middleware
kwargs:
{'host': '127.0.0.1', 'port': 6379, ...}\n
Example
MIDDLEWARES = [\n ('panther.middlewares.redis.Middleware', {'host': '127.0.0.1', 'port': 6379}),\n]\n
Write a class
and inherit from
from panther.middlewares.base import BaseMiddleware\n
Then you can write your custom before()
and after()
methods
methods
should be async
before()
should have request
parameterafter()
should have response
parameterbefore()
and after()
are optionalmethods
can get kwargs
from their __init__
core/middlewares.py
from panther.request import Request\nfrom panther.response import Response\nfrom panther.middlewares.base import BaseMiddleware\n\n\nclass CustomMiddleware(BaseMiddleware):\n\n def __init__(self, something):\n self.something = something\n\n async def before(self, request: Request) -> Request:\n print('Before Endpoint', self.something)\n return request\n\n async def after(self, response: Response) -> Response:\n print('After Endpoint', self.something)\n return response\n
core/configs.py MIDDLEWARES = [\n ('core.middlewares.CustomMiddleware', {'something': 'hello-world'}),\n ]\n
"},{"location":"monitoring/","title":"Monitoring","text":"Variable: MONITORING
Type: bool
Default: False
Panther has a Monitoring
middleware that process the perf_time
of every request
It will create a monitoring.log
file and log the records
Then you can watch them live with: panther monitor
async def before(self, request: Request) -> Request:\n ip, port = request.client\n self.log = f'{request.method} | {request.path} | {ip}:{port}'\n self.start_time = perf_counter()\n return request\n
async def after(self, status_code: int):\n response_time = (perf_counter() - self.start_time) * 1_000\n monitoring_logger.info(f'{self.log} | {response_time: .3} ms | {status_code}')\n
"},{"location":"panther_odm/","title":"Panther ODM","text":""},{"location":"panther_odm/#find_one","title":"find_one","text":"Example:
user: User = User.find_one(id=1, name='Ali') \n\nuser: User = User.find_one({'id': 1, 'name': 'Ali'}) \n\nuser: User = User.find_one({'id': 1}, name='Ali') \n
Example:
users: list[User] = User.find(id=1, name='Ali') \n\nusers: list[User] = User.find({'id': 1, 'name': 'Ali'}) \n\nusers: list[User] = User.find({'id': 1}, name='Ali') \n
Example:
User.insert_one(id=1, name='Ali') \n\nUser.insert_one({'id': 1, 'name': 'Ali'}) \n\nUser.insert_one({'id': 1}, name='Ali') \n
Example:
user: User = User.find_one(name='Ali')\nuser.delete()\n
Example:
is_deleted: bool = User.delete_one(id=1, name='Ali')\n
Example:
deleted_count: int = User.delete_many(id=1, name='Ali')\n\ndeleted_count: int = User.delete_many({'id': 1}, name='Ali')\n\ndeleted_count: int = User.delete_many({'id': 1, 'name': 'Ali'})\n
Example:
user = User.find_one(name='Ali')\nuser.update(name='Saba')\n
dictionary
as first parameter
and pass the fields you want to update as kwargs
or another dictionary
as second parameter
Example:
is_updated: bool = User.update_one({'id': 1, 'name': 'Ali'}, name='Saba', age=26)\n\nis_updated: bool = User.update_one({'id': 1, 'name': 'Ali'}, {'name': 'Saba', 'age': 26})\n\nis_updated: bool = User.update_one({'id': 1, 'name': 'Ali'}, {'name': 'Saba'}, age=26)\n
dictionary
as first parameter
and pass the fields you want to update as kwargs
or another dictionary
as second parameter
Example:
updated_count: int = User.update_many({'name': 'Ali'}, name='Saba', age=26)\n\nupdated_count: int = User.update_many({'name': 'Ali'}, {'name': 'Saba', 'age': 26})\n\nupdated_count: int = User.update_many({'name': 'Ali'}, {'name': 'Saba'}, age=26)\n
Example:
user: User = User.last(name='Ali', age=26) \n\nuser: User = User.last({'name': 'Ali', 'age': 26}) \n\nuser: User = User.last({'name': 'Ali'}, age=26) \n
Example:
users_count: int = User.count(name='Ali')\n
Example:
user: User = User.find_or_insert(name='Ali')\n
collect_urls
and rename it to flatten_urls
Variable: THROTTLING
Type: str
In Panther, you can use Throttling
for all APIs at once in core/configs.py
or per API in its @API
decorator
The Throttling
class has 2 field rate
& duration
rate: int
duration: datetime.timedelta
It will return Too Many Request
status_code: 429
if user try to request in the duration
more than rate
And user will baned( getToo Many Request
) for duration
And keep that in mind if you have Throttling
in @API()
, the Throttling
of core/configs.py
will be ignored.
core/configs.py
from datetime import timedelta\n\nfrom panther.throttling import Throttling\n\n\n# User only can request 5 times in every minute\nTHROTTLING = Throttling(rate=5, duration=timedelta(minutes=1))\n
"},{"location":"throttling/#for-single-api-example","title":"For Single API Example:","text":"apis.py
from datetime import timedelta\n\nfrom panther.throttling import Throttling\nfrom panther.app import API\n\n\n# User only can request 5 times in every minute\nInfoThrottling = Throttling(rate=5, duration=timedelta(minutes=1))\n\n\n@API(throttling=InfoThrottling)\nasync def info_api():\n pass\n
"},{"location":"todos/","title":"TODOs","text":""},{"location":"todos/#base","title":"Base","text":"Variable: URLs
Type: str
Required: True
URLs
should point to the root of your urls
file, and in that file you should have a dict
name urls
key
of urls dict is path
& value is endpoint
or another dict
variable_name
>user/<user_id>/blog/<title>/
endpoint
should have parameters with those names tooasync def profile_api(user_id: int, title: str):
`URLs = 'configs/urls.py'`\n
from app.urls import app_urls\n\nurls = {\n 'user/': app_urls,\n}\n
app/urls.py
from app.apis import *\n\nurls = {\n 'login/': login_api,\n 'logout/': logout_api,\n 'profile/<user_id>/': profile_api,\n}\n
app/apis.py
...\n\n@API()\nasync def profile_api(user_id: int):\n return User.find_one(id=user_id)\n
Panther create a database connection depends on database middleware you are using on core/configs.py
and you can access to this connection from your models
or direct access from from panther.db.connection import db
Now we are going to create a new API which uses our default database(PantherDB
) and creating a Book
Create Book
model in app/models.py
from panther.db import Model\n\n\nclass Book(Model):\n title: str\n description: str\n pages_count: int\n
Add book
url in app/urls.py
that points to book_api()
...\nfrom app.apis import time_api, book_api\n\n\nurls = {\n '': hello_world,\n 'info/': info,\n 'time/': time_api,\n 'book/': book_api,\n}\n
Create book_api()
in app/apis.py
from panther import status\nfrom panther.app import API\nfrom panther.response import Response\n\n\n@API()\nasync def book_api():\n ...\n return Response(status_code=status.HTTP_201_CREATED) \n
Now we should use the Panther ODM to create a book, it's based on mongo queries, for creation we use insert_one
like this:
from panther import status\nfrom panther.app import API\nfrom panther.response import Response\nfrom app.models import Book\n\n\n@API()\nasync def book_api():\n Book.insert_one(\n title='Python',\n description='Python is good.',\n pages_count=10\n )\n return Response(status_code=status.HTTP_201_CREATED) \n
In next step we are going to explain more about Panther ODM
Is A Fast & Friendly Web Framework For Building Async APIs With Python 3.11+
Supported by JetBrains
"},{"location":"#why-use-panther","title":"Why Use Panther ?","text":"
$ python3 -m venv .venv
$ source .venv/bin/activate
$ .\\.venv\\Scripts\\activate
$ pip install panther
$ pip install panther[full]
$ panther create <project_name> <directory>\n
"},{"location":"#run-project","title":"Run Project","text":"Panther uses Uvicorn as ASGI (Asynchronous Server Gateway Interface)
$ panther run
"},{"location":"#monitoring-requests","title":"Monitoring Requests","text":"$ panther monitor
"},{"location":"#python-shell","title":"Python Shell","text":"Panther Uses bpython for shell
$ panther shell
"},{"location":"#example","title":"Example","text":"Then run the project:
$ cd myproject
$ panther run
or $ panther run --reload
now you can see these two urls:
http://127.0.0.1:8000/
http://127.0.0.1:8000/info/
Writing Your First CRUD: First CRUD
"},{"location":"#you-can-create-project-with","title":"You can create project with","text":"$ panther create myproject\n
"},{"location":"#or-create-it-yourself","title":"or create it yourself:","text":"core/configs.py:
URLs = 'core.urls.url_routing'\n
core/urls.py:
from app.urls import urls as app_urls\n\nurl_routing = {\n '/': app_urls,\n}\n
app/urls.py:
from app.apis import hello_world, info\n\nurls = {\n '': hello_world,\n 'info/': info,\n}\n
app/apis.py:
from datetime import datetime, timedelta\n\nfrom panther.app import API\nfrom panther.configs import config\nfrom panther import version, status\nfrom panther.request import Request\nfrom panther.response import Response\nfrom panther.throttling import Throttling\n\n\nInfoThrottling = Throttling(rate=5, duration=timedelta(minutes=1))\n\n@API()\nasync def hello_world():\n return {'detail': 'Hello World'}\n\n\n@API(cache=True, throttling=InfoThrottling)\nasync def info(request: Request):\n data = {\n 'version': version(),\n 'datetime_now': datetime.now().isoformat(),\n 'user_agent': request.headers.user_agent,\n 'db_engine': config['db_engine'],\n }\n return Response(data=data, status_code=status.HTTP_202_ACCEPTED)\n
"},{"location":"authentications/","title":"Authentications","text":"Variable: AUTHENTICATION
Type: str
Default: None
You can set your Authentication class in core/configs.py
, then Panther will use this class for authentication in every API
, if you set auth=True
in @API()
, and put the user
in request.user
or raise HTTP_401_UNAUTHORIZED
We already have one built-in authentication class which is used JWT
for authentication.
You can write your own authentication class too (we are going to discuss it)
"},{"location":"authentications/#jwtauthentication","title":"JWTAuthentication","text":"This class will
token
from Authorization
header of request with keyword of Bearer
decode
it user
in USER_MODEL
you have already setJWTAuthentication
is going to use panther.db.models.BaseUser
if you didn't set the USER_MODEL
in your core/configs.py
You can customize these 3 variables for JWTAuthentication
in your core/configs.py
as JWTConfig
like below (JWTConfig
is optional):
...\nfrom datetime import timedelta\nfrom panther.utils import load_env \nfrom pathlib import Path\n\nBASE_DIR = Path(__name__).resolve().parent \nenv = load_env(BASE_DIR / '.env')\n\nSECRET_KEY = env['SECRET_KEY']\n\nJWTConfig = { \n 'key': SECRET_KEY, \n 'algorithm': 'HS256', \n 'life_time': timedelta(days=2), \n}\n
key \u2003\u2003\u2003\u2003--> default is SECRET_KEY
algorithm \u2003 --> default is HS256
life_time\u2003\u2003--> default is timedelta(days=1)
Create a class and inherits it from panther.authentications.BaseAuthentication
Implement authentication(cls, request: Request)
method
request.headers.authorization
or ...USER_MODEL
panther.exceptions.AuthenticationException
Address it in core/configs.py
AUTHENTICATION = 'project_name.core.authentications.CustomAuthentication'
You can look at the source code of JWTAuthentication for
"},{"location":"benchmarks/","title":"Benchmarks","text":""},{"location":"benchmarks/#benchmark","title":"Benchmark","text":"Framework Throughput Request Handled Max Latencies Sanic 23,326 233,842 268.8ms Panther 14,719 147,595 113.1ms FastAPI 14,573 146,467 155.1ms Tornado 4,969 50.585 426.5ms Flask 3,555 36,396 1.2s Django 2,188 22,814 526.3ms Bottle 1,226 39,650 30.0s Pyramid 1,023 30,912 30.0s Cherrypy 639 24,944 30.0s"},{"location":"benchmarks/#all-tests-happen-in","title":"All tests happen in","text":"[Test Source Code]
"},{"location":"benchmarks/#panther","title":"Panther","text":"[Test Source Code]
"},{"location":"benchmarks/#fastapi","title":"FastAPI","text":"[Test Source Code]
"},{"location":"benchmarks/#tornado","title":"Tornado","text":"[Test Source Code]
"},{"location":"benchmarks/#flask","title":"Flask","text":"[Test Source Code]
"},{"location":"benchmarks/#django","title":"Django","text":"[Test Source Code]
"},{"location":"benchmarks/#bottle","title":"Bottle","text":"[Test Source Code]
"},{"location":"benchmarks/#pyramid","title":"Pyramid","text":"[Test Source Code]
"},{"location":"benchmarks/#cherrypy","title":"Cherrypy","text":"[Test Source Code]
"},{"location":"class_first_crud/","title":"Class Base","text":"We assume you could run the project with Introduction
Now let's write custom API Create
, Retrieve
, Update
and Delete
for a Book
:
Create a model named Book
in app/models.py
:
from panther.db import Model\n\n\nclass Book(Model):\n name: str\n author: str\n pages_count: int\n
"},{"location":"class_first_crud/#create-api-class","title":"Create API Class","text":"Create the BookAPI()
in app/apis.py
:
from panther.app import GenericAPI\n\n\nclass BookAPI(GenericAPI):\n ... \n
We are going to complete it later ...
"},{"location":"class_first_crud/#update-urls","title":"Update URLs","text":"Add the BookAPI
in app/urls.py
:
from app.apis import BookAPI\n\n\nurls = {\n 'book/': BookAPI,\n}\n
We assume that the urls
in core/urls.py
pointing to app/urls.py
, like below:
from app.urls import urls as app_urls\n\n\nurls = {\n '/': app_urls,\n}\n
"},{"location":"class_first_crud/#add-database-middleware","title":"Add Database Middleware","text":"Add one database middleware in core/configs.py
MIDDLEWARES
, we are going to add pantherdb
PantherDB is a Simple, FileBase and Document Oriented database:
...\n\nMIDDLEWARES = [\n ('panther.middlewares.db.Middleware', {'url': f'pantherdb://{BASE_DIR}/{DB_NAME}.pdb'}),\n]\n
"},{"location":"class_first_crud/#apis","title":"APIs","text":""},{"location":"class_first_crud/#api-create-a-book","title":"API - Create a Book","text":"Now we are going to create a book on post
request, We need to:
Declare post
method in BookAPI
:
from panther.app import GenericAPI\n\n\nclass BookAPI(GenericAPI):\n\n def post(self):\n ...\n
Declare request: Request
in BookAPI.post()
function:
from panther.app import GenericAPI\nfrom panther.request import Request\n\n\nclass BookAPI(GenericAPI):\n\n def post(self, request: Request):\n ...\n
Create serializer in app/serializers.py
, we used pydantic
for the validation
of request.data
:
from pydantic import BaseModel\n\n\nclass BookSerializer(BaseModel):\n name: str\n author: str\n pages_count: int\n
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\nfrom panther.request import Request\n\nfrom app.serializers import BookSerializer\n\n\nclass BookAPI(GenericAPI):\n input_model = BookSerializer\n\n def post(self, request: Request):\n ...\n
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\nfrom panther.request import Request\n\nfrom app.serializers import BookSerializer\n\n\nclass BookAPI(GenericAPI):\n input_model = BookSerializer\n\n def post(self, request: Request):\n body: BookSerializer = request.data\n ...\n
Now we have access to the validated data, and we can create our first book:
from panther.app import GenericAPI\nfrom panther.request import Request\n\nfrom app.serializers import BookSerializer\nfrom app.models import Book\n\n\nclass BookAPI(GenericAPI):\n input_model = BookSerializer\n\n def post(self, request: Request):\n body: BookSerializer = request.data\n Book.insert_one(\n name=body.name,\n author=body.author,\n pages_count=body.pages_count,\n )\n ...\n
And finally we return 201 Created
status_code as response of post
:
from panther import status\nfrom panther.app import GenericAPI\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.serializers import BookSerializer\nfrom app.models import Book\n\n\nclass BookAPI(GenericAPI):\n input_model = BookSerializer\n\n def post(self, request: Request):\n body: BookSerializer = request.data\n book = Book.insert_one(\n name=body.name,\n author=body.author,\n pages_count=body.pages_count,\n )\n return Response(data=book, status_code=status.HTTP_201_CREATED)\n
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.
We just need to add another method for GET
method and return the lists of books:
from panther import status\nfrom panther.app import GenericAPI\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.serializers import BookSerializer\nfrom app.models import Book\n\n\nclass BookAPI(GenericAPI):\n input_model = BookSerializer\n\n def post(self, request: Request):\n ...\n\n def get(self):\n books: list[Book] = Book.find()\n return Response(data=books, status_code=status.HTTP_200_OK)\n
Panther validate input with input_model
, only in POST
, PUT
, PATCH
methods.
Assume we don't want to return field author
in response:
Create new serializer in app/serializers.py
:
from pydantic import BaseModel\n\n\nclass BookOutputSerializer(BaseModel):\n name: str\n pages_count: int\n
Add the BookOutputSerializer
as output_model
to your class
from panther import status\nfrom panther.app import GenericAPI\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.serializers import BookSerializer, BookOutputSerializer\nfrom app.models import Book\n\n\nclass BookAPI(GenericAPI):\n input_model = BookSerializer\n output_model = BookOutputSerializer\n\n def post(self, request: Request):\n ...\n\n def get(self):\n books: list[Book] = Book.find()\n return Response(data=books, status_code=status.HTTP_200_OK)\n
Panther use the output_model
, in all methods.
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\n\nfrom panther import status\nfrom panther.app import GenericAPI\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.serializers import BookSerializer, BookOutputSerializer\nfrom app.models import Book\n\n\nclass BookAPI(GenericAPI):\n input_model = BookSerializer\n output_model = BookOutputSerializer\n cache = True\n cache_exp_time = timedelta(seconds=10)\n\n def post(self, request: Request):\n ...\n\n def get(self):\n books: list[Book] = Book.find()\n return Response(data=books, status_code=status.HTTP_200_OK)\n
Panther is going to use the DEFAULT_CACHE_EXP
from core/configs.py
if cache_exp_time
has not been set.
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\n\nfrom panther import status\nfrom panther.app import GenericAPI\nfrom panther.request import Request\nfrom panther.response import Response\nfrom panther.throttling import Throttling\n\nfrom app.serializers import BookSerializer, BookOutputSerializer\nfrom app.models import Book\n\n\nclass BookAPI(GenericAPI):\n input_model = BookSerializer\n output_model = BookOutputSerializer\n cache = True\n cache_exp_time = timedelta(seconds=10)\n throttling = Throttling(rate=10, duration=timedelta(minutes=1))\n\n def post(self, request: Request):\n ...\n\n def get(self):\n books: list[Book] = Book.find()\n return Response(data=books, status_code=status.HTTP_200_OK)\n
"},{"location":"class_first_crud/#api-retrieve-a-book","title":"API - Retrieve a Book","text":"For retrieve
, update
and delete
API, we are going to
Create another class named SingleBookAPI
in app/apis.py
:
from panther.app import GenericAPI\n\n\nclass SingleBookAPI(GenericAPI):\n ...\n
Add it in app/urls.py
:
from app.apis import BookAPI, SingleBookAPI\n\n\nurls = {\n 'book/': BookAPI,\n 'book/<book_id>/': SingleBookAPI,\n}\n
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
Complete the api:
from panther import status\nfrom panther.app import GenericAPI\nfrom panther.response import Response\n\nfrom app.models import Book\n\n\nclass SingleBookAPI(GenericAPI):\n\n def get(self, book_id: int):\n if book := Book.find_one(id=book_id):\n return Response(data=book, status_code=status.HTTP_200_OK)\n else:\n return Response(status_code=status.HTTP_404_NOT_FOUND)\n
We can update in several ways:
Update a document
from panther import status\nfrom panther.app import GenericAPI\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.models import Book\nfrom app.serializers import BookSerializer\n\n\nclass SingleBookAPI(GenericAPI):\n input_model = BookSerializer\n\n def get(self, book_id: int):\n ...\n\n def put(self, request: Request, book_id: int):\n body: BookSerializer = request.data\n\n book: Book = Book.find_one(id=book_id)\n book.update(\n name=body.name, \n author=body.author, \n pages_count=body.pages_count\n )\n return Response(status_code=status.HTTP_202_ACCEPTED)\n
Update with update_one
query
from panther import status\nfrom panther.app import GenericAPI\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.models import Book\nfrom app.serializers import BookSerializer\n\n\nclass SingleBookAPI(GenericAPI):\n input_model = BookSerializer\n\n def get(self, book_id: int):\n ...\n\n def put(self, request: Request, book_id: int):\n is_updated: bool = Book.update_one({'id': book_id}, request.data.dict())\n data = {'is_updated': is_updated}\n return Response(data=data, status_code=status.HTTP_202_ACCEPTED)\n
Update with update_many
query
from panther import status\nfrom panther.app import GenericAPI\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.models import Book\nfrom app.serializers import BookSerializer\n\n\nclass SingleBookAPI(GenericAPI):\n input_model = BookSerializer\n\n def get(self, book_id: int):\n ...\n\n def put(self, request: Request, book_id: int):\n updated_count: int = Book.update_many({'id': book_id}, request.data.dict())\n data = {'updated_count': updated_count}\n return Response(data=data, status_code=status.HTTP_202_ACCEPTED)\n
You can handle the PATCH the same way as PUT
We can delete in several ways too:
Delete a document
from panther import status\n from panther.app import GenericAPI\n from panther.request import Request\n from panther.response import Response\n\n from app.models import Book\n\n class SingleBookAPI(GenericAPI):\n input_model = BookSerializer\n\n def get(self, book_id: int):\n ...\n\n def put(self, request: Request, book_id: int):\n ...\n\n def delete(self, book_id: int):\n is_deleted: bool = Book.delete_one(id=book_id)\n if is_deleted:\n return Response(status_code=status.HTTP_204_NO_CONTENT)\n else:\n return Response(status_code=status.HTTP_400_BAD_REQUEST)\n
2. Delete with delete_one
query from panther import status\n from panther.app import GenericAPI\n from panther.request import Request\n from panther.response import Response\n\n from app.models import Book\n\n\n class SingleBookAPI(GenericAPI):\n input_model = BookSerializer\n\n def get(self, book_id: int):\n ...\n\n def put(self, request: Request, book_id: int):\n ...\n\n def delete(self, book_id: int):\n is_deleted: bool = Book.delete_one(id=book_id)\n return Response(status_code=status.HTTP_204_NO_CONTENT)\n
Delete with delete_many
query
from panther import status\n from panther.app import GenericAPI\n from panther.request import Request\n from panther.response import Response\n\n from app.models import Book\n\n\n class SingleBookAPI(GenericAPI):\n input_model = BookSerializer\n\n def get(self, book_id: int):\n ...\n\n def put(self, request: Request, book_id: int):\n ...\n\n def delete(self, book_id: int):\n deleted_count: int = Book.delete_many(id=book_id)\n return Response(status_code=status.HTTP_204_NO_CONTENT)\n
Panther stores all the configs in the core/configs.py
Type: bool
(Default: False
)
It should be True
if you want to use panther monitor
command and see the monitoring logs
If True
it will:
logs/monitoring.log
Type: bool
(Default: False
)
If True
it will:
logs/query.log
Type: list
(Default: [ ]
)
List of middlewares you want to use
"},{"location":"configs/#authentication","title":"AUTHENTICATION","text":"Type: str | None
(Default: None
)
Every request goes through authentication()
method of this class
Example: AUTHENTICATION = 'panther.authentications.JWTAuthentication'
Type: str
(Required)
It should be the address of your urls
dict
Example: URLS = 'configs.urls.url_routing'
Type: timedelta| None
(Default: None
)
We use it as default cache_exp_time
you can overwrite it in your @API
too
It is used when you set cache=True
in @API
decorator
Example: DEFAULT_CACHE_EXP = timedelta(seconds=10)
Type: Throttling | None
(Default: None
)
We use it as default throttling
you can overwrite it in your @API
too
Example: THROTTLING = Throttling(rate=10, duration=timedelta(seconds=10))
Type: str | None
(Default: 'panther.db.models.BaseUser'
)
It is used for authentication
Example: USER_MODEL = 'panther.db.models.User'
Type: dict | None
(Default: JWTConfig = {'key': SECRET_KEY}
)
We use it when you set panther.authentications.JWTAuthentication
as AUTHENTICATION
We assume you could run the project with Introduction
Now let's write custom API Create
, Retrieve
, Update
and Delete
for a Book
:
Create a model named Book
in app/models.py
:
from panther.db import Model\n\n\nclass Book(Model):\n name: str\n author: str\n pages_count: int\n
"},{"location":"function_first_crud/#create-api-function","title":"Create API Function","text":"Create the book_api()
in app/apis.py
:
from panther.app import API\n\n\n@API()\nasync def book_api():\n ... \n
We are going to complete it later ...
"},{"location":"function_first_crud/#update-urls","title":"Update URLs","text":"Add the book_api
in app/urls.py
:
from app.apis import book_api\n\n\nurls = {\n 'book/': book_api,\n}\n
We assume that the urls
in core/urls.py
pointing to app/urls.py
, like below:
from app.urls import urls as app_urls\n\n\nurls = {\n '/': app_urls,\n}\n
"},{"location":"function_first_crud/#add-database-middleware","title":"Add Database Middleware","text":"Add one database middleware in core/configs.py
MIDDLEWARES
, we are going to add pantherdb
PantherDB is a Simple, FileBase and Document Oriented database:
...\n\nMIDDLEWARES = [\n ('panther.middlewares.db.Middleware', {'url': f'pantherdb://{BASE_DIR}/{DB_NAME}.pdb'}),\n]\n
"},{"location":"function_first_crud/#apis","title":"APIs","text":""},{"location":"function_first_crud/#api-create-a-book","title":"API - Create a Book","text":"Now we are going to create a book on post
request, We need to:
Declare request: Request
in book_api
function:
from panther.app import API\nfrom panther.request import Request\n\n\n@API()\nasync def book_api(request: Request):\n ...\n
Create serializer in app/serializers.py
, we used pydantic
for the validation
of request.data
:
from pydantic import BaseModel\n\n\nclass BookSerializer(BaseModel):\n name: str\n author: str\n pages_count: int\n
Pass the created serializer to our book_api
as input_model
so the incoming data will be validated and cleaned automatically:
from panther.app import API\nfrom panther.request import Request\n\nfrom app.serializers import BookSerializer\n\n\n@API(input_model=BookSerializer)\nasync def book_api(request: Request):\n ...\n
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 API\nfrom panther.request import Request\n\nfrom app.serializers import BookSerializer\n\n\n@API(input_model=BookSerializer)\nasync def book_api(request: Request):\n body: BookSerializer = request.data\n ...\n
Now we have access to the validated data, and we can create our first book:
from panther.app import API\nfrom panther.request import Request\n\nfrom app.serializers import BookSerializer\nfrom app.models import Book\n\n\n@API(input_model=BookSerializer)\nasync def book_api(request: Request):\n body: BookSerializer = request.data\n\n Book.insert_one(\n name=body.name,\n author=body.author,\n pages_count=body.pages_count,\n )\n ...\n
But we only want this happens in post
requests, so we add this condition
:
from panther.app import API\nfrom panther.request import Request\n\nfrom app.serializers import BookSerializer\nfrom app.models import Book\n\n\n@API(input_model=BookSerializer)\nasync def book_api(request: Request):\n if request.method == 'POST':\n body: BookSerializer = request.data\n\n Book.create(\n name=body.name,\n author=body.author,\n pages_count=body.pages_count,\n )\n ...\n
And finally we return 201 Created
status_code as response of post
and 501 Not Implemented
for other methods:
from panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.serializers import BookSerializer\nfrom app.models import Book\n\n\n@API(input_model=BookSerializer)\nasync def book_api(request: Request):\n if request.method == 'POST':\n body: BookSerializer = request.data\n\n book: Book = Book.create(\n name=body.name,\n author=body.author,\n pages_count=body.pages_count,\n )\n return Response(data=book, status_code=status.HTTP_201_CREATED)\n\n return Response(status_code=status.HTTP_501_NOT_IMPLEMENTED)\n
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.
We just need to add another condition on GET
methods and return the lists of books:
from panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.serializers import BookSerializer\nfrom app.models import Book\n\n\n@API(input_model=BookSerializer)\nasync def book_api(request: Request):\n if request.method == 'POST':\n ...\n\n elif request.method == 'GET':\n books: list[Book] = Book.find()\n return Response(data=books, status_code=status.HTTP_200_OK)\n\n return Response(status_code=status.HTTP_501_NOT_IMPLEMENTED)\n
Panther validate input with input_model
, only in POST
, PUT
, PATCH
methods.
Assume we don't want to return field author
in response:
Create new serializer in app/serializers.py
:
from pydantic import BaseModel\n\n\nclass BookOutputSerializer(BaseModel):\n name: str\n pages_count: int\n
Add the BookOutputSerializer
as output_model
to your API()
from panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.serializers import BookSerializer, BookOutputSerializer\nfrom app.models import Book\n\n\n@API(input_model=BookSerializer, output_model=BookOutputSerializer)\nasync def book_api(request: Request):\n if request.method == 'POST':\n ...\n\n elif request.method == 'GET':\n books: list[Book] = Book.find()\n return Response(data=books, status_code=status.HTTP_200_OK)\n\n return Response(status_code=status.HTTP_501_NOT_IMPLEMENTED)\n
Panther use the output_model
, in all methods.
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\n\nfrom panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.serializers import BookSerializer, BookOutputSerializer\nfrom app.models import Book\n\n\n@API(input_model=BookSerializer, output_model=BookOutputSerializer, cache=True, cache_exp_time=timedelta(seconds=10))\nasync def book_api(request: Request):\n if request.method == 'POST':\n ...\n\n elif request.method == 'GET':\n books: list[Book] = Book.find()\n return Response(data=books, status_code=status.HTTP_200_OK)\n\n return Response(status_code=status.HTTP_501_NOT_IMPLEMENTED)\n
Panther is going to use the DEFAULT_CACHE_EXP
from core/configs.py
if cache_exp_time
has not been set.
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\n\nfrom panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\nfrom panther.throttling import Throttling\n\nfrom app.serializers import BookSerializer, BookOutputSerializer\nfrom app.models import Book\n\n\n@API(\n input_model=BookSerializer, \n output_model=BookOutputSerializer, \n cache=True, \n cache_exp_time=timedelta(seconds=10),\n throttling=Throttling(rate=10, duration=timedelta(minutes=1))\n)\nasync def book_api(request: Request):\n if request.method == 'POST':\n ...\n\n elif request.method == 'GET':\n books: list[Book] = Book.find()\n return Response(data=books, status_code=status.HTTP_200_OK)\n\n return Response(status_code=status.HTTP_501_NOT_IMPLEMENTED)\n
"},{"location":"function_first_crud/#api-retrieve-a-book","title":"API - Retrieve a Book","text":"For retrieve
, update
and delete
API, we are going to
Create another api named single_book_api
in app/apis.py
:
from panther.app import API\nfrom panther.request import Request\n\n\n@API()\nasync def single_book_api(request: Request):\n ...\n
Add it in app/urls.py
:
from app.apis import book_api, single_book_api\n\n\nurls = {\n 'book/': book_api,\n 'book/<book_id>/': single_book_api,\n}\n
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
Complete the api:
from panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.models import Book\n\n\n@API()\nasync def single_book_api(request: Request, book_id: int):\n if request.method == 'GET':\n if book := Book.find_one(id=book_id):\n return Response(data=book, status_code=status.HTTP_200_OK)\n else:\n return Response(status_code=status.HTTP_404_NOT_FOUND)\n
We can update in several ways:
Update a document
from panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.models import Book\nfrom app.serializers import BookSerializer\n\n\n@API(input_model=BookSerializer)\nasync def single_book_api(request: Request, book_id: int):\n body: BookSerializer = request.data\n if request.method == 'GET':\n ...\n elif request.method == 'PUT':\n book: Book = Book.find_one(id=book_id)\n book.update(\n name=body.name, \n author=body.author, \n pages_count=body.pages_count\n )\n return Response(status_code=status.HTTP_202_ACCEPTED)\n
Update with update_one
query
from panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.models import Book\nfrom app.serializers import BookSerializer\n\n\n@API(input_model=BookSerializer)\nasync def single_book_api(request: Request, book_id: int):\n if request.method == 'GET':\n ...\n elif request.method == 'PUT':\n is_updated: bool = Book.update_one({'id': book_id}, request.data.dict())\n data = {'is_updated': is_updated}\n return Response(data=data, status_code=status.HTTP_202_ACCEPTED)\n
Update with update_many
query
from panther import status\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\n\nfrom app.models import Book\nfrom app.serializers import BookSerializer\n\n\n@API(input_model=BookSerializer)\nasync def single_book_api(request: Request, book_id: int):\n if request.method == 'GET':\n ...\n elif request.method == 'PUT':\n updated_count: int = Book.update_many({'id': book_id}, request.data.dict())\n data = {'updated_count': updated_count}\n return Response(data=data, status_code=status.HTTP_202_ACCEPTED)\n
You can handle the PATCH the same way as PUT
We can delete in several ways too:
Delete a document
from panther import status\n from panther.app import API\n from panther.request import Request\n from panther.response import Response\n\n from app.models import Book\n\n\n @API()\n async def single_book_api(request: Request, book_id: int):\n if request.method == 'GET':\n ...\n elif request.method == 'PUT':\n ...\n elif request.method == 'DELETE':\n is_deleted: bool = Book.delete_one(id=book_id)\n if is_deleted:\n return Response(status_code=status.HTTP_204_NO_CONTENT)\n else:\n return Response(status_code=status.HTTP_400_BAD_REQUEST)\n
2. Delete with delete_one
query from panther import status\n from panther.app import API\n from panther.request import Request\n from panther.response import Response\n\n from app.models import Book\n\n\n @API()\n async def single_book_api(request: Request, book_id: int):\n if request.method == 'GET':\n ...\n elif request.method == 'PUT':\n ...\n elif request.method == 'DELETE':\n is_deleted: bool = Book.delete_one(id=book_id)\n return Response(status_code=status.HTTP_204_NO_CONTENT)\n
Delete with delete_many
query
from panther import status\n from panther.app import API\n from panther.request import Request\n from panther.response import Response\n\n from app.models import Book\n\n\n @API()\n async def single_book_api(request: Request, book_id: int):\n if request.method == 'GET':\n ...\n elif request.method == 'PUT':\n ...\n elif request.method == 'DELETE':\n deleted_count: int = Book.delete_many(id=book_id)\n return Response(status_code=status.HTTP_204_NO_CONTENT)\n
Variable: LOG_QUERIES
Type: bool
Default: False
Panther has a log_query
decorator on queries that process the perf_time
of every query
Make sure it is False
on production for better performance
INFO: | 2023-03-19 20:37:27 | Query --> User.insert_one() --> 1.6 ms\n
"},{"location":"log_queries/#the-log-query-decorator-is-something-like-this","title":"The Log Query Decorator Is Something Like This","text":"def log_query(func):\n def log(*args, **kwargs):\n if config['log_queries'] is False:\n return func(*args, **kwargs)\n\n start = perf_counter()\n response = func(*args, **kwargs)\n end = perf_counter()\n class_name = ...\n query_logger.info(f'Query --> {class_name}.{func.__name__}() --> {(end - start) * 1_000:.2} ms')\n return response\n return log\n
"},{"location":"middlewares/","title":"Middlewares","text":"Variable: MIDDLEWARES
Type: list
Default: []
Panther has several built-in
middleware:
Database Middleware
Redis Middleware
And you can write your own custom middlewares too
"},{"location":"middlewares/#structure-of-middlewares","title":"Structure of middlewares","text":"MIDDLEWARES
itself is a list
of tuples
which each tuple
is like below:
(Address of Middleware Class
, kwargs as dict
)
This middleware will create a db
connection that uses in ODM
or you can use it manually from:
from panther.db.connection import db\n
We only support 2 database: PantherDB
& MongoDB
panther.middlewares.db.Middleware
kwargs:
{'url': f'pantherdb://{BASE_DIR}/{DB_NAME}.pdb'}
{'url': f'mongodb://{DB_HOST}:27017/{DB_NAME}'}
Example of PantherDB
(Built-in Local Storage
):
MIDDLEWARES = [\n ('panther.middlewares.db.Middleware', {'url': f'pantherdb://{BASE_DIR}/{DB_NAME}.pdb'}),\n]\n
MongoDB
: MIDDLEWARES = [\n ('panther.middlewares.db.Middleware', {'url': f'mongodb://{DB_HOST}:27017/{DB_NAME}'}),\n]\n
panther.middlewares.redis.Middleware
kwargs:
{'host': '127.0.0.1', 'port': 6379, ...}\n
Example
MIDDLEWARES = [\n ('panther.middlewares.redis.Middleware', {'host': '127.0.0.1', 'port': 6379}),\n]\n
Write a class
and inherit from
from panther.middlewares.base import BaseMiddleware\n
Then you can write your custom before()
and after()
methods
methods
should be async
before()
should have request
parameterafter()
should have response
parameterbefore()
and after()
are optionalmethods
can get kwargs
from their __init__
core/middlewares.py
from panther.request import Request\nfrom panther.response import Response\nfrom panther.middlewares.base import BaseMiddleware\n\n\nclass CustomMiddleware(BaseMiddleware):\n\n def __init__(self, something):\n self.something = something\n\n async def before(self, request: Request) -> Request:\n print('Before Endpoint', self.something)\n return request\n\n async def after(self, response: Response) -> Response:\n print('After Endpoint', self.something)\n return response\n
core/configs.py MIDDLEWARES = [\n ('core.middlewares.CustomMiddleware', {'something': 'hello-world'}),\n ]\n
"},{"location":"monitoring/","title":"Monitoring","text":"Variable: MONITORING
Type: bool
Default: False
Panther has a Monitoring
middleware that process the perf_time
of every request
It will create a monitoring.log
file and log the records
Then you can watch them live with: panther monitor
async def before(self, request: Request) -> Request:\n ip, port = request.client\n self.log = f'{request.method} | {request.path} | {ip}:{port}'\n self.start_time = perf_counter()\n return request\n
async def after(self, status_code: int):\n response_time = (perf_counter() - self.start_time) * 1_000\n monitoring_logger.info(f'{self.log} | {response_time: .3} ms | {status_code}')\n
"},{"location":"panther_odm/","title":"Panther ODM","text":""},{"location":"panther_odm/#find_one","title":"find_one","text":"Example:
user: User = User.find_one(id=1, name='Ali') \n\nuser: User = User.find_one({'id': 1, 'name': 'Ali'}) \n\nuser: User = User.find_one({'id': 1}, name='Ali') \n
Example:
users: list[User] = User.find(id=1, name='Ali') \n\nusers: list[User] = User.find({'id': 1, 'name': 'Ali'}) \n\nusers: list[User] = User.find({'id': 1}, name='Ali') \n
Example:
User.insert_one(id=1, name='Ali') \n\nUser.insert_one({'id': 1, 'name': 'Ali'}) \n\nUser.insert_one({'id': 1}, name='Ali') \n
Example:
user: User = User.find_one(name='Ali')\nuser.delete()\n
Example:
is_deleted: bool = User.delete_one(id=1, name='Ali')\n
Example:
deleted_count: int = User.delete_many(id=1, name='Ali')\n\ndeleted_count: int = User.delete_many({'id': 1}, name='Ali')\n\ndeleted_count: int = User.delete_many({'id': 1, 'name': 'Ali'})\n
Example:
user = User.find_one(name='Ali')\nuser.update(name='Saba')\n
dictionary
as first parameter
and pass the fields you want to update as kwargs
or another dictionary
as second parameter
Example:
is_updated: bool = User.update_one({'id': 1, 'name': 'Ali'}, name='Saba', age=26)\n\nis_updated: bool = User.update_one({'id': 1, 'name': 'Ali'}, {'name': 'Saba', 'age': 26})\n\nis_updated: bool = User.update_one({'id': 1, 'name': 'Ali'}, {'name': 'Saba'}, age=26)\n
dictionary
as first parameter
and pass the fields you want to update as kwargs
or another dictionary
as second parameter
Example:
updated_count: int = User.update_many({'name': 'Ali'}, name='Saba', age=26)\n\nupdated_count: int = User.update_many({'name': 'Ali'}, {'name': 'Saba', 'age': 26})\n\nupdated_count: int = User.update_many({'name': 'Ali'}, {'name': 'Saba'}, age=26)\n
Example:
user: User = User.last(name='Ali', age=26) \n\nuser: User = User.last({'name': 'Ali', 'age': 26}) \n\nuser: User = User.last({'name': 'Ali'}, age=26) \n
Example:
users_count: int = User.count(name='Ali')\n
Example:
user: User = User.find_or_insert(name='Ali')\n
collect_urls
and rename it to flatten_urls
Variable: THROTTLING
Type: str
In Panther, you can use Throttling
for all APIs at once in core/configs.py
or per API in its @API
decorator
The Throttling
class has 2 field rate
& duration
rate: int
duration: datetime.timedelta
It will return Too Many Request
status_code: 429
if user try to request in the duration
more than rate
And user will baned( getToo Many Request
) for duration
And keep that in mind if you have Throttling
in @API()
, the Throttling
of core/configs.py
will be ignored.
core/configs.py
from datetime import timedelta\n\nfrom panther.throttling import Throttling\n\n\n# User only can request 5 times in every minute\nTHROTTLING = Throttling(rate=5, duration=timedelta(minutes=1))\n
"},{"location":"throttling/#for-single-api-example","title":"For Single API Example:","text":"apis.py
from datetime import timedelta\n\nfrom panther.throttling import Throttling\nfrom panther.app import API\n\n\n# User only can request 5 times in every minute\nInfoThrottling = Throttling(rate=5, duration=timedelta(minutes=1))\n\n\n@API(throttling=InfoThrottling)\nasync def info_api():\n pass\n
"},{"location":"todos/","title":"TODOs","text":""},{"location":"todos/#base","title":"Base","text":"Variable: URLs
Type: str
Required: True
URLs
should point to your root urls
with dotted address (path.module.url_dict
), and it should be dict
.key
of url_routing dict is path
& value is endpoint
or another dict
variable_name
>user/<user_id>/blog/<title>/
endpoint
should have parameters with those names tooasync def profile_api(user_id: int, title: str):
`URLs = 'core.urls.url_routing`\n
from app.urls import app_urls\n\nurl_routing = {\n 'user/': app_urls,\n}\n
app/urls.py
from app.apis import *\n\nurls = {\n 'login/': login_api,\n 'logout/': logout_api,\n 'profile/<user_id>/': profile_api,\n}\n
app/apis.py
...\n\n@API()\nasync def profile_api(user_id: int):\n return User.find_one(id=user_id)\n
Panther create a database connection depends on database middleware you are using on core/configs.py
and you can access to this connection from your models
or direct access from from panther.db.connection import db
Now we are going to create a new API which uses our default database(PantherDB
) and creating a Book
Create Book
model in app/models.py
from panther.db import Model\n\n\nclass Book(Model):\n title: str\n description: str\n pages_count: int\n
Add book
url in app/urls.py
that points to book_api()
...\nfrom app.apis import time_api, book_api\n\n\nurls = {\n '': hello_world,\n 'info/': info,\n 'time/': time_api,\n 'book/': book_api,\n}\n
Create book_api()
in app/apis.py
from panther import status\nfrom panther.app import API\nfrom panther.response import Response\n\n\n@API()\nasync def book_api():\n ...\n return Response(status_code=status.HTTP_201_CREATED) \n
Now we should use the Panther ODM to create a book, it's based on mongo queries, for creation we use insert_one
like this:
from panther import status\nfrom panther.app import API\nfrom panther.response import Response\nfrom app.models import Book\n\n\n@API()\nasync def book_api():\n Book.insert_one(\n title='Python',\n description='Python is good.',\n pages_count=10\n )\n return Response(status_code=status.HTTP_201_CREATED) \n
In next step we are going to explain more about Panther ODM
Required: True
URLs
should point to the root of your urls
file,
-and in that file you should have a dict
name urls
URLs
should point to your root urls
with dotted address (path.module.url_dict
),
+and it should be dict
.key
of urls dict is path
& value is endpoint
or another dict
key
of url_routing dict is path
& value is endpoint
or another dict
`URLs = 'configs/urls.py'`
+ `URLs = 'core.urls.url_routing`
- core/urls.py
from app.urls import app_urls
-urls = {
+url_routing = {
'user/': app_urls,
}