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.
Files changed (128) hide show
  1. vkx/__init__.py +125 -0
  2. vkx/adapters/__init__.py +17 -0
  3. vkx/adapters/_common.py +173 -0
  4. vkx/adapters/aiohttp.py +42 -0
  5. vkx/adapters/django.py +48 -0
  6. vkx/adapters/fastapi.py +45 -0
  7. vkx/adapters/flask.py +43 -0
  8. vkx/adapters/sanic.py +42 -0
  9. vkx/adapters/starlette.py +41 -0
  10. vkx/client/__init__.py +116 -0
  11. vkx/client/client.py +1280 -0
  12. vkx/client/errors.py +231 -0
  13. vkx/client/http.py +62 -0
  14. vkx/client/pagination.py +244 -0
  15. vkx/client/storage.py +256 -0
  16. vkx/client/tokens.py +427 -0
  17. vkx/client/upload.py +952 -0
  18. vkx/models/__init__.py +36 -0
  19. vkx/models/base_model.py +179 -0
  20. vkx/models/categories.py +245 -0
  21. vkx/models/events/__init__.py +21 -0
  22. vkx/models/events/bot_events.py +317 -0
  23. vkx/models/events/callback_events.py +533 -0
  24. vkx/models/events/enums/__init__.py +5 -0
  25. vkx/models/events/enums/callback_events.py +91 -0
  26. vkx/models/events/enums/group_events.py +64 -0
  27. vkx/models/events/enums/user_events.py +50 -0
  28. vkx/models/events/objects/__init__.py +3 -0
  29. vkx/models/events/objects/group_event_objects.py +403 -0
  30. vkx/models/events/objects/user_event_objects.py +244 -0
  31. vkx/models/events/user_events.py +243 -0
  32. vkx/models/methods/__init__.py +43 -0
  33. vkx/models/methods/account.py +270 -0
  34. vkx/models/methods/ads.py +822 -0
  35. vkx/models/methods/app_widgets.py +111 -0
  36. vkx/models/methods/apps.py +359 -0
  37. vkx/models/methods/auth.py +20 -0
  38. vkx/models/methods/base_category.py +92 -0
  39. vkx/models/methods/board.py +294 -0
  40. vkx/models/methods/bugtracker.py +245 -0
  41. vkx/models/methods/calls.py +30 -0
  42. vkx/models/methods/database.py +188 -0
  43. vkx/models/methods/docs.py +181 -0
  44. vkx/models/methods/donut.py +62 -0
  45. vkx/models/methods/downloaded_games.py +18 -0
  46. vkx/models/methods/fave.py +351 -0
  47. vkx/models/methods/friends.py +510 -0
  48. vkx/models/methods/gifts.py +24 -0
  49. vkx/models/methods/groups.py +1341 -0
  50. vkx/models/methods/lead_forms.py +146 -0
  51. vkx/models/methods/likes.py +133 -0
  52. vkx/models/methods/market.py +962 -0
  53. vkx/models/methods/messages.py +1302 -0
  54. vkx/models/methods/newsfeed.py +392 -0
  55. vkx/models/methods/notes.py +169 -0
  56. vkx/models/methods/notifications.py +57 -0
  57. vkx/models/methods/orders.py +107 -0
  58. vkx/models/methods/pages.py +137 -0
  59. vkx/models/methods/photos.py +842 -0
  60. vkx/models/methods/podcasts.py +22 -0
  61. vkx/models/methods/polls.py +212 -0
  62. vkx/models/methods/pretty_cards.py +104 -0
  63. vkx/models/methods/search.py +28 -0
  64. vkx/models/methods/secure.py +163 -0
  65. vkx/models/methods/stats.py +60 -0
  66. vkx/models/methods/status.py +34 -0
  67. vkx/models/methods/storage.py +53 -0
  68. vkx/models/methods/store.py +79 -0
  69. vkx/models/methods/stories.py +298 -0
  70. vkx/models/methods/streaming.py +43 -0
  71. vkx/models/methods/translations.py +20 -0
  72. vkx/models/methods/users.py +212 -0
  73. vkx/models/methods/utils.py +129 -0
  74. vkx/models/methods/video.py +826 -0
  75. vkx/models/methods/wall.py +669 -0
  76. vkx/models/methods/widgets.py +50 -0
  77. vkx/models/objects.py +19901 -0
  78. vkx/models/responses/__init__.py +63 -0
  79. vkx/models/responses/account.py +45 -0
  80. vkx/models/responses/ads.py +73 -0
  81. vkx/models/responses/app_widgets.py +19 -0
  82. vkx/models/responses/apps.py +101 -0
  83. vkx/models/responses/auth.py +15 -0
  84. vkx/models/responses/base.py +11 -0
  85. vkx/models/responses/board.py +52 -0
  86. vkx/models/responses/bugtracker.py +81 -0
  87. vkx/models/responses/calls.py +24 -0
  88. vkx/models/responses/database.py +63 -0
  89. vkx/models/responses/docs.py +61 -0
  90. vkx/models/responses/donut.py +23 -0
  91. vkx/models/responses/downloaded_games.py +10 -0
  92. vkx/models/responses/fave.py +36 -0
  93. vkx/models/responses/friends.py +115 -0
  94. vkx/models/responses/gifts.py +12 -0
  95. vkx/models/responses/groups.py +330 -0
  96. vkx/models/responses/lead_forms.py +25 -0
  97. vkx/models/responses/likes.py +42 -0
  98. vkx/models/responses/market.py +202 -0
  99. vkx/models/responses/messages.py +333 -0
  100. vkx/models/responses/newsfeed.py +156 -0
  101. vkx/models/responses/notes.py +18 -0
  102. vkx/models/responses/notifications.py +43 -0
  103. vkx/models/responses/orders.py +12 -0
  104. vkx/models/responses/pages.py +30 -0
  105. vkx/models/responses/photos.py +216 -0
  106. vkx/models/responses/podcasts.py +12 -0
  107. vkx/models/responses/pretty_cards.py +33 -0
  108. vkx/models/responses/search.py +15 -0
  109. vkx/models/responses/store.py +30 -0
  110. vkx/models/responses/stories.py +99 -0
  111. vkx/models/responses/streaming.py +22 -0
  112. vkx/models/responses/translations.py +15 -0
  113. vkx/models/responses/users.py +39 -0
  114. vkx/models/responses/utils.py +12 -0
  115. vkx/models/responses/video.py +206 -0
  116. vkx/models/responses/wall.py +185 -0
  117. vkx/models/responses/widgets.py +18 -0
  118. vkx/py.typed +0 -0
  119. vkx/server/__init__.py +26 -0
  120. vkx/server/_polling.py +250 -0
  121. vkx/server/bots_longpoll.py +64 -0
  122. vkx/server/callback.py +54 -0
  123. vkx/server/callback_server.py +212 -0
  124. vkx/server/longpoll.py +70 -0
  125. vkx-0.1.0.dist-info/METADATA +898 -0
  126. vkx-0.1.0.dist-info/RECORD +128 -0
  127. vkx-0.1.0.dist-info/WHEEL +4 -0
  128. 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
+ ]
@@ -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")
@@ -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")
@@ -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
@@ -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)])