eXpress SmartApp JSON-RPC library
Project description
BotX-SmartApp-RPC
Библиотека, позволяющая писать смартаппы, используя наш JSONRPC-like протокол
Установка
Используя poetry:
poetry add pybotx-smartapp-rpc
Добавление RPC методов
- Создайте класс для входящих аргументов:
from pybotx_smartapp_rpc import RPCArgsBaseModel
...
class SumArgs(RPCArgsBaseModel):
a: int
b: int
- Создайте RPC метод:
from pybotx_smartapp_rpc import SmartApp, RPCRouter, RPCResultResponse
...
rpc = RPCRouter()
...
@rpc.method("sum")
async def sum(
smartapp: SmartApp, rpc_arguments: SumArgs
) -> RPCResultResponse[int]:
return RPCResultResponse(result=rpc_arguments.a + rpc_arguments.b)
# Так же у метода может не быть аргументов:
@rpc.method("answer")
async def answer(smartapp: SmartApp) -> RPCResultResponse[int]:
return RPCResultResponse(result=42)
- Создайте экземпляр
SmartAppRPCи подключите роутер из прошлого пункта:
from pybotx_smartapp_rpc import SmartAppRPC
from anywhere import methods
...
smartapp = SmartAppRPC(routers=[methods.rpc])
- Сделайте хендлер для
smartapp_eventи вызывайте в нем хендлер библиотеки:
a. Aсинхронный подход:@collector.smartapp_event async def handle_smartapp_event(event: SmartAppEvent, bot: Bot) -> None: await smartapp.handle_smartapp_event(event, bot)
б. Синхронный подход:from pybotx import SyncSmartAppEventResponsePayload ... @collector.sync_smartapp_event async def handle_sync_smartapp_event(event: SmartAppEvent, bot: Bot) -> SyncSmartAppEventResponsePayload: return await smartapp.handle_sync_smartapp_event(event, bot)
Продвинутая работа с библиотекой
- В
RPCResultResponseможно передаватьbotx.Fileфайлы.
@rpc.method("get-pdf")
async def get_pdf(
smartapp: SmartApp, rpc_arguments: GetPDFArgs
) -> RPCResultResponse[None]:
...
return RPCResultResponse(result=None, files=[...])
- В
SmartAppRPC,RPCRouterиRPCRouter.methodможно передать мидлвари, сначала будут вызваны мидлвари приложения, затем мидлвари роутера и в конце мидлвари метода.
smartapp = SmartAppRPC(..., middlewares=[...])
...
rpc = RPCRouter(middlewares=[...])
...
@rpc.method("sum", middlewares=[...])
RPCArgsBaseModelэто алиас дляpydantic.BaseModel, вы можете использовать все возможности исходного класса.
from uuid import UUID
...
class DelUserArgs(RPCArgsBaseModel):
# pydantic сериализует входящую строку в UUID
user_huid: UUID
- Через объект
smartapp, передаваемый в хендлер можно получить доступ кeventиbot.
...
@rpc.method("del-user")
async def del_user(
smartapp: SmartApp, rpc_arguments: DelUserArgs
) -> RPCResultResponse[None]:
await smartapp.bot.send_message(
body="Done",
bot_id=smartapp.event.bot.id,
chat_id=smartapp.event.chat.id,
)
...
- Используя метод
smartapp.send_eventможно отправлять RPC ивенты сref: null.
Это может пригодиться при необходимости отправки уведомления не в ответ на RPC запрос.
@rpc.method("notify-me")
async def notify_me(
smartapp: SmartApp, rpc_arguments: NotifyMeArgs
) -> RPCResultResponse[None]:
...
await smartapp.send_event("notified", files=[notify_file])
...
- Используя метод
smartapp.send_pushилиsmartapp.send_custom_pushможно отправлять пуш уведомлений на клиент. И обновлять счетчик уведомлений на икноке смартапа.
@rpc.method("notify-me")
async def notify_me(
smartapp: SmartApp, rpc_arguments: NotifyMeArgs
) -> RPCResultResponse[None]:
await smartapp.send_push(42, "You have 42 new emails!")
...
- В мидлварях можно создавать новые объекты в
smartapp.state, чтобы потом использовать их в хендлерах.
async def user_middleware(smartapp: SmartApp, rpc_arguments: RPCArgsBaseModel, call_next: Callable) -> RPCResponse[User]:
smartapp.state.user = await User.get(smartapp.message.user_huid)
return await call_next(smartapp, rpc_arguments)
@rpc.method("get-user-fullname")
async def get_user_fullname(smartapp: SmartApp) -> RPCResultResponse[str]:
return RPCResultResponse(result=smartapp.state.user.fullname)
- Можно выбрасывать пользовательские RPC ошибки, которые будут отправлены как ответ на RPC запрос.
from pybotx_smartapp_rpc import RPCErrorExc, RPCError
class CustomError(RPCError):
id: str = "CUSTOM_ERROR"
reason: str = "It's error reason"
...
@rpc.method("return-error")
async def return_error(smartapp: SmartApp, rpc_arguments: RaiseOneErrorArgs) -> None:
# one error
raise RPCErrorExc(
CustomError(
meta={"args": rpc_arguments.model_dump()},
)
)
# or list of errors
raise RPCErrorExc(
[
CustomError(
meta={"args": rpc_arguments.model_dump()},
),
RPCError(
reason="It's one more error reason",
id="CUSTOM_ERROR_NUMBER_TWO",
meta={"args": rpc_arguments.model_dump()},
)
]
)
- Можно добавить хендлер на определенный тип исключений. В него будут отправлять исключения того же и дочерних классов.
Хендлер обязан возвращать
RPCErrorResponse, ошибки из которого будут отправлены источнику запроса.
from pybotx_smartapp_rpc import SmartAppRPC, RPCErrorResponse
...
async def key_error_handler(exc: KeyError, smartapp: SmartApp) -> RPCErrorResponse:
key = exc.args[0]
return RPCErrorResponse(
errors=[
RPCError(
reason=f"Key {key} not found.",
id="KEY_ERROR",
meta={"key": key},
),
]
)
smartapp = SmartAppRPC(..., exception_handlers={KeyError: key_error_handler})
Swagger documentation
Можно подключить rpc роутеры к авто генерируемой документации FastAPI и использовать документацию в Swagger. Для этого необходимо переопределить функцию для генерации OpenAPI схемы:
from fastapi import FastAPI
application = FastAPI()
def get_custom_openapi():
return custom_openapi(
title="Smartapp API",
version="0.1.0",
fastapi_routes=application.routes,
rpc_router=smartapp.router,
openapi_version="3.0.2",
)
application.openapi = get_custom_openapi
Пример функции custom_openapi:
from fastapi.encoders import jsonable_encoder
from fastapi.openapi.models import OpenAPI
from fastapi.openapi.utils import get_openapi
from pybotx_smartapp_rpc import RPCRouter
from pybotx_smartapp_rpc.openapi_utils import *
from starlette.routing import BaseRoute
def custom_openapi(
*,
title: str,
version: str,
fastapi_routes: Sequence[BaseRoute],
rpc_router: RPCRouter,
**kwargs: Any,
) -> Dict[str, Any]:
openapi_dict = get_openapi(
title=title,
version=version,
routes=fastapi_routes,
**kwargs,
)
paths: Dict[str, Dict[str, Any]] = {}
flat_rpc_models = get_rpc_flat_models_from_routes(rpc_router)
rpc_model_name_map = get_rpc_model_name_map(flat_rpc_models)
rpc_definitions = get_rpc_model_definitions(
flat_models=flat_rpc_models, model_name_map=rpc_model_name_map
)
for method_name in rpc_router.rpc_methods.keys():
if not rpc_router.rpc_methods[method_name].include_in_schema:
continue
path = get_rpc_openapi_path( # type: ignore
method_name=method_name,
route=rpc_router.rpc_methods[method_name],
model_name_map=rpc_model_name_map,
)
if path:
paths.setdefault(f"/{method_name}", {}).update(path)
if rpc_definitions:
openapi_dict.setdefault("components", {}).setdefault("schemas", {}).update(
{k: rpc_definitions[k] for k in sorted(rpc_definitions)}
)
openapi_dict.setdefault("paths", {}).update(paths)
return jsonable_encoder(OpenAPI(**openapi_dict), by_alias=True, exclude_none=True)
Возможности RPC Swagger
- Можно добавлять теги к запросам, анaлогично FastAPI.
rpc = RPCRouter(tags=["RPC"])
@rpc.method("documented-method", tags=["docs"])
async def docs(
smartapp: SmartApp, rpc_arguments: DocumentedArgs
) -> RPCResultResponse[DocumentedResponse]:
"""Desctiption of this method."""
...
- Можно переопределять pydantic модель успешного ответа.
@rpc.method("method", return_type=Response)
async def method(
smartapp: SmartApp, rpc_arguments: MethodArgs
) -> RPCResultResponse[int]:
...
- Можно исключать некоторые методы из документации.
rpc = RPCRouter(include_in_schema=False)
@rpc.method("_hidden_method", include_in_schema=False)
async def hidden_method(smartapp: SmartApp) -> RPCResultResponse[int]:
...
- Можно определять пользовательские ошибки.
from pybotx_smartapp_rpc import RPCError, RPCErrorExc
class Meta(BaseModel):
user_id: int
username: str
class UsernotFoundError(RPCError):
"""Error description for swagger."""
id: str = "UserNotFound"
reason: str = "User not found in db"
meta: Meta
@rpc.method("method-with_error", errors=[UsernotFoundError])
async def get_user(
smartapp: SmartApp, rpc_arguments: UserArgs
) -> RPCResultResponse[User]:
...
raise RPCErrorExc(UsernotFoundError(meta={"user_id": 1, "username": "test"}))
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 pybotx_smartapp_rpc-0.13.1.tar.gz.
File metadata
- Download URL: pybotx_smartapp_rpc-0.13.1.tar.gz
- Upload date:
- Size: 16.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.3.3 CPython/3.13.1 Linux/6.8.0-1044-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d594f398607a310e1f165c3f7e3ea4c2a2d644e08a1e5066d4a240cb1dcc8d1b
|
|
| MD5 |
9b0a687fc175f11fe11b54c04e570952
|
|
| BLAKE2b-256 |
8b9435f51488950a021ee0c613822270240a2585062f0118379d7f713b3c745d
|
File details
Details for the file pybotx_smartapp_rpc-0.13.1-py3-none-any.whl.
File metadata
- Download URL: pybotx_smartapp_rpc-0.13.1-py3-none-any.whl
- Upload date:
- Size: 19.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.3.3 CPython/3.13.1 Linux/6.8.0-1044-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e6d417f45638cf55a10e9a33d59c6abcfab731b7ec9678272004f98a9431211a
|
|
| MD5 |
2402415b76776d6528d4b69badfb81e2
|
|
| BLAKE2b-256 |
65692f83dfed7d87d07207027e5550615fe3cbefde937600fd5a4571dd9c45be
|