apidaora
OpenAPI / HTTP / REST API using dataclasses and TypedDict annotation for python
Documentation: https://dutradda.github.io/apidaora
Source Code: https://github.com/dutradda/apidaora
Key Features
- Declaration of request/response as dataclasses and dicts using typing annotations
- Input data validation with jsondaora
- One of the fastest python api framework
- Can run on any asgi server
Requirements
- Python 3.8+
- jsondaora for json validation/parsing
- orjson for json/bytes serialization (jsondaora dependency)
Instalation
$ pip install apidaora
Simple example
from apidaora import appdaora, route
@route.get('/hello')
def hello_controller(name: str) -> str:
return f'Hello {name}!'
app = appdaora(hello_controller)
Running the server (needs uvicorn installed):
uvicorn myapp:app
INFO: Started server process [16220]
INFO: Waiting for application startup.
INFO: ASGI 'lifespan' protocol appears unsupported.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Quering the server (needs curl installed):
curl -i localhost:8000/hello?name=World
HTTP/1.1 200 OK
date: Thu, 1st January 1970 00:00:00 GMT
server: uvicorn
content-type: application/json
content-length: 14
"Hello World!"
Basic example
from typing import TypedDict
from jsondaora import IntegerField, StringField, jsondaora
from apidaora import Header, appdaora, route
class Integer(IntegerField, minimum=18):
...
class String(StringField, max_length=100):
...
class Age(Header, type=Integer):
...
@jsondaora
class You(TypedDict):
name: str
last_name: str
location: str
age: int
@jsondaora
class ReqBody(TypedDict):
last_name: str
@jsondaora
class HelloOutput(TypedDict):
hello_message: str
about_you: You
@route.put('/hello/{name}')
async def hello_controller(
name: str, location: String, age: Age, body: ReqBody
) -> HelloOutput:
you = You(
name=name,
location=location.value,
age=age.value.value,
last_name=body['last_name'],
)
return HelloOutput(
hello_message=await hello_message(name, location.value), about_you=you
)
async def hello_message(name: str, location: str) -> str:
return f'Hello {name}! Welcome to {location}!'
app = appdaora(hello_controller)
Running the server:
uvicorn myapp:app
INFO: Started server process [16220]
INFO: Waiting for application startup.
INFO: ASGI 'lifespan' protocol appears unsupported.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Quering the server:
curl -i -X PUT localhost:8000/hello/Me?location=World \
-H 'x-age: 32' \
-d '{"last_name":"My Self"}'
HTTP/1.1 200 OK
date: Thu, 1st January 1970 00:00:00 GMT
server: uvicorn
content-type: application/json
content-length: 123
{"hello_message":"Hello Me! Welcome to World!","about_you":{"name":"Me","location":"World","age":32,"last_name":"My Self"}}
Example for more request/response details
from http import HTTPStatus
from typing import Dict
from jsondaora import jsondaora
from apidaora import BadRequestError, Header, Response, appdaora, json, route
# Domain layer, here are the domain related definitions
# it is apidaora/framework/http independent
@jsondaora
class You:
name: str
last_name: str
age: int
DB: Dict[str, You] = {}
def add_you(you: You) -> None:
if you.name in DB:
raise YouAlreadyBeenAddedError(you.name)
DB[you.name] = you
def get_you(name: str) -> You:
try:
return DB[name]
except KeyError:
raise YouWereNotFoundError(name)
class DBError(Exception):
@property
def info(self) -> Dict[str, str]:
return {'name': self.args[0]}
class YouAlreadyBeenAddedError(DBError):
name = 'you-already-been-added'
class YouWereNotFoundError(DBError):
name = 'you-were-not-found'
# Application layer, here are the http related definitions
# See: https://dutrdda.github.io/apidaora/tutorial/headers/
class ReqID(Header, type=str, http_name='http_req_id'):
...
@route.post('/you/')
async def add_you_controller(req_id: ReqID, body: You) -> Response:
try:
add_you(body)
except YouAlreadyBeenAddedError as error:
raise BadRequestError(name=error.name, info=error.info) from error
return json(body, HTTPStatus.CREATED, headers=(req_id,))
@route.get('/you/{name}')
async def get_you_controller(name: str, req_id: ReqID) -> Response:
try:
return json(get_you(name), headers=(req_id,))
except YouWereNotFoundError as error:
raise BadRequestError(name=error.name, info=error.info) from error
app = appdaora([add_you_controller, get_you_controller])
Running the server:
uvicorn myapp:app
INFO: Started server process [16220]
INFO: Waiting for application startup.
INFO: ASGI 'lifespan' protocol appears unsupported.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Quering the server:
curl -X POST -i localhost:8000/you/ -H 'http_req_id: 1a2b3c4d' -d '{"name":"Me","last_name":"Myself","age":32}'
HTTP/1.1 201 Created
date: Thu, 1st January 1970 00:00:00 GMT
server: uvicorn
content-type: application/json
content-length: 43
http_req_id: 1a2b3c4d
{"name":"Me","last_name":"Myself","age":32}
curl -i localhost:8000/you/Me -H 'http_req_id: 4d3c2b1a'
HTTP/1.1 200 OK
date: Thu, 1st January 1970 00:00:00 GMT
server: uvicorn
content-type: application/json
content-length: 43
http_req_id: 4d3c2b1a
{"name":"Me","last_name":"Myself","age":32}
Benchmark
The full results can be found here