Skip to content

Commit b7d6ac2

Browse files
authored
feat: add handler for synchronous smartapp requests (#36)
1 parent f988ca7 commit b7d6ac2

File tree

7 files changed

+111
-62
lines changed

7 files changed

+111
-62
lines changed

app/api/endpoints/botx.py

Lines changed: 18 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,75 +8,44 @@
88
Bot,
99
BotXMethodCallbackNotFoundError,
1010
UnknownBotAccountError,
11-
UnsupportedBotAPIVersionError,
1211
UnverifiedRequestError,
1312
build_bot_disabled_response,
1413
build_command_accepted_response,
1514
build_unverified_request_response,
1615
)
17-
from pybotx.constants import BOT_API_VERSION
1816

1917
from app.api.dependencies.bot import bot_dependency
18+
from app.api.exceptions.botx import handle_exceptions
2019
from app.logger import logger
21-
from app.settings import settings
2220

2321
router = APIRouter()
2422

2523

2624
@router.post("/command")
25+
@handle_exceptions
2726
async def command_handler(request: Request, bot: Bot = bot_dependency) -> JSONResponse:
2827
"""Receive commands from users. Max timeout - 5 seconds."""
29-
30-
try: # noqa: WPS225
31-
bot.async_execute_raw_bot_command(
32-
await request.json(),
33-
request_headers=request.headers,
34-
)
35-
except ValueError:
36-
error_label = "Bot command validation error"
37-
38-
if settings.DEBUG:
39-
logger.exception(error_label)
40-
else:
41-
logger.warning(error_label)
42-
43-
return JSONResponse(
44-
build_bot_disabled_response(error_label),
45-
status_code=HTTPStatus.SERVICE_UNAVAILABLE,
46-
)
47-
except UnknownBotAccountError as exc:
48-
error_label = f"No credentials for bot {exc.bot_id}"
49-
logger.warning(error_label)
50-
51-
return JSONResponse(
52-
build_bot_disabled_response(error_label),
53-
status_code=HTTPStatus.SERVICE_UNAVAILABLE,
54-
)
55-
except UnsupportedBotAPIVersionError as exc:
56-
error_label = (
57-
f"Unsupported Bot API version: `{exc.version}`. "
58-
f"Set protocol version to `{BOT_API_VERSION}` in Admin panel."
59-
)
60-
logger.warning(error_label)
61-
62-
return JSONResponse(
63-
build_bot_disabled_response(error_label),
64-
status_code=HTTPStatus.SERVICE_UNAVAILABLE,
65-
)
66-
except UnverifiedRequestError as exc:
67-
logger.warning(f"UnverifiedRequestError: {exc.args[0]}")
68-
return JSONResponse(
69-
content=build_unverified_request_response(
70-
status_message=exc.args[0],
71-
),
72-
status_code=HTTPStatus.UNAUTHORIZED,
73-
)
74-
28+
bot.async_execute_raw_bot_command(
29+
await request.json(),
30+
request_headers=request.headers,
31+
)
7532
return JSONResponse(
7633
build_command_accepted_response(), status_code=HTTPStatus.ACCEPTED
7734
)
7835

7936

37+
@router.post("/smartapps/request")
38+
@handle_exceptions
39+
async def sync_smartapp_event_handler(
40+
request: Request, bot: Bot = bot_dependency
41+
) -> JSONResponse:
42+
response = await bot.sync_execute_raw_smartapp_event(
43+
await request.json(),
44+
request_headers=request.headers,
45+
)
46+
return JSONResponse(response.jsonable_dict(), status_code=HTTPStatus.OK)
47+
48+
8049
@router.get("/status")
8150
async def status_handler(request: Request, bot: Bot = bot_dependency) -> JSONResponse:
8251
"""Show bot status and commands list."""

app/api/exceptions/__init__.py

Whitespace-only changes.

app/api/exceptions/botx.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""Handlers for BotX request exceptions."""
2+
3+
from functools import wraps
4+
from http import HTTPStatus
5+
from typing import Any, Callable
6+
7+
from fastapi.responses import JSONResponse
8+
from pybotx import (
9+
UnknownBotAccountError,
10+
UnsupportedBotAPIVersionError,
11+
UnverifiedRequestError,
12+
build_bot_disabled_response,
13+
build_unverified_request_response,
14+
)
15+
from pybotx.constants import BOT_API_VERSION
16+
17+
from app.logger import logger
18+
from app.settings import settings
19+
20+
21+
def handle_exceptions(func: Callable) -> Callable: # noqa: WPS212
22+
@wraps(func)
23+
async def wrapper(*args: Any, **kwargs: Any) -> JSONResponse:
24+
try: # noqa: WPS225
25+
return await func(*args, **kwargs)
26+
except ValueError:
27+
error_label = "Bot command validation error"
28+
29+
if settings.DEBUG:
30+
logger.exception(error_label)
31+
else:
32+
logger.warning(error_label)
33+
34+
return JSONResponse(
35+
build_bot_disabled_response(error_label),
36+
status_code=HTTPStatus.SERVICE_UNAVAILABLE,
37+
)
38+
except UnknownBotAccountError as exc:
39+
error_label = f"No credentials for bot {exc.bot_id}"
40+
logger.warning(error_label)
41+
42+
return JSONResponse(
43+
build_bot_disabled_response(error_label),
44+
status_code=HTTPStatus.SERVICE_UNAVAILABLE,
45+
)
46+
except UnsupportedBotAPIVersionError as exc:
47+
error_label = (
48+
f"Unsupported Bot API version: `{exc.version}`. "
49+
f"Set protocol version to `{BOT_API_VERSION}` in Admin panel."
50+
)
51+
logger.warning(error_label)
52+
53+
return JSONResponse(
54+
build_bot_disabled_response(error_label),
55+
status_code=HTTPStatus.SERVICE_UNAVAILABLE,
56+
)
57+
except UnverifiedRequestError as exc:
58+
logger.warning(f"UnverifiedRequestError: {exc.args[0]}")
59+
return JSONResponse(
60+
content=build_unverified_request_response(
61+
status_message=exc.args[0],
62+
),
63+
status_code=HTTPStatus.UNAUTHORIZED,
64+
)
65+
66+
return wrapper

app/bot/commands/common.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
"""Handlers for default bot commands and system events."""
22

3-
4-
from pybotx import Bot, HandlerCollector, IncomingMessage, SmartAppEvent
3+
from pybotx import (
4+
Bot,
5+
HandlerCollector,
6+
IncomingMessage,
7+
SmartAppEvent,
8+
SyncSmartAppEventResponsePayload,
9+
)
510

611
from app.smartapp.smartapp import smartapp
712

@@ -13,6 +18,13 @@ async def handle_smartapp_event(event: SmartAppEvent, bot: Bot) -> None:
1318
await smartapp.handle_smartapp_event(event, bot)
1419

1520

21+
@collector.sync_smartapp_event
22+
async def handle_sync_smartapp_event(
23+
event: SmartAppEvent, bot: Bot
24+
) -> SyncSmartAppEventResponsePayload:
25+
return await smartapp.handle_sync_smartapp_event(event, bot)
26+
27+
1628
@collector.command("/_test-redis-callback-repo", visible=False)
1729
async def test_redis_callback_repo(message: IncomingMessage, bot: Bot) -> None:
1830
"""Testing redis callback."""

poetry.lock

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml.jinja

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ authors = []
1010
[tool.poetry.dependencies]
1111
python = ">=3.8,<3.12"
1212
13-
pybotx = "~0.65.0"
14-
pybotx-smartapp-rpc = "~0.8.0"
13+
pybotx = ">=0.67.1"
14+
pybotx-smartapp-rpc = ">=0.9.0"
1515
pybotx-smart-logger = "~0.10.1"
1616

1717
fastapi = "~0.110.1"

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ per-file-ignores =
8181
app/db/sqlalchemy.py:WPS442,WPS323
8282
# too complex function
8383
app/services/openapi.py:WPS211,WPS210,WPS231,WPS234,WPS221,WPS110,WPS111
84+
# found module cognitive complexity that is too high
85+
app/api/exceptions/botx.py:WPS232
8486

8587
no-accept-encodings = True
8688
inline-quotes = double

0 commit comments

Comments
 (0)