Async web framework toolkit based on Starlette
Project description
adc-webkit
Web-фреймворк на базе Starlette для построения async HTTP API. Предоставляет класс-based эндпоинты, JWT-аутентификацию, парсинг тела запроса (JSON, form-data, streaming), типизированный request context через Pydantic и автоматическую генерацию OpenAPI/Swagger.
Установка
pip install git+https://github.com/ascet-dev/adc-webkit.git@main
Быстрый старт
import asyncio
from pydantic import BaseModel
from adc_webkit.web import Web, Route
from adc_webkit.web.endpoints import JsonEndpoint, Ctx, Response
class HelloResponse(BaseModel):
message: str
class HelloEndpoint(JsonEndpoint):
response = Response(model=HelloResponse)
async def execute(self, ctx: Ctx) -> dict:
return {"message": "Hello, World!"}
class API(Web):
routes = [
Route(method="GET", path="/hello", view=HelloEndpoint),
]
api = API.create()
asyncio.run(api.start("0.0.0.0", 8000))
# Swagger UI: http://localhost:8000/doc
API
Web
Основной класс приложения. Оборачивает Starlette.
from adc_webkit.web import Web, Route, Doc
class API(Web):
routes = [
Route(method="GET", path="/users/{user_id}", view=GetUserEndpoint),
Route(method="POST", path="/users", view=CreateUserEndpoint),
]
doc = Doc(
title="My API",
version="2.0.0",
description="User management API",
)
api = API.create()
# Привязка внешних компонентов к lifecycle приложения
api.bind_component("db", db_pool, start_method="start", stop_method="stop")
# Запуск
await api.start("0.0.0.0", 8000, logs_config={})
Swagger UI доступен по адресу /doc, OpenAPI JSON -- /doc/swagger.json.
Route
from adc_webkit.web import Route
Route(method="GET", path="/users/{user_id}", view=GetUserEndpoint)
method -- один из: GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD, TRACE.
Эндпоинты
JsonEndpoint
Для эндпоинтов, возвращающих JSON:
from pydantic import BaseModel
from adc_webkit.web.endpoints import JsonEndpoint, Ctx, Response
from adc_webkit.web.openapi import Doc
from adc_webkit.errors import NotFound
class UserQuery(BaseModel):
user_id: int # path-параметр
class UserOut(BaseModel):
id: int
name: str
class GetUserEndpoint(JsonEndpoint):
query = UserQuery # модель для path + query параметров
response = Response(model=UserOut, errors=[NotFound])
doc = Doc(tags=["users"], summary="Get user by ID")
async def execute(self, ctx: Ctx) -> dict:
user = await get_user(ctx.query.user_id)
if not user:
raise NotFound("User not found")
return user
StreamEndpoint
Для потоковой отдачи файлов:
from adc_webkit.web.endpoints import StreamEndpoint, Ctx
from adc_webkit.types import DownloadFile
class DownloadReportEndpoint(StreamEndpoint):
async def execute(self, ctx: Ctx) -> DownloadFile:
async def generate():
yield b"header\n"
for row in data:
yield row.encode()
return DownloadFile(file=generate(), filename="report.csv")
Возвращает StreamingResponse с Content-Disposition: attachment.
Request Context (Ctx)
Типизированный контекст запроса, передаваемый в execute:
@dataclass
class Ctx(Generic[Q, B, H]):
query: Q # path + query параметры (Pydantic model)
body: B # тело запроса (Pydantic model)
headers: H # заголовки (Pydantic model)
request: Request # оригинальный Starlette Request
auth_payload: Any # результат аутентификации
Модели для query, body, headers указываются как атрибуты класса эндпоинта:
class CreateUserBody(BaseModel):
name: str
email: str
class CreateUserEndpoint(JsonEndpoint):
query = None # нет path/query параметров
body = CreateUserBody # парсинг тела запроса
headers = None # нет валидации заголовков
async def execute(self, ctx: Ctx) -> dict:
return await create_user(ctx.body.name, ctx.body.email)
Response
Описание ответа эндпоинта (для OpenAPI и валидации):
from adc_webkit.web.endpoints import Response
Response(
model=UserOut, # Pydantic модель ответа
status_code=200, # HTTP статус
description="success", # описание для OpenAPI
errors=[NotFound, BadRequest], # ошибки для OpenAPI
)
Аутентификация
JWT
from adc_webkit.web.auth import JWT
jwt_auth = JWT(
public_key="-----BEGIN PUBLIC KEY-----\n...",
algorithms=["RS256"],
audience="my-api",
)
class ProtectedEndpoint(JsonEndpoint):
auth = jwt_auth
async def execute(self, ctx: Ctx) -> dict:
user_id = ctx.auth_payload["sub"]
return {"user_id": user_id}
JWT читает заголовок Authorization: Bearer <token>, декодирует через python-jose.
Для динамического получения публичного ключа (JWKS) -- наследуйте JWT и переопределите get_public_key(request).
Кастомная аутентификация
from adc_webkit.web.auth import HTTPAuth
class APIKeyAuth(HTTPAuth):
header_name = "X-API-Key"
async def get_auth_payload(self, request) -> dict:
key = request.headers.get(self.header_name)
if key != "expected-key":
raise Unauthorized("Invalid API key")
return {"api_key": key}
Body Parsers
По умолчанию используется JsonParser. Можно переключить:
from adc_webkit.web.body_parsers import FormDataParser, StreamParser, ParserFactory
# Form data (multipart/form-data)
class UploadEndpoint(JsonEndpoint):
body = UploadSchema
body_parser = ParserFactory(FormDataParser, max_files=10)
# Raw stream
class StreamUploadEndpoint(JsonEndpoint):
body_parser = ParserFactory(StreamParser)
Типы файлов
from adc_webkit.types import UploadFile, DownloadFile
class UploadSchema(BaseModel):
file: UploadFile(size_le=10_000_000, extension_in=[".jpg", ".png"])
description: str
UploadFile() -- фабрика, возвращающая Pydantic Annotated-тип с валидацией размера и расширения.
Ошибки
Все ошибки наследуют AppError и автоматически конвертируются в JSON-ответ:
from adc_webkit.errors import (
BadRequest, # 400
Unauthorized, # 401
Forbidden, # 403
NotFound, # 404
MethodNotAllowed, # 405
RequestTimeout, # 408
Conflict, # 409
Gone, # 410
PayloadTooLarge, # 413
UnprocessableEntity,# 422
ServerError, # 500
IntegrationError, # 500
)
raise NotFound("User not found")
raise BadRequest("Invalid email", errors=["email must contain @"])
Формат ответа:
{"message": "User not found", "errors": [], "code": 404}
Примеры
CRUD API
from pydantic import BaseModel
from adc_webkit.web import Web, Route
from adc_webkit.web.endpoints import JsonEndpoint, Ctx, Response
from adc_webkit.web.openapi import Doc
from adc_webkit.web.auth import JWT
from adc_webkit.errors import NotFound
jwt = JWT(public_key="...", algorithms=["RS256"])
class ItemId(BaseModel):
item_id: int
class ItemBody(BaseModel):
name: str
price: float
class ItemOut(BaseModel):
id: int
name: str
price: float
class GetItem(JsonEndpoint):
query = ItemId
auth = jwt
response = Response(model=ItemOut, errors=[NotFound])
doc = Doc(tags=["items"], summary="Get item")
async def execute(self, ctx: Ctx) -> dict:
item = await db.items.get_by_id(ctx.query.item_id)
if not item:
raise NotFound("Item not found")
return item
class CreateItem(JsonEndpoint):
body = ItemBody
auth = jwt
response = Response(model=ItemOut, status_code=201)
doc = Doc(tags=["items"], summary="Create item")
async def execute(self, ctx: Ctx) -> dict:
return await db.items.create(**ctx.body.model_dump())
class API(Web):
routes = [
Route(method="GET", path="/items/{item_id}", view=GetItem),
Route(method="POST", path="/items", view=CreateItem),
]
doc = Web.doc.__class__(title="Items API", version="1.0.0")
api = API.create()
Требования
- Python >= 3.8
- starlette >= 0.47.0
- uvicorn >= 0.27.0
- pydantic >= 2.11.7
- python-jose[cryptography] >= 3.5.0
- ujson >= 5.10.0
- swagger-ui-py
Лицензия
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file adc_webkit-0.2.0.tar.gz.
File metadata
- Download URL: adc_webkit-0.2.0.tar.gz
- Upload date:
- Size: 14.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c874e1b03a64c34f926dee2b325ea171cdb5918a9b94f5b368a6641e768008e8
|
|
| MD5 |
270dfcf26acffabbc59278254011cb51
|
|
| BLAKE2b-256 |
6bcbdcb6df4c7971729f3029b476539554e7f0640d4d3be2b3fb1e9f6da8a615
|
File details
Details for the file adc_webkit-0.2.0-py3-none-any.whl.
File metadata
- Download URL: adc_webkit-0.2.0-py3-none-any.whl
- Upload date:
- Size: 21.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b0eaf9d3975992a0ccd4310bc5be9d30e86d9910aa4b1460f1fad4692d5f1fe
|
|
| MD5 |
61b8784cd279c31b374cb3edcc47369f
|
|
| BLAKE2b-256 |
34b57029d67132188653fdd81beb9ffb006e2dfb48c4aa14aafe315f2793eea3
|