vkx 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vkx/__init__.py +125 -0
- vkx/adapters/__init__.py +17 -0
- vkx/adapters/_common.py +173 -0
- vkx/adapters/aiohttp.py +42 -0
- vkx/adapters/django.py +48 -0
- vkx/adapters/fastapi.py +45 -0
- vkx/adapters/flask.py +43 -0
- vkx/adapters/sanic.py +42 -0
- vkx/adapters/starlette.py +41 -0
- vkx/client/__init__.py +116 -0
- vkx/client/client.py +1280 -0
- vkx/client/errors.py +231 -0
- vkx/client/http.py +62 -0
- vkx/client/pagination.py +244 -0
- vkx/client/storage.py +256 -0
- vkx/client/tokens.py +427 -0
- vkx/client/upload.py +952 -0
- vkx/models/__init__.py +36 -0
- vkx/models/base_model.py +179 -0
- vkx/models/categories.py +245 -0
- vkx/models/events/__init__.py +21 -0
- vkx/models/events/bot_events.py +317 -0
- vkx/models/events/callback_events.py +533 -0
- vkx/models/events/enums/__init__.py +5 -0
- vkx/models/events/enums/callback_events.py +91 -0
- vkx/models/events/enums/group_events.py +64 -0
- vkx/models/events/enums/user_events.py +50 -0
- vkx/models/events/objects/__init__.py +3 -0
- vkx/models/events/objects/group_event_objects.py +403 -0
- vkx/models/events/objects/user_event_objects.py +244 -0
- vkx/models/events/user_events.py +243 -0
- vkx/models/methods/__init__.py +43 -0
- vkx/models/methods/account.py +270 -0
- vkx/models/methods/ads.py +822 -0
- vkx/models/methods/app_widgets.py +111 -0
- vkx/models/methods/apps.py +359 -0
- vkx/models/methods/auth.py +20 -0
- vkx/models/methods/base_category.py +92 -0
- vkx/models/methods/board.py +294 -0
- vkx/models/methods/bugtracker.py +245 -0
- vkx/models/methods/calls.py +30 -0
- vkx/models/methods/database.py +188 -0
- vkx/models/methods/docs.py +181 -0
- vkx/models/methods/donut.py +62 -0
- vkx/models/methods/downloaded_games.py +18 -0
- vkx/models/methods/fave.py +351 -0
- vkx/models/methods/friends.py +510 -0
- vkx/models/methods/gifts.py +24 -0
- vkx/models/methods/groups.py +1341 -0
- vkx/models/methods/lead_forms.py +146 -0
- vkx/models/methods/likes.py +133 -0
- vkx/models/methods/market.py +962 -0
- vkx/models/methods/messages.py +1302 -0
- vkx/models/methods/newsfeed.py +392 -0
- vkx/models/methods/notes.py +169 -0
- vkx/models/methods/notifications.py +57 -0
- vkx/models/methods/orders.py +107 -0
- vkx/models/methods/pages.py +137 -0
- vkx/models/methods/photos.py +842 -0
- vkx/models/methods/podcasts.py +22 -0
- vkx/models/methods/polls.py +212 -0
- vkx/models/methods/pretty_cards.py +104 -0
- vkx/models/methods/search.py +28 -0
- vkx/models/methods/secure.py +163 -0
- vkx/models/methods/stats.py +60 -0
- vkx/models/methods/status.py +34 -0
- vkx/models/methods/storage.py +53 -0
- vkx/models/methods/store.py +79 -0
- vkx/models/methods/stories.py +298 -0
- vkx/models/methods/streaming.py +43 -0
- vkx/models/methods/translations.py +20 -0
- vkx/models/methods/users.py +212 -0
- vkx/models/methods/utils.py +129 -0
- vkx/models/methods/video.py +826 -0
- vkx/models/methods/wall.py +669 -0
- vkx/models/methods/widgets.py +50 -0
- vkx/models/objects.py +19901 -0
- vkx/models/responses/__init__.py +63 -0
- vkx/models/responses/account.py +45 -0
- vkx/models/responses/ads.py +73 -0
- vkx/models/responses/app_widgets.py +19 -0
- vkx/models/responses/apps.py +101 -0
- vkx/models/responses/auth.py +15 -0
- vkx/models/responses/base.py +11 -0
- vkx/models/responses/board.py +52 -0
- vkx/models/responses/bugtracker.py +81 -0
- vkx/models/responses/calls.py +24 -0
- vkx/models/responses/database.py +63 -0
- vkx/models/responses/docs.py +61 -0
- vkx/models/responses/donut.py +23 -0
- vkx/models/responses/downloaded_games.py +10 -0
- vkx/models/responses/fave.py +36 -0
- vkx/models/responses/friends.py +115 -0
- vkx/models/responses/gifts.py +12 -0
- vkx/models/responses/groups.py +330 -0
- vkx/models/responses/lead_forms.py +25 -0
- vkx/models/responses/likes.py +42 -0
- vkx/models/responses/market.py +202 -0
- vkx/models/responses/messages.py +333 -0
- vkx/models/responses/newsfeed.py +156 -0
- vkx/models/responses/notes.py +18 -0
- vkx/models/responses/notifications.py +43 -0
- vkx/models/responses/orders.py +12 -0
- vkx/models/responses/pages.py +30 -0
- vkx/models/responses/photos.py +216 -0
- vkx/models/responses/podcasts.py +12 -0
- vkx/models/responses/pretty_cards.py +33 -0
- vkx/models/responses/search.py +15 -0
- vkx/models/responses/store.py +30 -0
- vkx/models/responses/stories.py +99 -0
- vkx/models/responses/streaming.py +22 -0
- vkx/models/responses/translations.py +15 -0
- vkx/models/responses/users.py +39 -0
- vkx/models/responses/utils.py +12 -0
- vkx/models/responses/video.py +206 -0
- vkx/models/responses/wall.py +185 -0
- vkx/models/responses/widgets.py +18 -0
- vkx/py.typed +0 -0
- vkx/server/__init__.py +26 -0
- vkx/server/_polling.py +250 -0
- vkx/server/bots_longpoll.py +64 -0
- vkx/server/callback.py +54 -0
- vkx/server/callback_server.py +212 -0
- vkx/server/longpoll.py +70 -0
- vkx-0.1.0.dist-info/METADATA +898 -0
- vkx-0.1.0.dist-info/RECORD +128 -0
- vkx-0.1.0.dist-info/WHEEL +4 -0
- vkx-0.1.0.dist-info/licenses/LICENSE +21 -0
vkx/__init__.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""vkx: клиент VK API (vkx.client), типы API/событий (vkx.models) и серверный блок (vkx.server)."""
|
|
2
|
+
|
|
3
|
+
from .client import (
|
|
4
|
+
API_HOST,
|
|
5
|
+
API_VERSION,
|
|
6
|
+
AUTH_ERROR_CODES,
|
|
7
|
+
CAPTCHA_ERROR_CODE,
|
|
8
|
+
CATEGORY_SCOPES,
|
|
9
|
+
DEFAULT_GROUP_INTERVAL,
|
|
10
|
+
DEFAULT_MAX_RETRIES,
|
|
11
|
+
DEFAULT_PAGINATION_LIMIT,
|
|
12
|
+
DEFAULT_RETRY_BACKOFF,
|
|
13
|
+
DEFAULT_USER_INTERVAL,
|
|
14
|
+
EXECUTE_BATCH_LIMIT,
|
|
15
|
+
EXECUTE_CODE_BYTES_LIMIT,
|
|
16
|
+
INVALID_TOKEN_CODE,
|
|
17
|
+
PAGINATION_LIMITS,
|
|
18
|
+
RATE_LIMIT_ERROR_CODES,
|
|
19
|
+
SCOPE_TITLES,
|
|
20
|
+
TRANSIENT_ERROR_CODES,
|
|
21
|
+
HttpClient,
|
|
22
|
+
HttpResponse,
|
|
23
|
+
JSONStore,
|
|
24
|
+
MemoryStore,
|
|
25
|
+
PendingApiCall,
|
|
26
|
+
SQLiteStore,
|
|
27
|
+
State,
|
|
28
|
+
StaticTokenSource,
|
|
29
|
+
Store,
|
|
30
|
+
TokenSource,
|
|
31
|
+
TokenSourceError,
|
|
32
|
+
VKAuthError,
|
|
33
|
+
VKCaptchaError,
|
|
34
|
+
VKClient,
|
|
35
|
+
VKError,
|
|
36
|
+
VKErrorCode,
|
|
37
|
+
VKFloodError,
|
|
38
|
+
VKInvalidTokenError,
|
|
39
|
+
VKPermissionError,
|
|
40
|
+
VKRateLimitError,
|
|
41
|
+
VKRequestError,
|
|
42
|
+
VKServerError,
|
|
43
|
+
VKTimeoutError,
|
|
44
|
+
VKTransportError,
|
|
45
|
+
WebCookieSource,
|
|
46
|
+
build_error,
|
|
47
|
+
default_store,
|
|
48
|
+
limit_for,
|
|
49
|
+
merge_pages,
|
|
50
|
+
method_scope,
|
|
51
|
+
open_store,
|
|
52
|
+
paginate,
|
|
53
|
+
scope_keys,
|
|
54
|
+
scope_titles,
|
|
55
|
+
validate_web_cookies,
|
|
56
|
+
)
|
|
57
|
+
from .server import (
|
|
58
|
+
BotsLongPoll,
|
|
59
|
+
CallbackListener,
|
|
60
|
+
CallbackServer,
|
|
61
|
+
LongPoll,
|
|
62
|
+
create_callback_server,
|
|
63
|
+
parse_callback,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
__all__ = [
|
|
67
|
+
"API_HOST",
|
|
68
|
+
"API_VERSION",
|
|
69
|
+
"AUTH_ERROR_CODES",
|
|
70
|
+
"CAPTCHA_ERROR_CODE",
|
|
71
|
+
"CATEGORY_SCOPES",
|
|
72
|
+
"DEFAULT_GROUP_INTERVAL",
|
|
73
|
+
"DEFAULT_MAX_RETRIES",
|
|
74
|
+
"DEFAULT_PAGINATION_LIMIT",
|
|
75
|
+
"DEFAULT_RETRY_BACKOFF",
|
|
76
|
+
"DEFAULT_USER_INTERVAL",
|
|
77
|
+
"EXECUTE_BATCH_LIMIT",
|
|
78
|
+
"EXECUTE_CODE_BYTES_LIMIT",
|
|
79
|
+
"INVALID_TOKEN_CODE",
|
|
80
|
+
"PAGINATION_LIMITS",
|
|
81
|
+
"RATE_LIMIT_ERROR_CODES",
|
|
82
|
+
"SCOPE_TITLES",
|
|
83
|
+
"TRANSIENT_ERROR_CODES",
|
|
84
|
+
"BotsLongPoll",
|
|
85
|
+
"CallbackListener",
|
|
86
|
+
"CallbackServer",
|
|
87
|
+
"HttpClient",
|
|
88
|
+
"HttpResponse",
|
|
89
|
+
"JSONStore",
|
|
90
|
+
"LongPoll",
|
|
91
|
+
"MemoryStore",
|
|
92
|
+
"PendingApiCall",
|
|
93
|
+
"SQLiteStore",
|
|
94
|
+
"State",
|
|
95
|
+
"StaticTokenSource",
|
|
96
|
+
"Store",
|
|
97
|
+
"TokenSource",
|
|
98
|
+
"TokenSourceError",
|
|
99
|
+
"VKAuthError",
|
|
100
|
+
"VKCaptchaError",
|
|
101
|
+
"VKClient",
|
|
102
|
+
"VKError",
|
|
103
|
+
"VKErrorCode",
|
|
104
|
+
"VKFloodError",
|
|
105
|
+
"VKInvalidTokenError",
|
|
106
|
+
"VKPermissionError",
|
|
107
|
+
"VKRateLimitError",
|
|
108
|
+
"VKRequestError",
|
|
109
|
+
"VKServerError",
|
|
110
|
+
"VKTimeoutError",
|
|
111
|
+
"VKTransportError",
|
|
112
|
+
"WebCookieSource",
|
|
113
|
+
"build_error",
|
|
114
|
+
"create_callback_server",
|
|
115
|
+
"default_store",
|
|
116
|
+
"limit_for",
|
|
117
|
+
"merge_pages",
|
|
118
|
+
"method_scope",
|
|
119
|
+
"open_store",
|
|
120
|
+
"paginate",
|
|
121
|
+
"parse_callback",
|
|
122
|
+
"scope_keys",
|
|
123
|
+
"scope_titles",
|
|
124
|
+
"validate_web_cookies",
|
|
125
|
+
]
|
vkx/adapters/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Веб-адаптеры Callback API для конкретных фреймворков.
|
|
2
|
+
|
|
3
|
+
Ядро живёт в ``._common`` и не зависит от фреймворков. Каждый модуль
|
|
4
|
+
(``fastapi``, ``starlette``, ``aiohttp``, ``sanic``, ``django``, ``flask``)
|
|
5
|
+
подключает свой фреймворк лениво — при импорте самого модуля, поэтому в
|
|
6
|
+
зависимостях vkx ничего лишнего не появляется: адаптер импортируют только те,
|
|
7
|
+
у кого этот фреймворк уже установлен.
|
|
8
|
+
|
|
9
|
+
Async-фреймворки используют ``CallbackDispatch`` (задача в текущем loop),
|
|
10
|
+
синхронные — ``SyncCallbackDispatch`` (handler выполняется в том же потоке, что
|
|
11
|
+
и обработка колбека, и обязан быть синхронным). Ни отдельный loop, ни пул
|
|
12
|
+
потоков не нужны.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from ._common import CallbackDispatch, EventHandler, SyncCallbackDispatch
|
|
16
|
+
|
|
17
|
+
__all__ = ("CallbackDispatch", "EventHandler", "SyncCallbackDispatch")
|
vkx/adapters/_common.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Общая логика веб-адаптеров Callback API.
|
|
2
|
+
|
|
3
|
+
VK ждёт от вебхука ответ: если он задерживается, событие приходит повторно, а
|
|
4
|
+
при регулярных задержках сообщество может отключить сервер. Поэтому для
|
|
5
|
+
async-фреймворков уведомление разбирается, ответ ``ok`` отдаётся сразу, а
|
|
6
|
+
обработчик запускается фоновой задачей. Исключение — ``confirmation``: код
|
|
7
|
+
подтверждения адреса нужно вернуть немедленно, откладывать его нельзя.
|
|
8
|
+
|
|
9
|
+
Диспетчеры:
|
|
10
|
+
|
|
11
|
+
- ``CallbackDispatch`` — для async-фреймворков (FastAPI, Starlette, aiohttp,
|
|
12
|
+
Sanic, Django ASGI): задача ставится в текущий loop.
|
|
13
|
+
- ``SyncCallbackDispatch`` — для синхронных (Flask, Django WSGI): синхронный
|
|
14
|
+
обработчик вызывается прямо в текущем потоке, без пула потоков и loop.
|
|
15
|
+
|
|
16
|
+
Адаптеры конкретных фреймворков (``vkx.adapters.fastapi``,
|
|
17
|
+
``vkx.adapters.starlette``, ``vkx.adapters.aiohttp``, ``vkx.adapters.sanic``,
|
|
18
|
+
``vkx.adapters.django``, ``vkx.adapters.flask``) — тонкие обёртки над ними.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import asyncio
|
|
24
|
+
import inspect
|
|
25
|
+
import logging
|
|
26
|
+
from collections.abc import Awaitable, Callable
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from ..models.events.callback_events import CallbackEvent
|
|
30
|
+
from ..models.events.enums import CallbackEventType
|
|
31
|
+
from ..server.callback import RawCallback
|
|
32
|
+
from ..server.callback_server import CallbackListener
|
|
33
|
+
|
|
34
|
+
EventHandler = Callable[[CallbackEvent], Any | Awaitable[Any]]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class _DispatchBase:
|
|
38
|
+
"""Общее для диспетчеров: разбор уведомления и ответ VK."""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
listener: CallbackListener,
|
|
43
|
+
*,
|
|
44
|
+
handler: EventHandler | None = None,
|
|
45
|
+
logger: logging.Logger | None = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
self._listener = listener
|
|
48
|
+
self._handler = handler
|
|
49
|
+
self._logger = logger or logging.getLogger("vkx.adapters")
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def listener(self) -> CallbackListener:
|
|
53
|
+
return self._listener
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def handler(self) -> EventHandler | None:
|
|
57
|
+
return self._handler
|
|
58
|
+
|
|
59
|
+
def dispatch(self, raw: RawCallback) -> tuple[str, int]:
|
|
60
|
+
"""Разобрать уведомление и решить, что ответить VK.
|
|
61
|
+
|
|
62
|
+
:return: пара ``(тело, статус)`` для HTTP-ответа.
|
|
63
|
+
"""
|
|
64
|
+
event = self._listener.handle(raw)
|
|
65
|
+
if event is None: # чужой group_id / плохой secret / дубликат
|
|
66
|
+
return "ok", 200
|
|
67
|
+
if event.type is CallbackEventType.CONFIRMATION:
|
|
68
|
+
return self._confirmation(event), 200
|
|
69
|
+
self.schedule(event)
|
|
70
|
+
return "ok", 200
|
|
71
|
+
|
|
72
|
+
def schedule(self, event: CallbackEvent) -> None:
|
|
73
|
+
"""Выполнить обработчик события."""
|
|
74
|
+
raise NotImplementedError
|
|
75
|
+
|
|
76
|
+
def _confirmation(self, event: CallbackEvent) -> str:
|
|
77
|
+
server = self._listener.get(event.group_id) if event.group_id else None
|
|
78
|
+
code = server.confirmation_code if server else None
|
|
79
|
+
return code or ""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class CallbackDispatch(_DispatchBase):
|
|
83
|
+
"""Ответ ``ok`` сразу + обработка в фоне задачей текущего loop.
|
|
84
|
+
|
|
85
|
+
Для async-фреймворков (FastAPI, Starlette, aiohttp, Sanic, Django ASGI).
|
|
86
|
+
``handler`` может быть как async, так и обычной функцией: синхронная уходит
|
|
87
|
+
в отдельный поток (``asyncio.to_thread``), чтобы не блокировать loop.
|
|
88
|
+
|
|
89
|
+
Задачи удерживаются внутри, чтобы их не собрал GC, и логируются при падении;
|
|
90
|
+
на остановке приложения дождитесь их через ``aclose``.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(
|
|
94
|
+
self,
|
|
95
|
+
listener: CallbackListener,
|
|
96
|
+
*,
|
|
97
|
+
handler: EventHandler | None = None,
|
|
98
|
+
logger: logging.Logger | None = None,
|
|
99
|
+
) -> None:
|
|
100
|
+
super().__init__(listener, handler=handler, logger=logger)
|
|
101
|
+
self._tasks: set[asyncio.Task[None]] = set()
|
|
102
|
+
|
|
103
|
+
def schedule(self, event: CallbackEvent) -> None:
|
|
104
|
+
if self._handler is None:
|
|
105
|
+
self._logger.debug("Событие %s отброшено: обработчик не задан", event.type)
|
|
106
|
+
return
|
|
107
|
+
task = asyncio.create_task(self._run(event))
|
|
108
|
+
self._tasks.add(task)
|
|
109
|
+
task.add_done_callback(self._tasks.discard)
|
|
110
|
+
|
|
111
|
+
async def aclose(self, *, timeout: float | None = None) -> None:
|
|
112
|
+
"""Дождаться завершения фоновых задач."""
|
|
113
|
+
tasks = set(self._tasks)
|
|
114
|
+
if tasks:
|
|
115
|
+
await asyncio.wait(tasks, timeout=timeout)
|
|
116
|
+
|
|
117
|
+
async def __aenter__(self) -> CallbackDispatch:
|
|
118
|
+
return self
|
|
119
|
+
|
|
120
|
+
async def __aexit__(self, *exc_info: object) -> None:
|
|
121
|
+
await self.aclose()
|
|
122
|
+
|
|
123
|
+
async def _run(self, event: CallbackEvent) -> None:
|
|
124
|
+
handler = self._handler
|
|
125
|
+
if handler is None:
|
|
126
|
+
return
|
|
127
|
+
try:
|
|
128
|
+
if inspect.iscoroutinefunction(handler):
|
|
129
|
+
await handler(event)
|
|
130
|
+
else:
|
|
131
|
+
result = await asyncio.to_thread(handler, event)
|
|
132
|
+
if inspect.isawaitable(result):
|
|
133
|
+
await result
|
|
134
|
+
except Exception: # noqa: BLE001
|
|
135
|
+
self._logger.exception("Ошибка обработки события %s", event.type)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class SyncCallbackDispatch(_DispatchBase):
|
|
139
|
+
"""Синхронный диспетчер для Flask и Django WSGI.
|
|
140
|
+
|
|
141
|
+
Синхронный ``handler`` вызывается прямо в том же потоке, что и первичная
|
|
142
|
+
обработка колбека (WSGI-сервер обычно выделяет отдельный поток на запрос),
|
|
143
|
+
поэтому ни пул потоков, ни event loop не нужны. ``handler`` обязан быть
|
|
144
|
+
синхронным: async-функция вызовет ``TypeError`` — для неё нужен
|
|
145
|
+
``CallbackDispatch``.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
def __init__(
|
|
149
|
+
self,
|
|
150
|
+
listener: CallbackListener,
|
|
151
|
+
*,
|
|
152
|
+
handler: EventHandler | None = None,
|
|
153
|
+
logger: logging.Logger | None = None,
|
|
154
|
+
) -> None:
|
|
155
|
+
super().__init__(listener, handler=handler, logger=logger)
|
|
156
|
+
if handler is not None and inspect.iscoroutinefunction(handler):
|
|
157
|
+
raise TypeError(
|
|
158
|
+
"SyncCallbackDispatch требует синхронный handler; "
|
|
159
|
+
"для async-функции используйте CallbackDispatch"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def schedule(self, event: CallbackEvent) -> None:
|
|
163
|
+
handler = self._handler
|
|
164
|
+
if handler is None:
|
|
165
|
+
self._logger.debug("Событие %s отброшено: обработчик не задан", event.type)
|
|
166
|
+
return
|
|
167
|
+
try:
|
|
168
|
+
handler(event)
|
|
169
|
+
except Exception: # noqa: BLE001
|
|
170
|
+
self._logger.exception("Ошибка обработки события %s", event.type)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
__all__ = ("CallbackDispatch", "EventHandler", "SyncCallbackDispatch")
|
vkx/adapters/aiohttp.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""aiohttp-адаптер Callback API.
|
|
2
|
+
|
|
3
|
+
aiohttp в зависимости vkx не тянется: модуль импортируют только те, у кого он
|
|
4
|
+
уже есть.
|
|
5
|
+
|
|
6
|
+
Пример:
|
|
7
|
+
|
|
8
|
+
from vkx.adapters.aiohttp import create_callback_router
|
|
9
|
+
|
|
10
|
+
dispatch = CallbackDispatch(listener, handler=handle_vk_event)
|
|
11
|
+
app = web.Application()
|
|
12
|
+
app.add_routes(create_callback_router(dispatch))
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from aiohttp import web
|
|
18
|
+
|
|
19
|
+
from ._common import CallbackDispatch
|
|
20
|
+
|
|
21
|
+
__all__ = ("create_callback_router",)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def create_callback_router(
|
|
25
|
+
dispatch: CallbackDispatch,
|
|
26
|
+
*,
|
|
27
|
+
path: str = "/vk/callback",
|
|
28
|
+
name: str = "vk_callback",
|
|
29
|
+
) -> web.RouteTableDef:
|
|
30
|
+
"""Собрать ``RouteTableDef`` с POST-роутом для уведомлений Callback API.
|
|
31
|
+
|
|
32
|
+
Роут сам отвечает VK ``ok`` (или ``confirmation``-кодом) и запускает
|
|
33
|
+
обработчик из ``dispatch`` в фоне. Подключите через ``app.add_routes``.
|
|
34
|
+
"""
|
|
35
|
+
routes = web.RouteTableDef()
|
|
36
|
+
|
|
37
|
+
@routes.post(path, name=name)
|
|
38
|
+
async def vk_callback(request: web.Request) -> web.Response:
|
|
39
|
+
body, status = dispatch.dispatch(await request.read())
|
|
40
|
+
return web.Response(text=body, status=status)
|
|
41
|
+
|
|
42
|
+
return routes
|
vkx/adapters/django.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Django-адаптер Callback API.
|
|
2
|
+
|
|
3
|
+
Django в зависимости vkx не тянется: модуль импортируют только те, у кого он
|
|
4
|
+
уже есть.
|
|
5
|
+
|
|
6
|
+
Django WSGI синхронный, поэтому используйте ``SyncCallbackDispatch`` с обычным
|
|
7
|
+
(не async) хендлером — он выполнится в том же потоке, что и обработка колбека:
|
|
8
|
+
|
|
9
|
+
from vkx.adapters import SyncCallbackDispatch
|
|
10
|
+
from vkx.adapters.django import create_callback_view
|
|
11
|
+
|
|
12
|
+
dispatch = SyncCallbackDispatch(listener, handler=handle_vk_event)
|
|
13
|
+
|
|
14
|
+
urlpatterns = [path("vk/callback", create_callback_view(dispatch))]
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from collections.abc import Callable
|
|
20
|
+
from typing import cast
|
|
21
|
+
|
|
22
|
+
from django.http import HttpRequest, HttpResponse
|
|
23
|
+
from django.views.decorators.csrf import csrf_exempt as csrf_exempt_decorator
|
|
24
|
+
|
|
25
|
+
from ._common import CallbackDispatch
|
|
26
|
+
|
|
27
|
+
__all__ = ("create_callback_view",)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def create_callback_view(
|
|
31
|
+
dispatch: CallbackDispatch,
|
|
32
|
+
*,
|
|
33
|
+
csrf_exempt: bool = True,
|
|
34
|
+
) -> Callable[[HttpRequest], HttpResponse]:
|
|
35
|
+
"""Вернуть Django-view для уведомлений Callback API.
|
|
36
|
+
|
|
37
|
+
View отвечает VK ``ok`` (или ``confirmation``-кодом) и запускает обработчик
|
|
38
|
+
из ``dispatch`` в фоне. Подключите в ``urls.py`` через ``path``. По умолчанию
|
|
39
|
+
view освобождён от CSRF-проверки — VK не присылает CSRF-токен.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def view(request: HttpRequest) -> HttpResponse:
|
|
43
|
+
body, status = dispatch.dispatch(request.body)
|
|
44
|
+
return HttpResponse(body.encode("utf-8"), status=status, content_type="text/plain")
|
|
45
|
+
|
|
46
|
+
if csrf_exempt:
|
|
47
|
+
return cast(Callable[[HttpRequest], HttpResponse], csrf_exempt_decorator(view))
|
|
48
|
+
return view
|
vkx/adapters/fastapi.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""FastAPI-адаптер Callback API.
|
|
2
|
+
|
|
3
|
+
FastAPI в зависимости vkx не тянется: модуль импортируют только те, у кого он
|
|
4
|
+
уже есть.
|
|
5
|
+
|
|
6
|
+
Пример:
|
|
7
|
+
|
|
8
|
+
from vkx.adapters.fastapi import create_callback_router
|
|
9
|
+
from vkx.server import CallbackListener
|
|
10
|
+
|
|
11
|
+
listener = CallbackListener([server])
|
|
12
|
+
dispatch = CallbackDispatch(listener, handler=handle_vk_event)
|
|
13
|
+
app.include_router(create_callback_router(dispatch))
|
|
14
|
+
|
|
15
|
+
``handle_vk_event`` получит событие уже после того, как VK прочитает ``ok``.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from fastapi import APIRouter, Request, Response
|
|
21
|
+
|
|
22
|
+
from ._common import CallbackDispatch
|
|
23
|
+
|
|
24
|
+
__all__ = ("create_callback_router",)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def create_callback_router(
|
|
28
|
+
dispatch: CallbackDispatch,
|
|
29
|
+
*,
|
|
30
|
+
path: str = "/vk/callback",
|
|
31
|
+
name: str = "vk_callback",
|
|
32
|
+
) -> APIRouter:
|
|
33
|
+
"""Собрать ``APIRouter`` с POST-роутом для уведомлений Callback API.
|
|
34
|
+
|
|
35
|
+
Роут сам отвечает VK ``ok`` (или ``confirmation``-кодом) и запускает
|
|
36
|
+
обработчик из ``dispatch`` в фоне. Подключите через ``app.include_router``.
|
|
37
|
+
"""
|
|
38
|
+
router = APIRouter()
|
|
39
|
+
|
|
40
|
+
@router.post(path, name=name, include_in_schema=False)
|
|
41
|
+
async def vk_callback(request: Request) -> Response:
|
|
42
|
+
body, status = dispatch.dispatch(await request.body())
|
|
43
|
+
return Response(content=body, status_code=status, media_type="text/plain")
|
|
44
|
+
|
|
45
|
+
return router
|
vkx/adapters/flask.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Flask-адаптер Callback API.
|
|
2
|
+
|
|
3
|
+
Flask в зависимости vkx не тянется: модуль импортируют только те, у кого он
|
|
4
|
+
уже есть.
|
|
5
|
+
|
|
6
|
+
Flask WSGI синхронный, поэтому используйте ``SyncCallbackDispatch`` с обычным
|
|
7
|
+
(не async) хендлером — он выполнится в том же потоке, что и обработка колбека:
|
|
8
|
+
|
|
9
|
+
from vkx.adapters import SyncCallbackDispatch
|
|
10
|
+
from vkx.adapters.flask import create_callback_router
|
|
11
|
+
|
|
12
|
+
dispatch = SyncCallbackDispatch(listener, handler=handle_vk_event)
|
|
13
|
+
app.register_blueprint(create_callback_router(dispatch))
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from flask import Blueprint, Response, request
|
|
19
|
+
|
|
20
|
+
from ._common import SyncCallbackDispatch
|
|
21
|
+
|
|
22
|
+
__all__ = ("create_callback_router",)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def create_callback_router(
|
|
26
|
+
dispatch: SyncCallbackDispatch,
|
|
27
|
+
*,
|
|
28
|
+
path: str = "/vk/callback",
|
|
29
|
+
name: str = "vk_callback",
|
|
30
|
+
) -> Blueprint:
|
|
31
|
+
"""Собрать ``Blueprint`` с POST-роутом для уведомлений Callback API.
|
|
32
|
+
|
|
33
|
+
Роут сам отвечает VK ``ok`` (или ``confirmation``-кодом) и запускает
|
|
34
|
+
обработчик из ``dispatch`` в фоне. Подключите через ``app.register_blueprint``.
|
|
35
|
+
"""
|
|
36
|
+
bp = Blueprint(name, __name__)
|
|
37
|
+
|
|
38
|
+
@bp.post(path)
|
|
39
|
+
def vk_callback() -> Response:
|
|
40
|
+
body, status = dispatch.dispatch(request.get_data())
|
|
41
|
+
return Response(body, status=status, mimetype="text/plain")
|
|
42
|
+
|
|
43
|
+
return bp
|
vkx/adapters/sanic.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Sanic-адаптер Callback API.
|
|
2
|
+
|
|
3
|
+
Sanic в зависимости vkx не тянется: модуль импортируют только те, у кого он
|
|
4
|
+
уже есть.
|
|
5
|
+
|
|
6
|
+
Пример:
|
|
7
|
+
|
|
8
|
+
from vkx.adapters.sanic import create_callback_router
|
|
9
|
+
|
|
10
|
+
dispatch = CallbackDispatch(listener, handler=handle_vk_event)
|
|
11
|
+
app.blueprint(create_callback_router(dispatch))
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from sanic import Blueprint, Request
|
|
17
|
+
from sanic.response import HTTPResponse, text
|
|
18
|
+
|
|
19
|
+
from ._common import CallbackDispatch
|
|
20
|
+
|
|
21
|
+
__all__ = ("create_callback_router",)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def create_callback_router(
|
|
25
|
+
dispatch: CallbackDispatch,
|
|
26
|
+
*,
|
|
27
|
+
path: str = "/vk/callback",
|
|
28
|
+
name: str = "vk_callback",
|
|
29
|
+
) -> Blueprint:
|
|
30
|
+
"""Собрать ``Blueprint`` с POST-роутом для уведомлений Callback API.
|
|
31
|
+
|
|
32
|
+
Роут сам отвечает VK ``ok`` (или ``confirmation``-кодом) и запускает
|
|
33
|
+
обработчик из ``dispatch`` в фоне. Подключите через ``app.blueprint``.
|
|
34
|
+
"""
|
|
35
|
+
bp = Blueprint(name)
|
|
36
|
+
|
|
37
|
+
@bp.post(path, name=name)
|
|
38
|
+
async def vk_callback(request: Request) -> HTTPResponse:
|
|
39
|
+
body, status = dispatch.dispatch(request.body)
|
|
40
|
+
return text(body, status=status)
|
|
41
|
+
|
|
42
|
+
return bp
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Starlette-адаптер Callback API.
|
|
2
|
+
|
|
3
|
+
Starlette в зависимости vkx не тянется: модуль импортируют только те, у кого
|
|
4
|
+
он уже есть (сам Starlette, FastAPI и т.п.).
|
|
5
|
+
|
|
6
|
+
Пример:
|
|
7
|
+
|
|
8
|
+
from vkx.adapters.starlette import create_callback_router
|
|
9
|
+
|
|
10
|
+
dispatch = CallbackDispatch(listener, handler=handle_vk_event)
|
|
11
|
+
app = Starlette(routes=[*create_callback_router(dispatch).routes])
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from starlette.requests import Request
|
|
17
|
+
from starlette.responses import PlainTextResponse
|
|
18
|
+
from starlette.routing import Route, Router
|
|
19
|
+
|
|
20
|
+
from ._common import CallbackDispatch
|
|
21
|
+
|
|
22
|
+
__all__ = ("create_callback_router",)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def create_callback_router(
|
|
26
|
+
dispatch: CallbackDispatch,
|
|
27
|
+
*,
|
|
28
|
+
path: str = "/vk/callback",
|
|
29
|
+
name: str = "vk_callback",
|
|
30
|
+
) -> Router:
|
|
31
|
+
"""Собрать ``Router`` с POST-роутом для уведомлений Callback API.
|
|
32
|
+
|
|
33
|
+
Роут сам отвечает VK ``ok`` (или ``confirmation``-кодом) и запускает
|
|
34
|
+
обработчик из ``dispatch`` в фоне.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
async def vk_callback(request: Request) -> PlainTextResponse:
|
|
38
|
+
body, status = dispatch.dispatch(await request.body())
|
|
39
|
+
return PlainTextResponse(body, status_code=status)
|
|
40
|
+
|
|
41
|
+
return Router(routes=[Route(path, vk_callback, methods=["POST"], name=name)])
|